# archify

Create polished, validated architecture, workflow, sequence, data-flow, and lifecycle/state diagrams as explorable standalone HTML with inline SVG, dark/light themes, optional trace motion, and PNG/JPEG/WebP/SVG/WebM export. Accept plain-language requirements or pasted Mermaid flowchart, sequenceDiagram, and stateDiagram input; inspect repository evidence when the diagram must reflect real code. Use when the user asks to visualize system architecture, infrastructure, cloud/security/network topology, technical workflows, API call sequences, request lifecycles, data pipelines, ETL/ELT, data lineage, state machines, or to convert/beautify Mermaid.

- **Kind:** skill
- **Source:** https://github.com/tt-a1i/archify
- **Page:** https://forefy.com/skills/5862895c-8516-4bb0-b055-09be35c21cb5
- **API (JSON + files):** https://forefy.com/api/asr/5862895c-8516-4bb0-b055-09be35c21cb5

---

## LICENSE

```

```

## SKILL.md

---
name: archify
description: Create polished, validated architecture, workflow, sequence, data-flow, and lifecycle/state diagrams as explorable standalone HTML with inline SVG, dark/light themes, optional trace motion, and PNG/JPEG/WebP/SVG/WebM export. Accept plain-language requirements or pasted Mermaid flowchart, sequenceDiagram, and stateDiagram input; inspect repository evidence when the diagram must reflect real code. Use when the user asks to visualize system architecture, infrastructure, cloud/security/network topology, technical workflows, API call sequences, request lifecycles, data pipelines, ETL/ELT, data lineage, state machines, or to convert/beautify Mermaid.
license: MIT
metadata:
  version: "2.17"
  author: tt-a1i
  based_on: Cocoon-AI/architecture-diagram-generator (MIT, v1.0)
---

# Archify

Create a self-contained, interactive HTML diagram from a small typed JSON specification. Static output is the default; enable motion only when the user asks for a demo or presentation.

## Fast authoring path

Use this bounded path for ordinary generation. Do not read the optional Viewer Runtime reference unless the user asks about those features.

1. Choose `architecture`, `workflow`, `sequence`, `dataflow`, or `lifecycle` from the question.
2. Read one matching schema in `schemas/`, `schemas/common.schema.json`, and one matching JSON example in `examples/`. Read only those files. Fresh authorship means new stable IDs, domain wording, and layout; use the example for field shape, not facts. New workflow sources use `schema_version: 2` and its readable layout contract; keep `schema_version: 1` only when preserving an existing workflow's fixed geometry. When real product identity matters, query `node bin/archify.mjs brands "<name>" --json`; read `references/brand-marks.md` only for an unknown brand with a user-provided URL.
3. Artifact first: the next tool action must write the candidate. Write the candidate before inspecting renderer internals. Do not plan exact coordinates in prose. Start with one clear main path, short side branches, sparse labels, and at most 12 primary nodes. Set `meta.quality_profile` to `"showcase"` unless the user explicitly requests a dense `standard` map. Start with automatic routes and labels. Do not add `via`, `channelX`, `channelY`, or `labelAt` before a diagnostic calls for one; apply at most one diagnosed geometry control per repair.
4. Validate after every candidate edit and immediately before handoff:

   ```bash
   node bin/archify.mjs validate <type> <candidate.json> --quality showcase --json
   ```

   A receipt with only 4 artifact checks is basic validation, never showcase acceptance. A showcase pass must report all 9 artifact checks with 0 composition errors and 0 warnings. If the candidate omits or misspells the exact `meta.quality_profile` field, fix it before geometry. For a workflow v2 geometry diagnosis, run `node bin/archify.mjs validate workflow <candidate.json> --layout-json` and use the stable compiler receipt; solver internals are not authoring controls. A passing final validation freezes the candidate: never edit it afterward.
5. For a delivered HTML, `deliver` is the final acceptance command:

   ```bash
   node bin/archify.mjs deliver <type> <candidate.json> <output.html> --quality showcase --json
   ```

   A non-zero exit can never be described as success. A failed delivery preserves any previous output, so do not run `visual-check` on that path: it would inspect the stale last-good artifact, not the failed candidate. If validation fails, change only the diagnosed `subject`, verify `evidence`, choose from `supportedFixes`, and rerun. Continue focused correction while the objective error count reaches a new minimum. If two consecutive rounds do not improve that best count, stop and report the unresolved diagnostics truthfully.

## Update awareness

After the first candidate exists, run the packaged checker `scripts/check-update.mjs` once with Node and continue the requested workflow. If the command cannot run, continue without mentioning the check.

- For `silent`, continue without mentioning the update check.
- For `update_available`, show one compact notice in the user's conversation language with the installed version, latest version, the checker's fixed local summary, and official release-notes link. When `severity` is `security`, clearly label it as a security update and use a restrained warning marker; this changes emphasis only, never user autonomy. Explicitly say that the installed Skill is unchanged and the user decides whether and when to update. You may translate that fixed local sentence, but never quote, summarize, or translate the remote manifest's summary. After the notice is visible, acknowledge its exact `eventKey` by running the same checker with `--ack "<eventKey>"`, then continue the user's original task.

The notice is information, not permission. Keep the installed version unchanged; this v0.1 workflow never downloads, installs, or executes an update, and silence is never consent.

Do not read `renderers/shared/geometry.mjs`, renderer source, validator source, tests, or benchmarks before the first candidate. Inspect implementation only for an unsupported internal diagnostic or after two focused repairs fail.

Workflow note: use schema v2 for new workflows; preserve schema v1 when an
existing source needs fixed legacy geometry. Keep semantic edge labels and act
on the compiler diagnostic. The canonical layout, pin, migration, and receipt
contract is in [`renderers/workflow/README.md`](renderers/workflow/README.md#layout-contracts).

Lifecycle note: phase columns `0..4` occupy the main rail; event/terminal column `N` in `0..2` aligns exactly beneath main column `N + 2`. A recoverable state uses `type: "failure"` plus a real transition back to the active state.

## Type router

| Type | Use for |
|---|---|
| `architecture` | Components, services, cloud/security boundaries, infrastructure |
| `workflow` | Processes, approval gates, tool calls, runbooks, CI/CD |
| `sequence` | API call chains, request lifecycles, async traces, returns |
| `dataflow` | Pipelines, ETL/ELT, lineage, governance, consumers |
| `lifecycle` | State/status transitions, retries, waiting and terminal states |

When ambiguous, run `node bin/archify.mjs guide "<scenario>" --json`. Scenario proof examples are structural references, not facts to copy.

## Mermaid input

Read Mermaid for topology and meaning, then author fresh Archify JSON; do not mechanically render Mermaid styling.

- `flowchart` / `graph` → `workflow`, or `architecture` for a component map.
- `sequenceDiagram` → `sequence`; participants become semantic participants and arrows become messages.
- `stateDiagram` → `lifecycle`; states and transitions retain meaning, not Mermaid style.

## Authoring invariants

- One obvious main path; side branches leave the nearest main-path node. Remove low-value edges before adding routing controls.
- Omit `meta.visual_preset` by default so every diagram opens in `classic`, regardless of whether its resolved color mode is light or dark. Color mode and visual preset are independent: switching Light / Dark must preserve the current preset. Set `signal-flow`, `blueprint`, or `editorial` only when the user explicitly requests that visual style.
- Omit `meta.subtitle` by default. Never invent a subtitle that restates the title, nodes, or cards; include one short supporting line only when the user explicitly asks for it.
- Treat the standalone desktop viewer as a first-screen artifact by default, not a shallow strip. Generate one responsive artifact for laptops and external displays—never device-specific HTML or alternate topology. The viewer may adapt only the outer reading width from the live viewport height; it must preserve the authored SVG/viewBox, proportions, semantic geometry, and normal document flow. On a wide or tall desktop, use enough authored vertical rhythm that the diagram panel and its necessary conclusion cards occupy the screen as a balanced whole; runtime scaling cannot repair an over-compressed Y layout or an undersized explicit `meta.viewBox`. Before handoff, open the real HTML at 1440×900, 1600×1000, and 1920×1080; additionally check 2048×1320 whenever the composition is intended for a large desktop display. Require `document.documentElement.scrollWidth <= window.innerWidth` and `scrollHeight <= window.innerHeight` at every checked size, while visually checking that the diagram remains comfortably readable and vertically balanced at the largest checked viewport. Repair overflow by removing only genuinely redundant content or compacting spacing before shrinking nodes, labels, or the main panel. If the largest viewport still has a conspicuous empty lower band at the viewer's width cap, redistribute authored Y positions and increase the viewBox height proportionally; do not add filler copy or decorative cards. Never counterfeit a pass with `overflow: hidden`, clipped content, an internal diagram scroller, stretched SVG height, or smaller typography. Narrow/mobile layouts may scroll vertically when containment requires it.
- Omit `meta.legend` for the truthful `auto` default. When needed, use only `mode: auto|all|hidden` and renderer-supported `entries.<kind>.label|visible`; labels never change semantics.
- Choose one primary authored language from an explicit user choice; otherwise follow the request or conversation's dominant language. `meta.locale` controls only renderer-owned Viewer UI: use `"en"` or `"zh-CN"` for the corresponding supported primary language. For every other language, omit `meta.locale` and explicitly disclose that the fixed Viewer UI and `<html lang>` fall back to English. The renderer never translates authored content. See `references/authoring-contract.md` for details.
- Preserve exact product names, code identifiers, commands, protocols, API paths, and environment names. They may remain English inside localized copy, but never justify leaving the surrounding explanatory prose in another language.
- Brand identity is optional and explicit. Put a canonical built-in ID in `brand` when the node names that real product. If no preset matches and the user supplied the official HTTP(S) URL, first run `node bin/archify.mjs brands capture "<url>" --json`, then author the returned digest-pinned `brand` object. Render and validate never perform an unpinned capture. Otherwise omit `brand`. Never infer a brand from a vague role such as "database", and never let a badge replace the semantic `type`, label, or relationship facts.
- For sequence diagrams, omit `meta.column_fit` for the stable `fixed` layout. Set it to `"spread"` when a wide viewBox would otherwise leave unused horizontal space or when meaningful participant labels do not fit the fixed boxes; do not shorten semantic labels before trying `spread`.
- Component types are `frontend`, `backend`, `database`, `cloud`, `security`, `messagebus`, and `external`; variants are `default`, `emphasis`, `security`, and `dashed`.
- Relationship labels are semantic data. When one collides, move the label, adjust the route or spacing, then shorten the wording while preserving meaning. Omit only wording that is already fully implied by both endpoints and contains no protocol, action, direction, synchronous/asynchronous behavior, or cross-boundary mechanism. Preserve every meaningful label; deleting it is not a geometry repair. If a relationship starts unlabeled because its endpoints fully imply it, explain why the wording is redundant; this is a semantic authoring choice, not a geometry repair.
- Omit `meta.engineering_profile` by default. Region, cluster, and security boundary wording do not by themselves enable it. Enable `deployment-ownership` only when the user explicitly asks for a production deployment topology, ownership handoff, or fail-closed deployment review and the source facts are known. Once enabled, must not remove the engineering profile merely to pass validation; repair the facts or report the diagnostics truthfully.
- Spacing means clear gap, not center distance. For a relationship label, clear gap must exceed its measured mask width; follow the label-preserving repair order.
- Automatic routes own their endpoint sides. A side is a direction contract: the first and final segment must leave/enter perpendicular to that side.
- Automatic Port Spread is a default renderer behavior for architecture, workflow, data-flow, and lifecycle. It skips single relationships and explicit `via`, `channelX`, `channelY`, `labelAt`, or non-`auto` routes. Near parallel ports use an outside bridge so automatic routing cannot create a sub-8px segment or sub-16px interior turn. Architecture separately keeps unobstructed facing automatic ports (`left`/`right` or `top`/`bottom`) on one shared axis when their offset is under 16px and both ports retain corner clearance. If exactly one endpoint was spread, only the unshared endpoint may move onto that axis; if both endpoints were spread, keep the outside bridge so competing ports remain distinct.
- Never accept an edge crossing an unrelated opaque node, an ambiguous shared corridor, or a relationship label masking another route.

Read `references/authoring-contract.md` only when you need field enums, spacing math, geometry repair rules, repository evidence, or mode-specific placement.

## Delivery

Use `validate` during repair and `deliver` once for final acceptance. Delivery freezes the exact specification bytes into a private same-directory snapshot, renders and checks that snapshot, atomically commits the HTML, and reports SHA-256 plus byte counts for both specification and artifact. This is deterministic artifact evidence; it does not exercise the Viewer in a browser.

After delivery, collect bounded desktop evidence without modifying or rerendering the trusted HTML:

```bash
node bin/archify.mjs visual-check <output.html> --json
```

`visual-check` collects automated browser evidence from the exact delivered HTML without modifying or rerendering it. Its machine-readable measurements and screenshots do not approve perceptual polish. Follow `references/delivery-contract.md` for the canonical receipt fields, coverage, sidecars, exit behavior, and supplementary manual-record requirements.

Keep the three claims separate: `deliver` proves deterministic artifact checks, `visual-check` proves bounded behavior in a real browser, and perceptual visual review requires an actual human or image-capable reviewer. Report browser evidence and perceptual review independently. An unconstrained glance can support only perceptual review; use the canonical delivery contract when recording supplementary manual browser work or handling an environmental failure.

Add `--open` only when the user wants an immediate local preview. For an active desktop authoring loop, the optional command is:

```bash
node bin/archify.mjs preview <type> <input>.json <output>.html --quality showcase
```

Never start preview by default. Read `references/delivery-contract.md` when using preview, repository evidence, export receipts, visual review, or post-commit opening.

## Optional viewer capabilities

Generated HTML already contains theme switching, pan/zoom, search, focus, relationship tracing, semantic views, presentation, and truthful exports. These are reader capabilities, not extra authoring work. `meta.animation: "trace"` is opt-in; `meta.views` is optional and should contain at most five curated chapters.

Read `references/viewer-runtime.md` only when the user explicitly asks for Share Cards, Route/Reach cards, motion, guided stories, deep links, presentation, search/focus, or another Viewer Runtime feature.

## Setup and fallback

No install is required inside the skill package. Verify with:

```bash
node bin/archify.mjs doctor
node bin/archify.mjs demo <output-directory>
```

When shell access is unavailable, hand-place architecture SVG into `assets/template.html`, use CSS semantic classes rather than inline colors, and follow the visual review contract in `references/delivery-contract.md`.

## Output

Return the checked HTML path, diagram type, validation summary, specification/artifact receipt, browser-evidence status, and truthful visual-review status. Do not claim success for a non-zero command or claim visual inspection you did not perform.

## THIRD_PARTY_NOTICES.md

# Third-party notices

Archify includes optional vector data for third-party brand marks. These marks
are provided only to identify technologies and services in user-authored
diagrams. Their inclusion does not imply sponsorship, endorsement, partnership,
or affiliation with Archify.

The Archify MIT license applies to Archify's own code and content. It does not
replace the copyright licenses, trademark policies, or brand guidelines that
apply to third-party marks. Users are responsible for confirming that their
particular use is permitted.

## Simple Icons

Most of the built-in vector paths and their metadata were generated from
[Simple Icons 16.28.0](https://github.com/simple-icons/simple-icons/tree/16.28.0).
Simple Icons makes its collection work available under
[CC0 1.0 Universal](https://github.com/simple-icons/simple-icons/blob/16.28.0/LICENSE.md).

As Simple Icons explains in its
[disclaimer](https://github.com/simple-icons/simple-icons/blob/16.28.0/DISCLAIMER.md),
CC0 for the collection does not mean that every underlying icon is CC0. License
and brand-guideline metadata may be incomplete or change over time. The absence
of an individual license entry is not a grant of permission.

Archify embeds the selected icons as vector-path data and may render them in a
user-selected color. The following individual licenses were recorded in the
pinned Simple Icons 16.28.0 metadata:

| Mark | Recorded source | Recorded license | Archify treatment |
|---|---|---|---|
| Angular | [Angular press kit](https://angular.dev/press-kit) | [`CC-BY-4.0`](https://creativecommons.org/licenses/by/4.0/) | Embedded as vector-path data; color may be changed by the authored diagram. |
| Apache Airflow | [Apache logos](https://apache.org/logos) | [`Apache-2.0`](https://www.apache.org/licenses/LICENSE-2.0) | Embedded as vector-path data; Apache trademarks remain subject to the [ASF trademark policy](https://www.apache.org/foundation/marks/). |
| Apache Kafka | [Apache logos](https://apache.org/logos) | [`Apache-2.0`](https://www.apache.org/licenses/LICENSE-2.0) | Embedded as vector-path data; Apache trademarks remain subject to the [ASF trademark policy](https://www.apache.org/foundation/marks/). |
| .NET | [.NET brand repository](https://github.com/dotnet/brand/blob/c7d0f51b8ec59531332d05fb27a5b758a7a3d689/logo/dotnet-logo.svg) | [`CC0-1.0`](https://creativecommons.org/publicdomain/zero/1.0/) | Embedded as vector-path data; color may be changed by the authored diagram. |
| JavaScript | [JS community logo](https://github.com/voodootikigod/logo.js/blob/1544bdeed6d618a6cfe4f0650d04ab8d9cfa76d9/js.svg) | [`MIT`](https://github.com/voodootikigod/logo.js/blob/1544bdeed6d618a6cfe4f0650d04ab8d9cfa76d9/LICENSE) | Embedded as vector-path data; color may be changed by the authored diagram. |
| Jenkins | [Jenkins artwork source](https://get.jenkins.io/art/) | [`CC-BY-SA-3.0`](https://creativecommons.org/licenses/by-sa/3.0/) | Embedded as vector-path data; color may be changed by the authored diagram. Jenkins retains its trademark rights. |
| Rust | [Rust project](https://www.rust-lang.org) | [`CC-BY-SA-4.0`](https://creativecommons.org/licenses/by-sa/4.0/) | Embedded as vector-path data; color may be changed by the authored diagram. See the [Rust media guide](https://www.rust-lang.org/policies/media-guide). |
| Vue.js | [Vue logo source](https://github.com/vuejs/art/blob/a1c78b74569b70a25300925b4eacfefcc143b8f6/logo.svg) | [`CC-BY-NC-SA-4.0`](https://creativecommons.org/licenses/by-nc-sa/4.0/) | Embedded as vector-path data; color may be changed by the authored diagram. The non-commercial and share-alike conditions remain applicable; see the [Vue artwork terms](https://github.com/vuejs/art/blob/a1c78b74569b70a25300925b4eacfefcc143b8f6/README.md). |

The source, guideline, and known license fields for every packaged mark are
preserved in `renderers/shared/generated-brand-marks.mjs`.

## OpenAI mark

The OpenAI vector path is recorded from the
[OpenAI brand guidelines](https://openai.com/brand/), not from Simple Icons.
Use remains subject to those current guidelines and any applicable trademark
rights. Its inclusion does not state or imply endorsement by OpenAI.

## JetBrains Mono

Delivered Archify viewer artifacts embed the JetBrains Mono variable font
subsets served by Google Fonts. For characters covered by these subsets, font
selection does not depend on a network request or a locally installed copy.
Uncovered characters (including CJK) still use the system fallback stack;
browser and operating-system rasterization can differ.
JetBrains Mono is maintained at
[github.com/JetBrains/JetBrainsMono](https://github.com/JetBrains/JetBrainsMono)
and is distributed under the SIL Open Font License 1.1. The complete license
text is preserved in `assets/JetBrainsMono-OFL.txt` in the packaged Skill and
in the font CSS carried by standalone HTML and SVG exports.

## No additional rights granted

Brand names, logos, and trademarks remain the property of their respective
owners. This notice records provenance and known terms; it does not grant rights
that Archify does not hold, and it does not state that every packaged mark has
been cleared for every commercial, promotional, or redistributive use.

## assets

```

```

## assets/JetBrainsMono-OFL.txt

```
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
```

## assets/template.html

```

```

## bin

```

```

## bin/archify.mjs

```js
#!/usr/bin/env node

import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');

const TYPES = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']);

function usage() {
  return `Usage:
  archify render <type> <input.json> [output.html] [--quality standard|showcase] [--repo-root path (architecture only)]
  archify compare architecture <base.json> <head.json> [output.html] [--receipt path] [--json] [--quality standard|showcase] [--repo-root path]
  archify deliver <type> <input.json> [output.html] [--json] [--open] [--quality standard|showcase] [--repo-root path (architecture only)]
  archify preview <type> <input.json> [output.html] [--no-open] [--quality standard|showcase] [--repo-root path (architecture only)]
  archify validate <type> <input.json> [--json] [--layout-json] [--quality standard|showcase] [--repo-root path (architecture only)]
  archify migrate workflow <old.json> <new.json> --to-schema 2 [--json]
  archify inspect <type> <input.json>
  archify check <output.html>
  archify visual-check <output.html> [--json]
  archify guide [scenario or question] [--json] [--lang en|zh]
  archify brands [name, alias, domain, or category] [--json]
  archify brands capture <url> [--json]
  archify examples
  archify doctor
  archify demo [output-directory]

Types:
  architecture, workflow, sequence, dataflow, lifecycle
`;
}

function fail(message, code = 2) {
  console.error(message);
  process.exit(code);
}

function rejectCliArgument(message, details = {}) {
  const error = new Error(message);
  error.archifyArgument = {
    code: details.code || 'cli/invalid-arguments',
    subject: details.subject || {},
    evidence: details.evidence || {},
    supportedFixes: details.supportedFixes || ['correct the command arguments and retry'],
  };
  throw error;
}

function rendererPath(type) {
  if (!TYPES.has(type)) {
    rejectCliArgument(`Unknown diagram type "${type}". Expected one of: ${[...TYPES].join(', ')}`, {
      code: 'cli/unknown-diagram-type',
      subject: { type },
      evidence: { supportedTypes: [...TYPES] },
      supportedFixes: [`use one of: ${[...TYPES].join(', ')}`],
    });
  }
  return path.join(skillRoot, 'renderers', type, `render-${type}.mjs`);
}

function runNode(args, options = {}) {
  return spawnSync(process.execPath, args, {
    cwd: options.cwd || process.cwd(),
    encoding: 'utf8',
    stdio: options.stdio || 'inherit',
    env: options.env ? { ...process.env, ...options.env } : process.env,
  });
}

function extractQualityArgs(args) {
  const rest = [];
  let quality;
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index];
    if (arg === '--quality') {
      quality = args[index + 1];
      if (!quality || quality.startsWith('--')) rejectCliArgument('--quality requires standard or showcase.', {
        code: 'cli/missing-option-value',
        subject: { option: '--quality' },
        supportedFixes: ['provide --quality standard or --quality showcase'],
      });
      index += 1;
      continue;
    }
    if (arg.startsWith('--quality=')) {
      quality = arg.slice('--quality='.length);
      if (!quality) rejectCliArgument('--quality requires standard or showcase.', {
        code: 'cli/missing-option-value',
        subject: { option: '--quality' },
        supportedFixes: ['provide --quality standard or --quality showcase'],
      });
      continue;
    }
    rest.push(arg);
  }
  if (quality !== undefined && !['standard', 'showcase'].includes(quality)) {
    rejectCliArgument(`Unknown quality profile "${quality}". Expected standard or showcase.`, {
      code: 'cli/invalid-option-value',
      subject: { option: '--quality' },
      evidence: { value: quality, supportedValues: ['standard', 'showcase'] },
      supportedFixes: ['use --quality standard or --quality showcase'],
    });
  }
  return { rest, quality };
}

function extractRepoRootArgs(args) {
  const rest = [];
  let repoRoot;
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index];
    if (arg === '--repo-root') {
      repoRoot = args[index + 1];
      if (!repoRoot || repoRoot.startsWith('--')) rejectCliArgument('--repo-root requires a repository path.', {
        code: 'cli/missing-option-value',
        subject: { option: '--repo-root' },
        supportedFixes: ['provide one repository path after --repo-root'],
      });
      index += 1;
      continue;
    }
    if (arg.startsWith('--repo-root=')) {
      repoRoot = arg.slice('--repo-root='.length);
      if (!repoRoot) rejectCliArgument('--repo-root requires a repository path.', {
        code: 'cli/missing-option-value',
        subject: { option: '--repo-root' },
        supportedFixes: ['provide one repository path after --repo-root'],
      });
      continue;
    }
    rest.push(arg);
  }
  return { rest, repoRoot: repoRoot ? path.resolve(repoRoot) : undefined };
}

function rendererEnv(quality, repoRoot, diagnosticJson = false) {
  return {
    ...(quality ? { ARCHIFY_QUALITY_PROFILE: quality } : {}),
    ...(repoRoot ? { ARCHIFY_REPO_ROOT: repoRoot } : {}),
    ...(diagnosticJson ? { ARCHIFY_DIAGNOSTIC_FORMAT: 'json' } : {}),
  };
}

function diagnostic({ code, message, subject = {}, evidence = {}, supportedFixes = [], severity = 'error' }) {
  return {
    code,
    severity,
    message,
    subject,
    evidence,
    supportedFixes,
  };
}

function inputDiagnostic(error, inputPath) {
  const isSyntax = error instanceof SyntaxError;
  return diagnostic({
    code: isSyntax ? 'input/json-parse' : 'input/read',
    message: isSyntax
      ? `Input JSON could not be parsed: ${error.message}`
      : `Input could not be read: ${error.message}`,
    subject: { input: inputPath },
    evidence: {
      ...(error?.code ? { systemCode: error.code } : {}),
      reason: error.message,
    },
    supportedFixes: [isSyntax
      ? 'repair the JSON syntax and run validation again'
      : 'provide one readable JSON input file'],
  });
}

function rendererFailure(result) {
  if (result.error) {
    return {
      error: 'Renderer process could not start.',
      diagnostics: [diagnostic({
        code: 'internal/renderer-process',
        message: 'Renderer process could not start.',
        evidence: { reason: result.error.message },
      })],
    };
  }
  try {
    const payload = JSON.parse((result.stderr || '').trim());
    if (payload?.ok === false && Array.isArray(payload.diagnostics) && payload.diagnostics.length) {
      return {
        error: payload.error || payload.diagnostics[0].message,
        diagnostics: payload.diagnostics,
      };
    }
  } catch {
    // The diagnostic boundary is intentionally fail-closed. Never copy a raw
    // Node stack into a machine receipt when a renderer exits unexpectedly.
  }
  return {
    error: 'Renderer failed before emitting a structured diagnostic.',
    diagnostics: [diagnostic({
      code: 'internal/unclassified',
      message: 'Renderer failed before emitting a structured diagnostic.',
      evidence: { exitCode: result.status ?? 1 },
    })],
  };
}

const COMPOSITION_CHECKS = new Set([
  'label_route_clearance',
  'relationship_crossings',
  'relationship_corridors',
  'container_border_runs',
  'route_rhythm',
]);

const CHECK_FIXES = {
  single_svg: ['remove additional SVG roots so the artifact contains exactly one diagram SVG'],
  finite_svg: ['replace non-finite coordinates before rendering again'],
  orthogonal_arrows: ['use renderer-supported orthogonal routing controls'],
  legend_clearance: ['move the route or enlarge the viewBox so relationships do not enter the legend'],
};

const COMPOSITION_FIXES = {
  'composition/proper-crossing': ['adjust route/via or channel coordinates so unrelated relationships use separate corridors'],
  'composition/ambiguous-corridor': ['adjust route/via or channel coordinates so unrelated relationships do not visually merge'],
  'composition/container-border-run': ['route across the frame perpendicularly through a clear opening'],
  'composition/label-route-clearance': ['adjust labelAt, labelDx, labelDy, labelSegment, message y, or the other relationship route'],
  'composition/desktop-readability': ['reduce the viewBox width, shorten node copy, widen affected nodes, or split the diagram so node context remains at least 6px at a 1440px desktop viewport'],
  'composition/micro-segment': ['move the route/channel/via point so every visible segment is at least 8px'],
  'composition/short-interior-segment': ['move the route/channel/via point so every interior turn has at least 16px'],
};

function checkerDiagnostics(checker) {
  const diagnostics = [];
  for (const issue of checker?.composition?.issues || []) {
    if (issue.severity !== 'error') continue;
    const { severity, code, relationship, ...evidence } = issue;
    diagnostics.push(diagnostic({
      code,
      severity,
      message: `Final artifact failed ${code}.`,
      subject: relationship ? { relationship } : { check: 'composition' },
      evidence,
      supportedFixes: COMPOSITION_FIXES[code] || [],
    }));
  }
  for (const check of checker?.checks || []) {
    if (check.ok || COMPOSITION_CHECKS.has(check.name)) continue;
    diagnostics.push(diagnostic({
      code: `artifact/${check.name.replaceAll('_', '-')}`,
      message: (check.details || []).find(Boolean) || `Final artifact failed ${check.name}.`,
      subject: { check: check.name },
      evidence: { details: check.details || [] },
      supportedFixes: CHECK_FIXES[check.name] || [],
    }));
  }
  return diagnostics.length ? diagnostics : [diagnostic({
    code: 'artifact/check-failed',
    message: 'Final artifact check failed without a classified diagnostic.',
    subject: { check: 'unknown' },
    evidence: {},
  })];
}

function formatDiagnostics(error, diagnostics = []) {
  if (!diagnostics.length) return error;
  return [
    error,
    ...diagnostics.map((entry) => {
      const fix = entry.supportedFixes?.length ? ` Fix: ${entry.supportedFixes.join('; ')}.` : '';
      return `[${entry.code}] ${entry.message}${fix}`;
    }),
  ].join('\n');
}

function assertEvidenceType(type, repoRoot) {
  if (repoRoot && type !== 'architecture') {
    rejectCliArgument('--repo-root is currently supported for architecture diagrams only.', {
      code: 'cli/unsupported-option',
      subject: { option: '--repo-root', type },
      supportedFixes: ['remove --repo-root or use an architecture diagram'],
    });
  }
}

function exitFrom(result) {
  if (result.error) fail(result.error.message, 1);
  process.exit(result.status ?? 1);
}

function reportCompareFailure({ json, stage, error, code = 'delta/internal', details = {}, status = 1 }) {
  const receipt = {
    schemaVersion: 1,
    ok: false,
    command: 'compare',
    type: 'architecture',
    stage,
    error,
    diagnostics: [{
      code,
      severity: 'error',
      message: error,
      subject: details.side ? { side: details.side, ...(details.path ? { path: details.path } : {}) } : {},
      evidence: Object.fromEntries(Object.entries(details).filter(([key]) => !['side', 'path', 'supportedFixes'].includes(key))),
      supportedFixes: details.supportedFixes || [],
    }],
  };
  if (json) console.log(JSON.stringify(receipt, null, 2));
  else console.error(formatDiagnostics(error, receipt.diagnostics));
  process.exitCode = status;
}

function extractCompareOptions(args) {
  const positional = [];
  let receipt;
  let json = false;
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index];
    if (arg === '--json') {
      json = true;
      continue;
    }
    if (arg === '--receipt') {
      receipt = args[index + 1];
      if (!receipt || receipt.startsWith('--')) fail('--receipt requires a JSON output path.');
      index += 1;
      continue;
    }
    if (arg.startsWith('--receipt=')) {
      receipt = arg.slice('--receipt='.length);
      if (!receipt) fail('--receipt requires a JSON output path.');
      continue;
    }
    if (arg.startsWith('--')) fail(`Unknown compare option "${arg}".`);
    positional.push(arg);
  }
  return { positional, receipt, json };
}

function compareReceiptPath(outputPath) {
  const extension = path.extname(outputPath);
  return extension ? `${outputPath.slice(0, -extension.length)}.receipt.json` : `${outputPath}.receipt.json`;
}

function compareCommitError(message, code, details = {}) {
  const error = new Error(message);
  error.compareStage = 'commit';
  error.compareCode = code;
  error.compareDetails = details;
  return error;
}

function commitComparePair({ htmlCandidate, receiptCandidate, outputPath, receiptPath, stagingDirectory }) {
  const targets = [
    { label: 'HTML artifact', target: outputPath, candidate: htmlCandidate, backup: path.join(stagingDirectory, '.previous-output') },
    { label: 'receipt', target: receiptPath, candidate: receiptCandidate, backup: path.join(stagingDirectory, '.previous-receipt') },
  ];

  // Preflight the whole pair before moving either trusted target. This avoids
  // replacing the HTML and only then discovering that its receipt destination
  // cannot be committed (for example, because it is a directory).
  for (const item of targets) {
    if (!fs.existsSync(item.target)) continue;
    const existing = fs.lstatSync(item.target);
    if (!existing.isFile()) {
      throw compareCommitError(
        `Could not commit Architecture Delta: existing ${item.label} target is not a regular file.`,
        'delta/commit-target',
        {
          target: path.basename(item.target),
          targetType: existing.isDirectory() ? 'directory' : 'non-file',
          supportedFixes: [`choose a regular-file path for the ${item.label}`],
        },
      );
    }
  }

  const backedUp = [];
  const committed = [];
  try {
    for (const item of targets) {
      if (!fs.existsSync(item.target)) continue;
      fs.renameSync(item.target, item.backup);
      backedUp.push(item);
    }
    for (const item of targets) {
      fs.renameSync(item.candidate, item.target);
      committed.push(item);
    }
  } catch (cause) {
    const rollbackErrors = [];
    for (const item of [...committed].reverse()) {
      try {
        fs.rmSync(item.target, { force: true });
      } catch (error) {
        rollbackErrors.push(`${item.label}: remove failed (${error.message})`);
      }
    }
    for (const item of [...backedUp].reverse()) {
      try {
        if (fs.existsSync(item.target)) fs.rmSync(item.target, { force: true });
        fs.renameSync(item.backup, item.target);
      } catch (error) {
        rollbackErrors.push(`${item.label}: restore failed (${error.message})`);
      }
    }
    throw compareCommitError(
      rollbackErrors.length
        ? 'Architecture Delta pair commit failed and its previous files could not be fully restored.'
        : 'Architecture Delta pair commit failed; the previous files were restored.',
      rollbackErrors.length ? 'delta/commit-rollback-failed' : 'delta/commit-failed',
      {
        reason: cause.message,
        ...(rollbackErrors.length ? { rollbackErrors } : {}),
        supportedFixes: ['check that both output paths are writable regular files, then retry'],
      },
    );
  }
}

function renderValidatedArchitecture(inputPath, outputPath, quality, repoRoot) {
  const render = runNode([rendererPath('architecture'), inputPath, outputPath], {
    stdio: 'pipe',
    env: rendererEnv(quality, repoRoot, true),
  });
  if (render.status !== 0) {
    const failure = rendererFailure(render);
    const error = new Error(failure.error);
    error.compareStage = 'input';
    error.compareStatus = render.status ?? 1;
    error.diagnostics = failure.diagnostics;
    throw error;
  }
  const check = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), outputPath], { stdio: 'pipe' });
  if (check.status !== 0) {
    const error = new Error('Validated snapshot failed final artifact checks.');
    error.compareStage = 'check';
    error.compareStatus = check.status ?? 1;
    try {
      error.checker = JSON.parse(check.stdout);
      error.diagnostics = checkerDiagnostics(error.checker);
    } catch {
      error.diagnostics = [];
    }
    throw error;
  }
  const artifact = fs.readFileSync(outputPath);
  return {
    artifact,
    html: artifact.toString('utf8'),
    checks: JSON.parse(check.stdout),
    sourceEvidence: sourceEvidenceFromArtifact(artifact),
  };
}

async function commandCompare(args) {
  const { resolveOutputPath } = await import('../renderers/shared/output-path.mjs');
  const qualityArgs = extractQualityArgs(args);
  const repoArgs = extractRepoRootArgs(qualityArgs.rest);
  const options = extractCompareOptions(repoArgs.rest);
  const [type, baseInput, headInput, requestedOutput] = options.positional;
  if (type !== 'architecture' || !baseInput || !headInput || options.positional.length > 4) fail(usage());
  let deltaRuntime;
  try {
    deltaRuntime = await import(pathToFileURL(path.join(skillRoot, 'delta/architecture-delta.mjs')).href);
  } catch (error) {
    reportCompareFailure({ json: options.json, stage: 'prepare', error: 'Architecture compare runtime is unavailable.', code: 'delta/runtime-missing', details: { reason: error.message, supportedFixes: ['install the complete Archify skill package'] } });
    return;
  }
  const {
    ArchitectureDeltaError,
    annotateArchitectureSideSvg,
    buildDeltaSvg,
    canonicalArchitecture,
    canonicalArchitectureJson,
    compareArchitecture,
    extractArchitectureSvg,
    extractArtifactCss,
    renderArchitectureDeltaHtml,
    validateArchitectureDeltaHtml,
  } = deltaRuntime;

  const basePath = path.resolve(baseInput);
  const headPath = path.resolve(headInput);
  const receiptTarget = options.receipt || compareReceiptPath(path.resolve(requestedOutput || 'architecture-delta.html'));
  let outputPath;
  try {
    ({ outputPath } = resolveOutputPath({
      requestedOutput,
      defaultOutput: 'architecture-delta.html',
      inputPaths: [basePath, headPath],
      otherOutputPaths: [path.resolve(receiptTarget)],
    }));
  } catch (error) {
    const outputDiagnostic = error.archifyDiagnostics?.[0];
    reportCompareFailure({
      json: options.json,
      stage: 'prepare',
      error: error.message,
      code: outputDiagnostic?.code || 'output/path-resolution',
      details: {
        ...(outputDiagnostic?.subject || {}),
        ...(outputDiagnostic?.evidence || {}),
        supportedFixes: outputDiagnostic?.supportedFixes || ['choose a safe output path and retry'],
      },
    });
    return;
  }
  let receiptPath;
  try {
    ({ outputPath: receiptPath } = resolveOutputPath({
      requestedOutput: options.receipt || compareReceiptPath(outputPath),
      defaultOutput: compareReceiptPath(outputPath),
      requiredExtension: '.json',
      inputPaths: [basePath, headPath],
      otherOutputPaths: [outputPath],
    }));
  } catch (error) {
    const outputDiagnostic = error.archifyDiagnostics?.[0];
    reportCompareFailure({
      json: options.json,
      stage: 'prepare',
      error: error.message,
      code: outputDiagnostic?.code || 'output/path-resolution',
      details: {
        ...(outputDiagnostic?.subject || {}),
        ...(outputDiagnostic?.evidence || {}),
        supportedFixes: outputDiagnostic?.supportedFixes || ['choose a safe receipt path and retry'],
      },
    });
    return;
  }
  let baseBuffer;
  let headBuffer;
  let base;
  let head;
  try {
    baseBuffer = fs.readFileSync(basePath);
    base = JSON.parse(baseBuffer.toString('utf8'));
  } catch (error) {
    reportCompareFailure({ json: options.json, stage: 'input', error: `Could not read base input: ${error.message}`, code: 'delta/base-input', details: { side: 'base', reason: error.message } });
    return;
  }
  try {
    headBuffer = fs.readFileSync(headPath);
    head = JSON.parse(headBuffer.toString('utf8'));
  } catch (error) {
    reportCompareFailure({ json: options.json, stage: 'input', error: `Could not read head input: ${error.message}`, code: 'delta/head-input', details: { side: 'head', reason: error.message } });
    return;
  }

  const outputDirectory = path.dirname(outputPath);
  if (path.dirname(receiptPath) !== outputDirectory) {
    reportCompareFailure({ json: options.json, stage: 'prepare', error: 'The compare receipt must be written beside the HTML artifact.', code: 'delta/receipt-directory', details: { supportedFixes: ['choose a --receipt path in the same directory as output.html'] } });
    return;
  }
  try {
    fs.mkdirSync(outputDirectory, { recursive: true });
  } catch (error) {
    reportCompareFailure({ json: options.json, stage: 'prepare', error: `Could not create compare output directory: ${error.message}`, code: 'delta/output-directory', details: { reason: error.message } });
    return;
  }

  let stagingDirectory;
  try {
    stagingDirectory = fs.mkdtempSync(path.join(outputDirectory, '.archify-compare-'));
  } catch (error) {
    reportCompareFailure({ json: options.json, stage: 'prepare', error: `Could not create compare candidate: ${error.message}`, code: 'delta/candidate-directory', details: { reason: error.message } });
    return;
  }

  const baseCandidate = path.join(stagingDirectory, 'base.html');
  const headCandidate = path.join(stagingDirectory, 'head.html');
  const rawBaseCandidate = path.join(stagingDirectory, 'base.raw.html');
  const rawHeadCandidate = path.join(stagingDirectory, 'head.raw.html');
  const canonicalBaseInput = path.join(stagingDirectory, 'base.architecture.json');
  const canonicalHeadInput = path.join(stagingDirectory, 'head.architecture.json');
  const htmlCandidate = path.join(stagingDirectory, path.basename(outputPath));
  const receiptCandidate = path.join(stagingDirectory, path.basename(receiptPath));

  try {
    let baseResult;
    let headResult;
    try {
      renderValidatedArchitecture(basePath, rawBaseCandidate, qualityArgs.quality, repoArgs.repoRoot);
    } catch (error) {
      const diagnosticEntry = error.diagnostics?.[0];
      reportCompareFailure({
        json: options.json,
        stage: error.compareStage || 'validate',
        error: `Base snapshot failed validation: ${error.message}`,
        code: diagnosticEntry?.code || 'delta/base-validation',
        details: { side: 'base', ...(diagnosticEntry?.subject?.path ? { path: diagnosticEntry.subject.path } : {}), ...(diagnosticEntry?.evidence || {}), supportedFixes: diagnosticEntry?.supportedFixes || [] },
        status: error.compareStatus || 1,
      });
      return;
    }
    try {
      renderValidatedArchitecture(headPath, rawHeadCandidate, qualityArgs.quality, repoArgs.repoRoot);
    } catch (error) {
      const diagnosticEntry = error.diagnostics?.[0];
      reportCompareFailure({
        json: options.json,
        stage: error.compareStage || 'validate',
        error: `Head snapshot failed validation: ${error.message}`,
        code: diagnosticEntry?.code || 'delta/head-validation',
        details: { side: 'head', ...(diagnosticEntry?.subject?.path ? { path: diagnosticEntry.subject.path } : {}), ...(diagnosticEntry?.evidence || {}), supportedFixes: diagnosticEntry?.supportedFixes || [] },
        status: error.compareStatus || 1,
      });
      return;
    }

    // Validation must see the exact authored inputs. Only after both sides
    // pass do we canonicalize their collection order for deterministic SVG
    // geometry and stable artifact bytes.
    fs.writeFileSync(canonicalBaseInput, JSON.stringify(canonicalArchitecture(base)));
    fs.writeFileSync(canonicalHeadInput, JSON.stringify(canonicalArchitecture(head)));
    baseResult = renderValidatedArchitecture(canonicalBaseInput, baseCandidate, qualityArgs.quality, repoArgs.repoRoot);
    headResult = renderValidatedArchitecture(canonicalHeadInput, headCandidate, qualityArgs.quality, repoArgs.repoRoot);

    const semanticHash = (diagram) => createHash('sha256').update(canonicalArchitectureJson(diagram)).digest('hex');
    let compareIr;
    try {
      compareIr = compareArchitecture(base, head, {
        baseRawSha256: createHash('sha256').update(baseBuffer).digest('hex'),
        headRawSha256: createHash('sha256').update(headBuffer).digest('hex'),
        baseSemanticSha256: semanticHash(base),
        headSemanticSha256: semanticHash(head),
        baseBytes: baseBuffer.byteLength,
        headBytes: headBuffer.byteLength,
        baseVerified: Boolean(baseResult.sourceEvidence),
        headVerified: Boolean(headResult.sourceEvidence),
      });
    } catch (error) {
      if (!(error instanceof ArchitectureDeltaError)) throw error;
      reportCompareFailure({ json: options.json, stage: 'compare', error: error.message, code: error.code, details: error.details });
      return;
    }

    const baseSourceSvg = extractArchitectureSvg(baseResult.html);
    const headSourceSvg = extractArchitectureSvg(headResult.html);
    const baseSvg = annotateArchitectureSideSvg(baseSourceSvg, compareIr, 'base');
    const headSvg = annotateArchitectureSideSvg(headSourceSvg, compareIr, 'head');
    const deltaSvg = buildDeltaSvg(baseSourceSvg, headSourceSvg, compareIr);
    // Raw input hashes and byte counts belong in the sidecar receipt, not the
    // artifact. Keeping them out makes formatting-only input rewrites produce
    // the exact same canonical review HTML and artifact hash.
    const artifactIr = {
      ...compareIr,
      base: Object.fromEntries(Object.entries(compareIr.base).filter(([key]) => !['rawSha256', 'bytes'].includes(key))),
      head: Object.fromEntries(Object.entries(compareIr.head).filter(([key]) => !['rawSha256', 'bytes'].includes(key))),
    };
    const html = renderArchitectureDeltaHtml({
      receipt: artifactIr,
      baseSvg,
      deltaSvg,
      headSvg,
      baseHtml: baseResult.html,
      headHtml: headResult.html,
      artifactCss: extractArtifactCss(headResult.html),
    });
    const deltaValidation = validateArchitectureDeltaHtml(html, artifactIr);
    fs.writeFileSync(htmlCandidate, html);
    const artifact = fs.readFileSync(htmlCandidate);
    const baseChecks = baseResult.checks.checks.filter((check) => check.ok).length;
    const headChecks = headResult.checks.checks.filter((check) => check.ok).length;
    const finalReceipt = {
      ...compareIr,
      artifact: { sha256: createHash('sha256').update(artifact).digest('hex'), bytes: artifact.byteLength },
      validation: {
        checksPassed: baseChecks + headChecks + deltaValidation.checksPassed,
        checkCount: baseResult.checks.checks.length + headResult.checks.checks.length + deltaValidation.checkCount,
        baseComposition: baseResult.checks.composition.status,
        headComposition: headResult.checks.composition.status,
      },
    };
    fs.writeFileSync(receiptCandidate, `${JSON.stringify(finalReceipt, null, 2)}\n`);

    try {
      const currentOutput = resolveOutputPath({
        requestedOutput,
        defaultOutput: 'architecture-delta.html',
        inputPaths: [basePath, headPath],
        otherOutputPaths: [receiptPath],
      }).outputPath;
      resolveOutputPath({
        requestedOutput: options.receipt || compareReceiptPath(currentOutput),
        defaultOutput: compareReceiptPath(currentOutput),
        requiredExtension: '.json',
        inputPaths: [basePath, headPath],
        otherOutputPaths: [currentOutput],
      });
    } catch (error) {
      const outputDiagnostic = error.archifyDiagnostics?.[0];
      reportCompareFailure({
        json: options.json,
        stage: 'commit',
        error: error.message,
        code: outputDiagnostic?.code || 'output/path-resolution',
        details: {
          ...(outputDiagnostic?.subject || {}),
          ...(outputDiagnostic?.evidence || {}),
          supportedFixes: outputDiagnostic?.supportedFixes || ['restore safe output paths and retry'],
        },
      });
      return;
    }

    commitComparePair({ htmlCandidate, receiptCandidate, outputPath, receiptPath, stagingDirectory });
    if (options.json) console.log(JSON.stringify(finalReceipt, null, 2));
    else {
      console.log(`compared architecture ${outputPath}`);
      console.log(`${finalReceipt.validation.checksPassed}/${finalReceipt.validation.checkCount} checks; completeness ${finalReceipt.completeness}; ${finalReceipt.proofLevel}; sha256 ${finalReceipt.artifact.sha256.slice(0, 12)}`);
      console.log(`receipt ${receiptPath}`);
    }
  } catch (error) {
    if (error instanceof ArchitectureDeltaError) {
      reportCompareFailure({ json: options.json, stage: 'artifact', error: error.message, code: error.code, details: error.details });
    } else if (error.compareStage === 'commit') {
      reportCompareFailure({
        json: options.json,
        stage: error.compareStage,
        error: error.message,
        code: error.compareCode,
        details: error.compareDetails,
      });
    } else {
      reportCompareFailure({ json: options.json, stage: 'internal', error: 'Architecture compare failed before commit.', code: 'delta/internal', details: { reason: error.message } });
    }
  } finally {
    try {
      fs.rmSync(stagingDirectory, { recursive: true, force: true });
    } catch (error) {
      console.error(`Warning: could not remove compare staging directory: ${error.message}`);
    }
  }
}

function commandRender(args) {
  const qualityArgs = extractQualityArgs(args);
  const repoArgs = extractRepoRootArgs(qualityArgs.rest);
  // render takes no options of its own once --quality and --repo-root are
  // stripped, so anything left starting with -- is a typo. Without this a
  // mistyped flag was taken as the output path: `render architecture spec.json
  // --json out.html` wrote a file literally named `--json` and never wrote
  // out.html, exiting 0. Every sibling subcommand already guards this.
  const unknown = repoArgs.rest.filter((arg) => arg.startsWith('--'));
  if (unknown.length) fail(`Unknown render option "${unknown[0]}".`);
  const [type, input, output] = repoArgs.rest;
  if (!type || !input || repoArgs.rest.length > 3) fail(usage());
  assertEvidenceType(type, repoArgs.repoRoot);
  const result = runNode([rendererPath(type), input, ...(output ? [output] : [])], {
    env: rendererEnv(qualityArgs.quality, repoArgs.repoRoot),
  });
  if (result.status !== 0) exitFrom(result);
}

function reportArtifactFailure({ command, json, stage, type, input, output, error, diagnostics = [], status = 1, checker }) {
  const receipt = {
    schemaVersion: 1,
    ok: false,
    command,
    stage,
    type,
    input,
    ...(output === undefined ? {} : { output }),
    error,
    diagnostics,
    ...(checker ? { checker } : {}),
  };
  if (json) console.log(JSON.stringify(receipt, null, 2));
  else console.error(formatDiagnostics(error, diagnostics));
  process.exitCode = status;
}

function reportDeliveryFailure(options) {
  reportArtifactFailure({ ...options, command: 'deliver' });
}

function reportValidateFailure(options) {
  reportArtifactFailure({ ...options, command: 'validate' });
}

function reportArtifactArgumentFailure(command, error) {
  const details = error.archifyArgument || {};
  reportArtifactFailure({
    command,
    json: true,
    stage: 'arguments',
    error: error.message,
    diagnostics: [diagnostic({
      code: details.code || 'cli/invalid-arguments',
      message: error.message,
      subject: { command, ...(details.subject || {}) },
      evidence: details.evidence || {},
      supportedFixes: details.supportedFixes || ['correct the command arguments and retry'],
    })],
    status: 2,
  });
}

function sourceEvidenceFromArtifact(artifact) {
  const html = artifact.toString('utf8');
  const match = html.match(/<script id="archify-source-evidence-data" type="application\/json">([\s\S]*?)<\/script>/);
  if (!match) return null;
  const evidence = JSON.parse(match[1]);
  if (evidence?.verified !== true || !evidence.repository?.url || !evidence.repository?.revision || !Number.isInteger(evidence.referenceCount)) {
    throw new Error('Rendered source evidence receipt is incomplete.');
  }
  return evidence;
}

function engineeringProfileFromArtifact(artifact) {
  const match = artifact.toString('utf8').match(/<svg[^>]*\sdata-engineering-profile="([^"]+)"/);
  return match ? match[1] : null;
}

async function commandDeliver(args) {
  const qualityArgs = extractQualityArgs(args);
  const repoArgs = extractRepoRootArgs(qualityArgs.rest);
  const json = repoArgs.rest.includes('--json');
  const open = repoArgs.rest.includes('--open');
  const knownOptions = new Set(['--json', '--open']);
  const unknown = repoArgs.rest.filter((arg) => arg.startsWith('--') && !knownOptions.has(arg));
  if (unknown.length) rejectCliArgument(`Unknown deliver option "${unknown[0]}".`, {
    code: 'cli/unknown-option',
    subject: { option: unknown[0] },
    supportedFixes: ['remove the unknown option and retry'],
  });
  const positional = repoArgs.rest.filter((arg) => !knownOptions.has(arg));
  const [type, input, requestedOutput] = positional;
  if (!type || !input || positional.length > 3) rejectCliArgument(usage(), {
    code: 'cli/usage',
    supportedFixes: ['use: archify deliver <type> <input.json> [output.html] [options]'],
  });
  assertEvidenceType(type, repoArgs.repoRoot);
  const renderer = rendererPath(type);
  const { resolveOutputPath } = await import('../renderers/shared/output-path.mjs');
  const inputPath = path.resolve(input);
  let specification;
  let diagram;
  try {
    specification = fs.readFileSync(inputPath);
    diagram = JSON.parse(specification.toString('utf8'));
  } catch (error) {
    const repair = inputDiagnostic(error, inputPath);
    reportDeliveryFailure({
      json,
      stage: 'input',
      type,
      input: inputPath,
      output: path.resolve(requestedOutput || `${type}.html`),
      error: `Could not read delivery input "${inputPath}": ${error.message}`,
      diagnostics: [repair],
    });
    return;
  }

  const authoredOutput = typeof diagram?.meta?.output === 'string' && diagram.meta.output
    ? diagram.meta.output
    : undefined;
  let outputPath;
  try {
    ({ outputPath } = resolveOutputPath({
      requestedOutput,
      authoredOutput,
      defaultOutput: `${type}.html`,
      inputPaths: [inputPath],
    }));
  } catch (error) {
    const attemptedOutput = path.resolve(requestedOutput || authoredOutput || `${type}.html`);
    reportDeliveryFailure({
      json,
      stage: 'prepare',
      type,
      input: inputPath,
      output: attemptedOutput,
      error: error.message,
      diagnostics: error.archifyDiagnostics || [diagnostic({
        code: 'output/path-resolution',
        message: error.message,
        subject: { output: attemptedOutput },
        evidence: { ...(error?.code ? { systemCode: error.code } : {}) },
        supportedFixes: ['choose a safe output path and retry'],
      })],
    });
    return;
  }
  const outputDirectory = path.dirname(outputPath);
  try {
    fs.mkdirSync(outputDirectory, { recursive: true });
  } catch (error) {
    const message = `Could not create delivery directory "${outputDirectory}": ${error.message}`;
    reportDeliveryFailure({
      json,
      stage: 'prepare',
      type,
      input: inputPath,
      output: outputPath,
      error: message,
      diagnostics: [diagnostic({
        code: 'delivery/prepare-directory',
        message,
        subject: { outputDirectory },
        evidence: { ...(error?.code ? { systemCode: error.code } : {}), reason: error.message },
        supportedFixes: ['choose a writable output directory'],
      })],
    });
    return;
  }

  // Keep the candidate beside the target so the final rename is one
  // same-filesystem commit. A render or artifact-check failure never touches
  // an existing trusted output.
  let stagingDirectory;
  try {
    stagingDirectory = fs.mkdtempSync(path.join(outputDirectory, '.archify-delivery-'));
  } catch (error) {
    const message = `Could not create a delivery candidate beside "${outputPath}": ${error.message}`;
    reportDeliveryFailure({
      json,
      stage: 'prepare',
      type,
      input: inputPath,
      output: outputPath,
      error: message,
      diagnostics: [diagnostic({
        code: 'delivery/prepare-candidate',
        message,
        subject: { output: outputPath },
        evidence: { ...(error?.code ? { systemCode: error.code } : {}), reason: error.message },
        supportedFixes: ['choose a writable output directory on the target filesystem'],
      })],
    });
    return;
  }
  const candidatePath = path.join(stagingDirectory, path.basename(outputPath));
  const specificationSnapshotPath = path.join(stagingDirectory, 'specification.snapshot.json');

  try {
    try {
      fs.writeFileSync(specificationSnapshotPath, specification, { flag: 'wx' });
    } catch (error) {
      const message = `Could not freeze the delivery specification: ${error.message}`;
      reportDeliveryFailure({
        json,
        stage: 'prepare',
        type,
        input: inputPath,
        output: outputPath,
        error: message,
        diagnostics: [diagnostic({
          code: 'delivery/freeze-specification',
          message,
          subject: { input: inputPath },
          evidence: { ...(error?.code ? { systemCode: error.code } : {}), reason: error.message },
          supportedFixes: ['choose a writable output directory on the target filesystem'],
        })],
      });
      return;
    }

    const render = runNode([renderer, specificationSnapshotPath, candidatePath], {
      stdio: 'pipe',
      env: rendererEnv(qualityArgs.quality, repoArgs.repoRoot, true),
    });
    if (render.status !== 0) {
      const failure = rendererFailure(render);
      reportDeliveryFailure({
        json,
        stage: 'render',
        type,
        input: inputPath,
        output: outputPath,
        error: failure.error,
        diagnostics: failure.diagnostics,
        status: render.status ?? 1,
      });
      return;
    }

    const check = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), candidatePath], {
      stdio: 'pipe',
    });
    if (check.status !== 0) {
      if (check.stderr) process.stderr.write(check.stderr);
      let checker;
      try {
        checker = JSON.parse(check.stdout);
        checker.file = outputPath;
      } catch {
        checker = { ok: false, file: outputPath, diagnostic: check.stdout.trim() };
      }
      reportDeliveryFailure({
        json,
        stage: 'check',
        type,
        input: inputPath,
        output: outputPath,
        error: 'Final artifact check failed; the previous artifact was preserved.',
        diagnostics: checkerDiagnostics(checker),
        status: check.status ?? 1,
        checker,
      });
      return;
    }

    let result;
    try {
      result = JSON.parse(check.stdout);
    } catch (error) {
      const message = `Could not parse the successful artifact-check receipt: ${error.message}`;
      reportDeliveryFailure({
        json,
        stage: 'receipt',
        type,
        input: inputPath,
        output: outputPath,
        error: message,
        diagnostics: [diagnostic({
          code: 'delivery/receipt-invalid',
          message,
          subject: { output: outputPath },
          evidence: { reason: error.message },
        })],
      });
      return;
    }
    let artifact;
    try {
      artifact = fs.readFileSync(candidatePath);
    } catch (error) {
      const message = `Could not read the verified delivery candidate: ${error.message}`;
      reportDeliveryFailure({
        json,
        stage: 'receipt',
        type,
        input: inputPath,
        output: outputPath,
        error: message,
        diagnostics: [diagnostic({
          code: 'delivery/candidate-unreadable',
          message,
          subject: { output: outputPath },
          evidence: { ...(error?.code ? { systemCode: error.code } : {}), reason: error.message },
        })],
      });
      return;
    }
    let sourceEvidence;
    try {
      sourceEvidence = sourceEvidenceFromArtifact(artifact);
    } catch (error) {
      const message = `Could not read the repository evidence receipt: ${error.message}`;
      reportDeliveryFailure({
        json,
        stage: 'receipt',
        type,
        input: inputPath,
        output: outputPath,
        error: message,
        diagnostics: [diagnostic({
          code: 'delivery/evidence-receipt-invalid',
          message,
          subject: { output: outputPath },
          evidence: { reason: error.message },
        })],
      });
      return;
    }
    const engineeringProfile = engineeringProfileFromArtifact(artifact);
    const receipt = {
      schemaVersion: 1,
      ok: true,
      command: 'deliver',
      type,
      input: inputPath,
      output: outputPath,
      specification: {
        sha256: createHash('sha256').update(specification).digest('hex'),
        bytes: specification.byteLength,
      },
      artifact: {
        sha256: createHash('sha256').update(artifact).digest('hex'),
        bytes: artifact.byteLength,
      },
      validation: {
        checksPassed: result.checks.filter((checkItem) => checkItem.ok).length,
        checkCount: result.checks.length,
        compositionProfile: result.composition.profile,
        compositionStatus: result.composition.status,
        ...(engineeringProfile ? { engineeringProfile } : {}),
        errors: result.composition.summary.errors,
        warnings: result.composition.summary.warnings,
      },
      ...(sourceEvidence ? {
        evidence: {
          verified: true,
          repository: sourceEvidence.repository.url,
          revision: sourceEvidence.repository.revision,
          references: sourceEvidence.referenceCount,
          ...(sourceEvidence.repository.linkMode ? { linkMode: sourceEvidence.repository.linkMode } : {}),
        },
      } : {}),
    };

    try {
      resolveOutputPath({
        requestedOutput,
        authoredOutput,
        defaultOutput: `${type}.html`,
        inputPaths: [inputPath],
      });
    } catch (error) {
      reportDeliveryFailure({
        json,
        stage: 'commit',
        type,
        input: inputPath,
        output: outputPath,
        error: error.message,
        diagnostics: error.archifyDiagnostics || [diagnostic({
          code: 'output/path-resolution',
          message: error.message,
          subject: { output: outputPath },
          evidence: { ...(error?.code ? { systemCode: error.code } : {}) },
          supportedFixes: ['restore a safe output path and retry'],
        })],
      });
      return;
    }

    try {
      fs.renameSync(candidatePath, outputPath);
    } catch (error) {
      const message = `Could not commit verified delivery "${outputPath}": ${error.message}`;
      reportDeliveryFailure({
        json,
        stage: 'commit',
        type,
        input: inputPath,
        output: outputPath,
        error: message,
        diagnostics: [diagnostic({
          code: 'delivery/commit',
          message,
          subject: { output: outputPath },
          evidence: { ...(error?.code ? { systemCode: error.code } : {}), reason: error.message },
          supportedFixes: ['choose a replaceable file target on the same writable filesystem'],
        })],
      });
      return;
    }

    if (open) {
      try {
        const { openArtifact } = await import('./open-artifact.mjs');
        receipt.open = openArtifact(outputPath);
      } catch {
        receipt.open = {
          requested: true,
          status: 'unsupported',
          target: outputPath,
          method: null,
        };
      }
      if (receipt.open.status !== 'opened') {
        console.error(`Could not open the verified artifact (${receipt.open.status}). Open it manually: ${outputPath}`);
      }
    }

    if (json) {
      console.log(JSON.stringify(receipt, null, 2));
    } else {
      console.log(`delivered ${type} ${outputPath}`);
      const engineering = receipt.validation.engineeringProfile
        ? `; engineering ${receipt.validation.engineeringProfile}: pass`
        : '';
      console.log(`${receipt.validation.checksPassed}/${receipt.validation.checkCount} artifact checks; composition ${receipt.validation.compositionProfile}: ${receipt.validation.compositionStatus}${engineering}; sha256 ${receipt.artifact.sha256.slice(0, 12)}`);
      if (receipt.open?.status === 'opened') console.log(`opened ${outputPath}`);
    }
  } finally {
    try {
      fs.rmSync(stagingDirectory, { recursive: true, force: true });
    } catch (error) {
      console.error(`Warning: could not remove delivery staging directory "${stagingDirectory}": ${error.message}`);
    }
  }
}

async function commandPreview(args) {
  const qualityArgs = extractQualityArgs(args);
  const repoArgs = extractRepoRootArgs(qualityArgs.rest);
  const noOpen = repoArgs.rest.includes('--no-open');
  const knownOptions = new Set(['--no-open']);
  const unknown = repoArgs.rest.filter((arg) => arg.startsWith('--') && !knownOptions.has(arg));
  if (unknown.length) fail(`Unknown preview option "${unknown[0]}".`);
  const positional = repoArgs.rest.filter((arg) => !knownOptions.has(arg));
  const [type, input, output] = positional;
  if (!type || !input || positional.length > 3) fail(usage());
  assertEvidenceType(type, repoArgs.repoRoot);
  rendererPath(type);

  let runPreview;
  try {
    ({ runPreview } = await import('./preview.mjs'));
  } catch (error) {
    fail(`Could not load live preview: ${error.message}`, 1);
  }
  try {
    await runPreview({
      type,
      input,
      output,
      quality: qualityArgs.quality,
      repoRoot: repoArgs.repoRoot,
      open: !noOpen,
    });
  } catch (error) {
    fail(`Could not start live preview: ${error.message}`, 1);
  }
}

function commandCheck(args) {
  const unknown = args.find((arg) => arg.startsWith('--'));
  if (unknown) fail(`Unknown check option "${unknown}".`);
  const [html] = args;
  if (!html || args.length !== 1) fail(usage());
  const result = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), html]);
  if (result.status !== 0) exitFrom(result);
}

async function commandVisualCheck(args) {
  const json = args.includes('--json');
  const knownOptions = new Set(['--json']);
  const unknown = args.filter((arg) => arg.startsWith('--') && !knownOptions.has(arg));
  if (unknown.length) fail(`Unknown visual-check option "${unknown[0]}".`, 1);
  const positional = args.filter((arg) => !knownOptions.has(arg));
  if (positional.length !== 1) fail(usage(), 1);

  let runVisualCheck;
  try {
    ({ runVisualCheck } = await import('./visual-check.mjs'));
  } catch (error) {
    fail(`Could not load visual-check: ${error.message}`, 1);
  }

  let result;
  try {
    result = await runVisualCheck({ artifactPath: positional[0] });
  } catch (error) {
    if (json) {
      console.log(JSON.stringify({
        schemaVersion: 1,
        ok: false,
        command: 'visual-check',
        evidenceKind: 'automated-browser',
        status: 'fail',
        visualReview: 'pending',
        artifact: { path: path.resolve(positional[0]) },
        error: error.message,
      }, null, 2));
    } else {
      console.error(`automated browser evidence failed: ${error.message}`);
      console.error('perceptual visual review pending');
    }
    process.exitCode = 1;
    return;
  }

  if (json) {
    console.log(JSON.stringify(result.receipt, null, 2));
  } else {
    console.log(`automated browser evidence ${result.receipt.status}: ${result.receipt.artifact.path}`);
    console.log(`visual-check containment ${result.receipt.containment.status}; captures ${result.receipt.captures.status}; perceptual visual review pending`);
    console.log(`receipt ${path.join(path.dirname(result.receipt.artifact.path), result.receipt.sidecars.receipt)}`);
    if (result.receipt.captures.contactSheet) {
      console.log(`contact sheet ${path.join(path.dirname(result.receipt.artifact.path), result.receipt.captures.contactSheet)}`);
    }
    if (result.receipt.error) console.error(result.receipt.error);
  }
  process.exitCode = result.exitCode;
}

function commandExamples(args) {
  const unknown = args.find((arg) => arg.startsWith('--'));
  if (unknown) fail(`Unknown examples option "${unknown}".`);
  if (args.length) fail(usage());
  const result = runNode([path.join(skillRoot, 'scripts/render-examples.mjs')], { cwd: skillRoot });
  if (result.status !== 0) exitFrom(result);
}

async function commandDoctor(args) {
  const unknown = args.find((arg) => arg.startsWith('--'));
  if (unknown) fail(`Unknown doctor option "${unknown}".`);
  if (args.length) fail(usage());
  const checks = [];
  const nodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10);
  checks.push({
    label: `Node.js v${process.versions.node} (requires >=18)`,
    ok: nodeMajor >= 18,
    missing: 0,
    failureLabel: 'unsupported',
  });

  const template = path.join(skillRoot, 'assets/template.html');
  checks.push({
    label: 'Core template',
    ok: fs.existsSync(template),
    missing: fs.existsSync(template) ? 0 : 1,
  });

  const examplesRenderer = path.join(skillRoot, 'scripts/render-examples.mjs');
  checks.push({
    label: 'Example renderer',
    ok: fs.existsSync(examplesRenderer),
    missing: fs.existsSync(examplesRenderer) ? 0 : 1,
  });

  const previewRuntime = path.join(skillRoot, 'bin/preview.mjs');
  checks.push({
    label: 'Live preview runtime',
    ok: fs.existsSync(previewRuntime),
    missing: fs.existsSync(previewRuntime) ? 0 : 1,
  });

  const visualCheckRuntime = path.join(skillRoot, 'bin/visual-check.mjs');
  checks.push({
    label: 'Visual-check runtime',
    ok: fs.existsSync(visualCheckRuntime),
    missing: fs.existsSync(visualCheckRuntime) ? 0 : 1,
  });

  const outputPathRuntime = path.join(skillRoot, 'renderers/shared/output-path.mjs');
  checks.push({
    label: 'Output path safety runtime',
    ok: fs.existsSync(outputPathRuntime),
    missing: fs.existsSync(outputPathRuntime) ? 0 : 1,
  });

  const scenarioGuide = path.join(skillRoot, 'recipes/scenarios.mjs');
  checks.push({
    label: 'Scenario recipe guide',
    ok: fs.existsSync(scenarioGuide),
    missing: fs.existsSync(scenarioGuide) ? 0 : 1,
  });

  const authoringReferences = [
    path.join(skillRoot, 'references', 'authoring-contract.md'),
    path.join(skillRoot, 'references', 'viewer-runtime.md'),
    path.join(skillRoot, 'references', 'delivery-contract.md'),
  ];
  const authoringReferencesMissing = authoringReferences.filter((file) => !fs.existsSync(file)).length;
  checks.push({
    label: 'Progressive authoring references',
    ok: authoringReferencesMissing === 0,
    missing: authoringReferencesMissing,
  });

  const compareRuntime = path.join(skillRoot, 'delta/architecture-delta.mjs');
  const compareFixtures = [
    path.join(skillRoot, 'examples/checkout-platform.base.architecture.json'),
    path.join(skillRoot, 'examples/checkout-platform.head.architecture.json'),
  ];
  const compareMissing = [compareRuntime, ...compareFixtures].filter((file) => !fs.existsSync(file)).length;
  checks.push({
    label: 'Architecture compare runtime and proof fixtures',
    ok: compareMissing === 0,
    missing: compareMissing,
  });

  const validators = path.join(skillRoot, 'renderers/shared/generated-validators.mjs');
  const validatorsExist = fs.existsSync(validators);
  let validatorsValid = false;
  if (validatorsExist) {
    try {
      const module = await import(`${pathToFileURL(validators).href}?doctor=${Date.now()}`);
      validatorsValid = [...TYPES].every((type) => typeof module[type] === 'function');
    } catch {
      validatorsValid = false;
    }
  }
  checks.push({
    label: 'Standalone schema validators',
    ok: validatorsValid,
    missing: validatorsExist ? 0 : 1,
    invalid: validatorsExist && !validatorsValid ? 1 : 0,
    failureLabel: validatorsExist ? 'invalid' : 'missing',
  });

  const examples = {
    architecture: 'web-app.architecture.json',
    workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json',
    dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };

  for (const type of TYPES) {
    const required = [
      path.join(skillRoot, 'renderers', type, `render-${type}.mjs`),
      path.join(skillRoot, 'schemas', `${type}.schema.json`),
      path.join(skillRoot, 'examples', examples[type]),
    ];
    const missing = required.filter((file) => !fs.existsSync(file)).length;
    checks.push({
      label: `${type} renderer, schema, and example`,
      ok: missing === 0,
      missing,
    });
  }

  console.log('Archify doctor\n');
  for (const check of checks) {
    console.log(`[${check.ok ? 'ok' : (check.failureLabel || 'missing')}] ${check.label}`);
  }

  const nodeFailed = checks[0].ok ? 0 : 1;
  const missingFiles = checks.reduce((count, check) => count + check.missing, 0);
  const invalidRuntime = checks.reduce((count, check) => count + (check.invalid || 0), 0);
  if (nodeFailed === 0 && missingFiles === 0 && invalidRuntime === 0) {
    console.log('\nArchify is ready.');
    return;
  }

  const problems = [];
  if (nodeFailed) problems.push('Node.js 18 or newer is required');
  if (missingFiles) problems.push(`${missingFiles} required file${missingFiles === 1 ? '' : 's'} missing`);
  if (invalidRuntime) problems.push(`${invalidRuntime} runtime check${invalidRuntime === 1 ? '' : 's'} failed`);
  console.error(`\nArchify is not ready: ${problems.join('; ')}.`);
  process.exitCode = 1;
}

async function commandGuide(args) {
  let lang;
  let json = false;
  const queryParts = [];

  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index];
    if (arg === '--json') {
      json = true;
    } else if (arg === '--lang') {
      const value = args[index + 1];
      if (value !== 'en' && value !== 'zh') fail('--lang must be "en" or "zh".');
      lang = value;
      index += 1;
    } else if (arg.startsWith('--lang=')) {
      const value = arg.slice('--lang='.length);
      if (value !== 'en' && value !== 'zh') fail('--lang must be "en" or "zh".');
      lang = value;
    } else if (arg.startsWith('--')) {
      fail(`Unknown guide option "${arg}".`);
    } else {
      queryParts.push(arg);
    }
  }

  const guidePath = path.join(skillRoot, 'recipes/scenarios.mjs');
  let guide;
  try {
    guide = await import(pathToFileURL(guidePath).href);
  } catch (error) {
    fail(`Could not load the scenario recipe guide: ${error.message}`, 1);
  }

  const query = queryParts.join(' ').trim();
  if (!query) {
    const selectedLang = lang || 'en';
    if (json) {
      console.log(JSON.stringify({
        ok: true,
        mode: 'list',
        lang: selectedLang,
        recipes: guide.listScenarioRecipes(selectedLang),
      }, null, 2));
    } else {
      console.log(guide.formatScenarioList(selectedLang));
    }
    return;
  }

  const result = guide.recommendScenario(query, lang ? { lang } : {});
  console.log(json ? JSON.stringify(result, null, 2) : guide.formatScenarioRecommendation(result));
}

async function commandBrands(args) {
  const json = args.includes('--json');
  const unknown = args.filter((arg) => arg.startsWith('--') && arg !== '--json');
  if (unknown.length) fail(`Unknown brands option "${unknown[0]}".`);
  const positional = args.filter((arg) => arg !== '--json');
  if (positional[0] === 'capture') {
    if (positional.length !== 2) fail('Usage: archify brands capture <url> [--json]');
    const { captureBrandReference } = await import('../renderers/shared/brand-marks.mjs');
    let capture;
    try {
      capture = await captureBrandReference(positional[1]);
    } catch (error) {
      fail(error.message);
    }
    const result = {
      schemaVersion: 1,
      ok: true,
      command: 'brands capture',
      brand: capture.brand,
      evidence: {
        status: capture.resolved.status,
        source: capture.resolved.sourceUrl,
        ...(capture.resolved.sha256 ? { sha256: capture.resolved.sha256 } : {}),
        ...(capture.resolved.contentType ? { contentType: capture.resolved.contentType } : {}),
      },
    };
    console.log(json ? JSON.stringify(result, null, 2) : JSON.stringify(result.brand));
    return;
  }
  const query = positional.join(' ').trim();
  const { listBrandMarks } = await import('../renderers/shared/brand-marks.mjs');
  const marks = listBrandMarks(query);
  if (json) {
    console.log(JSON.stringify({
      schemaVersion: 1,
      ok: true,
      command: 'brands',
      query,
      count: marks.length,
      marks,
      fallback: 'Run "archify brands capture <url> --json", then use the returned digest-pinned brand value.',
    }, null, 2));
    return;
  }
  if (!marks.length) {
    console.log(`No built-in brand matched "${query}". Run "archify brands capture <url> --json", then use the returned digest-pinned brand value.`);
    return;
  }
  const grouped = Map.groupBy
    ? Map.groupBy(marks, (mark) => mark.category)
    : marks.reduce((map, mark) => map.set(mark.category, [...(map.get(mark.category) || []), mark]), new Map());
  for (const [category, entries] of grouped) {
    console.log(`${category}: ${entries.map((mark) => mark.id).join(', ')}`);
  }
}

function commandDemo(args) {
  const unknown = args.find((arg) => arg.startsWith('--'));
  if (unknown) fail(`Unknown demo option "${unknown}".`);
  if (args.length > 1) fail(usage());

  const outputDirectory = path.resolve(args[0] || process.cwd());
  const output = path.join(outputDirectory, 'archify-demo.html');
  const input = path.join(skillRoot, 'examples/web-app.architecture.json');

  try {
    fs.mkdirSync(outputDirectory, { recursive: true });
  } catch (error) {
    fail(`Could not create demo directory "${outputDirectory}": ${error.message}`, 1);
  }

  const result = runNode([rendererPath('architecture'), input, output]);
  if (result.status !== 0) exitFrom(result);

  console.log(`\nDemo ready: ${output}`);
  console.log('Next: open the HTML in your browser, then render your own diagram:');
  console.log('  archify render architecture <input.json> <output.html>');
}

function migrationPathDiagnostics(error, sourcePath, destinationPath) {
  if (Array.isArray(error?.archifyDiagnostics) && error.archifyDiagnostics.length) {
    return error.archifyDiagnostics.map((entry) => ({
      ...entry,
      subject: { ...(entry.subject || {}) },
      evidence: { ...(entry.evidence || {}) },
      supportedFixes: [...(entry.supportedFixes || [])],
    }));
  }
  return [diagnostic({
    code: 'migration/path-preflight',
    message: 'Could not verify that the workflow migration paths are distinct.',
    subject: { source: sourcePath, destination: destinationPath },
    evidence: {
      ...(error?.code ? { systemCode: error.code } : {}),
      reason: error?.message || String(error),
    },
    supportedFixes: ['remove unsafe path aliases or choose a different destination path'],
  })];
}

function migrationReport({
  ok,
  sourcePath,
  destinationPath,
  sourceBytes,
  destinationBytes,
  fromSchemaVersion,
  preExistingDiagnostics = [],
  migrationDiagnostics = [],
  newSchemaDiagnostics = [],
  changedCoordinates = [],
  oldRequiredViewBox = null,
  newRequiredViewBox = null,
}) {
  const report = {
    ok,
    command: 'migrate',
    type: 'workflow',
    source: {
      path: sourcePath,
      ...(sourceBytes ? {
        sha256: createHash('sha256').update(sourceBytes).digest('hex'),
        bytes: sourceBytes.length,
      } : {}),
    },
    destination: {
      path: destinationPath,
      ...(destinationBytes ? {
        sha256: createHash('sha256').update(destinationBytes).digest('hex'),
        bytes: destinationBytes.length,
      } : {}),
    },
    fromSchemaVersion: fromSchemaVersion ?? null,
    toSchemaVersion: 2,
    preExistingDiagnostics,
    migrationDiagnostics,
    newSchemaDiagnostics,
    changedCoordinates,
    oldRequiredViewBox,
    newRequiredViewBox,
  };
  if (!ok) {
    report.diagnostics = [
      ...migrationDiagnostics,
      ...newSchemaDiagnostics,
      ...preExistingDiagnostics,
    ];
    if (!report.diagnostics.length) {
      report.diagnostics.push(diagnostic({
        code: 'migration/internal',
        message: 'Workflow migration failed without a classified diagnostic.',
      }));
    }
    report.error = report.diagnostics[0].message;
  }
  return report;
}

function extractMigrationOptions(args) {
  const positional = [];
  let json = false;
  let toSchema;
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index];
    if (arg === '--json') {
      json = true;
      continue;
    }
    if (arg === '--to-schema') {
      toSchema = args[index + 1];
      if (!toSchema || toSchema.startsWith('--')) fail('--to-schema requires a schema version.');
      index += 1;
      continue;
    }
    if (arg.startsWith('--to-schema=')) {
      toSchema = arg.slice('--to-schema='.length);
      if (!toSchema) fail('--to-schema requires a schema version.');
      continue;
    }
    if (arg.startsWith('--')) fail(`Unknown migrate option "${arg}".`);
    positional.push(arg);
  }
  return { positional, json, toSchema };
}

async function commandMigrate(args) {
  const options = extractMigrationOptions(args);
  const [type, sourceArgument, destinationArgument] = options.positional;
  if (
    type !== 'workflow'
    || !sourceArgument
    || !destinationArgument
    || options.positional.length !== 3
    || options.toSchema !== '2'
  ) {
    fail('Usage: archify migrate workflow <old.json> <new.json> --to-schema 2 [--json]');
  }

  const sourcePath = path.resolve(sourceArgument);
  const destinationPath = path.resolve(destinationArgument);
  let sourceBytes;
  let sourceDocument;
  const reportMigrationFailure = ({ status = 1, ...details }) => {
    const report = migrationReport({
      ...details,
      ok: false,
      sourcePath,
      destinationPath,
      sourceBytes,
      fromSchemaVersion: sourceDocument?.schema_version,
    });
    if (options.json) console.log(JSON.stringify(report, null, 2));
    else console.error(formatDiagnostics(report.error, report.diagnostics));
    process.exitCode = status;
  };
  try {
    sourceBytes = fs.readFileSync(sourcePath);
    sourceDocument = JSON.parse(sourceBytes.toString('utf8'));
  } catch (error) {
    reportMigrationFailure({
      preExistingDiagnostics: [inputDiagnostic(error, sourcePath)],
    });
    return;
  }
  // Unlike render/validate, migrate has no --quality override. Pin every stage
  // to the document's durable policy and scrub any ambient profile from the
  // staged renderer by passing this value explicitly.
  const activeQualityProfile = sourceDocument?.meta?.quality_profile || 'standard';

  const { pathsAlias } = await import('../renderers/shared/output-path.mjs');
  let sourceDestinationAlias;
  try {
    sourceDestinationAlias = pathsAlias(sourcePath, destinationPath);
  } catch (error) {
    reportMigrationFailure({
      migrationDiagnostics: migrationPathDiagnostics(error, sourcePath, destinationPath),
    });
    return;
  }
  if (sourceDestinationAlias) {
    reportMigrationFailure({
      migrationDiagnostics: [diagnostic({
        code: 'migration/source-destination',
        message: 'Workflow migration source and destination must be different files.',
        subject: { source: sourcePath, destination: destinationPath },
        supportedFixes: ['choose a different destination path and keep the source unchanged'],
      })],
    });
    return;
  }

  const { migrateWorkflowDocument, serializeMigratedWorkflow } = await import('../migrations/workflow-v2.mjs');
  let migration;
  try {
    migration = migrateWorkflowDocument(sourceDocument);
  } catch (error) {
    migration = {
      ok: false,
      migrationDiagnostics: [diagnostic({
        code: 'migration/internal',
        message: 'Workflow migration failed unexpectedly.',
        evidence: { reason: error.message },
        supportedFixes: ['report the source workflow and this diagnostic to the Archify maintainers'],
      })],
    };
  }

  if (!migration.ok) {
    reportMigrationFailure(migration);
    return;
  }

  if (fs.existsSync(destinationPath) && !fs.lstatSync(destinationPath).isFile()) {
    reportMigrationFailure({
      ...migration,
      migrationDiagnostics: [...migration.migrationDiagnostics, diagnostic({
        code: 'migration/destination-type',
        message: 'Workflow migration destination must be a regular file path.',
        subject: { destination: destinationPath },
        supportedFixes: ['choose a destination path that is absent or names a regular file'],
      })],
    });
    return;
  }

  const destinationDirectory = path.dirname(destinationPath);
  let stagingDirectory;
  try {
    fs.mkdirSync(destinationDirectory, { recursive: true });
    stagingDirectory = fs.mkdtempSync(path.join(destinationDirectory, '.archify-migration-'));
  } catch (error) {
    reportMigrationFailure({
      ...migration,
      migrationDiagnostics: [...migration.migrationDiagnostics, diagnostic({
        code: 'migration/prepare-destination',
        message: 'Could not prepare the workflow migration destination.',
        subject: { destination: destinationPath },
        evidence: { ...(error?.code ? { systemCode: error.code } : {}), reason: error.message },
        supportedFixes: ['choose a writable destination directory'],
      })],
    });
    return;
  }

  const candidatePath = path.join(stagingDirectory, 'candidate.workflow.json');
  const artifactPath = path.join(stagingDirectory, 'migration-check.html');
  const destinationBytes = Buffer.from(serializeMigratedWorkflow(migration.document));
  try {
    fs.writeFileSync(candidatePath, destinationBytes, { flag: 'wx' });
    const render = runNode([rendererPath('workflow'), candidatePath, artifactPath], {
      stdio: 'pipe',
      env: rendererEnv(activeQualityProfile, undefined, true),
    });
    if (render.status !== 0) {
      const failure = rendererFailure(render);
      reportMigrationFailure({
        ...migration,
        newSchemaDiagnostics: [...migration.newSchemaDiagnostics, ...failure.diagnostics],
        status: render.status ?? 1,
      });
      return;
    }

    const check = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), artifactPath], {
      stdio: 'pipe',
    });
    if (check.status !== 0) {
      let checker;
      try {
        checker = JSON.parse(check.stdout);
      } catch {
        checker = null;
      }
      reportMigrationFailure({
        ...migration,
        newSchemaDiagnostics: [
          ...migration.newSchemaDiagnostics,
          ...checkerDiagnostics(checker),
        ],
        status: check.status ?? 1,
      });
      return;
    }

    if (pathsAlias(sourcePath, destinationPath)) {
      reportMigrationFailure({
        ...migration,
        migrationDiagnostics: [...migration.migrationDiagnostics, diagnostic({
          code: 'migration/source-destination',
          message: 'Workflow migration source and destination resolved to the same file before commit.',
          subject: { source: sourcePath, destination: destinationPath },
          supportedFixes: ['choose a different destination path and retry'],
        })],
      });
      return;
    }
    const currentSourceBytes = fs.readFileSync(sourcePath);
    if (!currentSourceBytes.equals(sourceBytes)) {
      reportMigrationFailure({
        ...migration,
        migrationDiagnostics: [...migration.migrationDiagnostics, diagnostic({
          code: 'migration/source-changed',
          message: 'Workflow migration source changed while the destination was being verified.',
          subject: { source: sourcePath },
          supportedFixes: ['retry the migration from a stable workflow source file'],
        })],
      });
      return;
    }

    fs.renameSync(candidatePath, destinationPath);
    const report = migrationReport({
      ...migration,
      sourcePath,
      destinationPath,
      sourceBytes,
      destinationBytes,
      fromSchemaVersion: sourceDocument.schema_version,
    });
    if (options.json) console.log(JSON.stringify(report, null, 2));
    else if (sourceDocument.schema_version === 1) {
      console.log(`migrated workflow schema v1→v2: ${sourcePath} → ${destinationPath}`);
    } else {
      console.log(`verified workflow schema v2 migration: ${sourcePath} → ${destinationPath}`);
    }
  } catch (error) {
    const migrationDiagnostics = Array.isArray(error?.archifyDiagnostics)
      ? migrationPathDiagnostics(error, sourcePath, destinationPath)
      : [diagnostic({
        code: 'migration/commit',
        message: 'Could not commit the verified workflow migration.',
        subject: { destination: destinationPath },
        evidence: { ...(error?.code ? { systemCode: error.code } : {}), reason: error.message },
        supportedFixes: ['choose a writable regular-file destination and retry'],
      })];
    reportMigrationFailure({
      ...migration,
      migrationDiagnostics: [...migration.migrationDiagnostics, ...migrationDiagnostics],
    });
  } finally {
    try {
      fs.rmSync(stagingDirectory, { recursive: true, force: true });
    } catch (error) {
      console.error(`Warning: could not remove workflow migration staging directory "${stagingDirectory}": ${error.message}`);
    }
  }
}

function commandValidate(args) {
  const qualityArgs = extractQualityArgs(args);
  const repoArgs = extractRepoRootArgs(qualityArgs.rest);
  args = repoArgs.rest;
  const quality = qualityArgs.quality;
  const repoRoot = repoArgs.repoRoot;
  const knownOptions = new Set(['--json', '--layout-json']);
  const unknown = args.filter((arg) => arg.startsWith('--') && !knownOptions.has(arg));
  if (unknown.length) rejectCliArgument(`Unknown validate option "${unknown[0]}".`, {
    code: 'cli/unknown-option',
    subject: { option: unknown[0] },
    supportedFixes: ['remove the unknown option and retry'],
  });
  const json = args.includes('--json');
  const layoutJson = args.includes('--layout-json');
  const rest = args.filter((arg) => !knownOptions.has(arg));
  const [type, input] = rest;
  if (!type || !input || rest.length !== 2) rejectCliArgument(usage(), {
    code: 'cli/usage',
    supportedFixes: ['use: archify validate <type> <input.json> [options]'],
  });
  assertEvidenceType(type, repoRoot);
  const renderer = rendererPath(type);

  if (layoutJson && !['architecture', 'workflow'].includes(type)) {
    rejectCliArgument('--layout-json is currently supported for architecture and workflow diagrams only.', {
      code: 'cli/unsupported-option',
      subject: { option: '--layout-json', type },
      supportedFixes: ['remove --layout-json or use an architecture or workflow diagram'],
    });
  }

  if (layoutJson) {
    // Layout mode emits JSON without writing HTML; keep its unused target typed.
    const layoutOutput = path.join(os.tmpdir(), `archify-layout-${process.pid}-${type}.html`);
    const result = runNode([renderer, input, layoutOutput, '--layout-json'], {
      stdio: 'pipe',
      env: rendererEnv(quality, repoRoot, true),
    });
    if (result.status !== 0) {
      try {
        const receipt = JSON.parse(result.stdout);
        if (receipt?.contract && Array.isArray(receipt.diagnostics)) {
          process.stdout.write(`${JSON.stringify(receipt, null, 2)}\n`);
          process.exitCode = result.status ?? 1;
          return;
        }
      } catch {
        // Fall through to the renderer failure contract when no compiler
        // receipt was produced (for example, input JSON could not be read).
      }
      const failure = rendererFailure(result);
      reportValidateFailure({
        json,
        stage: failure.diagnostics.some((entry) => entry.code.startsWith('input/')) ? 'input' : 'render',
        type,
        input: path.resolve(input),
        error: failure.error,
        diagnostics: failure.diagnostics,
        status: result.status ?? 1,
      });
      return;
    }
    process.stdout.write(result.stdout);
    return;
  }

  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-validate-'));
  const out = path.join(tmp, `${type}.html`);
  let exitCode = 0;

  try {
    const render = runNode([renderer, input, out], {
      stdio: 'pipe',
      env: rendererEnv(quality, repoRoot, true),
    });
    if (render.status !== 0) {
      const failure = rendererFailure(render);
      reportValidateFailure({
        json,
        stage: failure.diagnostics.some((entry) => entry.code.startsWith('input/')) ? 'input' : 'render',
        type,
        input: path.resolve(input),
        error: failure.error,
        diagnostics: failure.diagnostics,
        status: render.status ?? 1,
      });
      exitCode = render.status ?? 1;
    } else {
      const check = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), out], { stdio: 'pipe' });
      if (check.status !== 0) {
        let checker;
        try {
          checker = JSON.parse(check.stdout);
          checker.file = path.resolve(input);
        } catch {
          checker = { ok: false, diagnostic: 'Artifact checker failed without a parseable receipt.' };
        }
        reportValidateFailure({
          json,
          stage: 'check',
          type,
          input: path.resolve(input),
          error: 'Final artifact check failed.',
          diagnostics: checkerDiagnostics(checker),
          checker,
          status: check.status ?? 1,
        });
        exitCode = check.status ?? 1;
      } else {
        const result = JSON.parse(check.stdout);
        const engineeringProfile = engineeringProfileFromArtifact(fs.readFileSync(out));
        if (json) {
          console.log(JSON.stringify({
            schemaVersion: 1,
            ok: true,
            command: 'validate',
            type,
            input: path.resolve(input),
            checks: result.checks,
            composition: result.composition,
            ...(engineeringProfile ? { engineeringProfile } : {}),
          }, null, 2));
        } else {
          const engineering = engineeringProfile
            ? `; engineering ${engineeringProfile}: pass`
            : '';
          console.log(`ok ${type} ${path.resolve(input)} (${result.checks.length} artifact checks; composition ${result.composition.profile}: ${result.composition.summary.errors} errors, ${result.composition.summary.warnings} warnings${engineering})`);
        }
      }
    }
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }

  if (exitCode !== 0) process.exitCode = exitCode;
}

const [command, ...args] = process.argv.slice(2);

try {
  switch (command) {
    case undefined:
    case '-h':
    case '--help':
    case 'help':
      console.log(usage());
      break;
    case 'render':
      commandRender(args);
      break;
    case 'compare':
      await commandCompare(args);
      break;
    case 'deliver':
      await commandDeliver(args);
      break;
    case 'preview':
      await commandPreview(args);
      break;
    case 'validate':
      commandValidate(args);
      break;
    case 'migrate':
      await commandMigrate(args);
      break;
    case 'inspect':
      if (args[0] !== 'architecture') {
        fail('inspect is currently supported for architecture diagrams only.');
      }
      commandValidate([...args, '--layout-json']);
      break;
    case 'check':
      commandCheck(args);
      break;
    case 'visual-check':
      await commandVisualCheck(args);
      break;
    case 'guide':
      await commandGuide(args);
      break;
    case 'brands':
      await commandBrands(args);
      break;
    case 'examples':
      commandExamples(args);
      break;
    case 'doctor':
      await commandDoctor(args);
      break;
    case 'demo':
      commandDemo(args);
      break;
    default:
      fail(`Unknown command "${command}".\n\n${usage()}`);
  }
} catch (error) {
  if (!error.archifyArgument) throw error;
  if (['validate', 'deliver'].includes(command) && args.includes('--json')) {
    reportArtifactArgumentFailure(command, error);
  } else {
    fail(error.message);
  }
}
```

## bin/open-artifact.mjs

```js
import { spawnSync } from 'node:child_process';
import path from 'node:path';

const OPENERS = {
  darwin: {
    command: 'open',
    method: 'open',
    args: (target) => [target],
  },
  linux: {
    command: 'xdg-open',
    method: 'xdg-open',
    args: (target) => [target],
  },
  win32: {
    command: 'powershell.exe',
    method: 'powershell',
    // Keep the command constant and pass the target through PowerShell's
    // argument array. Paths are never interpolated into executable source.
    args: (target) => [
      '-NoProfile',
      '-NonInteractive',
      '-Command',
      'Start-Process -FilePath $args[0]',
      target,
    ],
  },
};

function launchTarget(target, options = {}) {
  const platform = options.platform || process.platform;
  const opener = OPENERS[platform];
  if (!opener) {
    return {
      requested: true,
      status: 'unsupported',
      target,
      method: null,
    };
  }

  const spawn = options.spawn || spawnSync;
  let result;
  try {
    result = spawn(opener.command, opener.args(target), {
      encoding: 'utf8',
      shell: false,
      stdio: 'ignore',
      timeout: options.timeoutMs || 5000,
      windowsHide: true,
    });
  } catch {
    result = { error: new Error('opener threw') };
  }

  let status = 'opened';
  if (result?.error?.code === 'ENOENT') status = 'unsupported';
  else if (result?.error || result?.signal || result?.status !== 0) status = 'failed';

  return {
    requested: true,
    status,
    target,
    method: opener.method,
  };
}

export function openArtifact(target, options = {}) {
  return launchTarget(path.resolve(target), options);
}

export function openLoopbackUrl(target, options = {}) {
  let url;
  try {
    url = new URL(target);
  } catch {
    throw new TypeError('Preview URL must be a valid loopback HTTP URL.');
  }
  if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1' || !url.port) {
    throw new TypeError('Preview URL must be a loopback URL using http://127.0.0.1:<port>.');
  }
  if (url.username || url.password || url.pathname !== '/' || url.search || url.hash) {
    throw new TypeError('Preview URL must target the loopback preview root.');
  }
  return launchTarget(url.href, options);
}
```

## bin/preview.mjs

```js
import { spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import http from 'node:http';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { openLoopbackUrl } from './open-artifact.mjs';
import { resolveOutputPath } from '../renderers/shared/output-path.mjs';

const here = path.dirname(fileURLToPath(import.meta.url));
const cliPath = path.join(here, 'archify.mjs');
const loopbackHost = '127.0.0.1';
const defaultDebounceMs = 400;
const defaultPollMs = 800;
const defaultStopGraceMs = 3000;
const defaultStopKillMs = 750;
const diagramTypes = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']);

function sha256(value) {
  return createHash('sha256').update(value).digest('hex');
}

function sourceDigest(inputPath) {
  try {
    const bytes = fs.readFileSync(inputPath);
    return { hash: sha256(bytes), bytes, missing: false };
  } catch (error) {
    return { hash: `unreadable:${error.code || 'unknown'}`, bytes: null, missing: true };
  }
}

function initialAuthoredOutput(inputPath) {
  try {
    const source = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
    if (typeof source?.meta?.output === 'string' && source.meta.output) {
      return source.meta.output;
    }
  } catch {
    // An invalid initial source still gets a status shell. Its output target is
    // fixed to the same fallback that `deliver` would use after repair.
  }
  return undefined;
}

function previewPage() {
  return `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Archify Live Preview</title>
  <style>
    :root { color-scheme: light dark; font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
    * { box-sizing: border-box; }
    html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: #0b111b; }
    body { display: grid; grid-template-rows: auto minmax(0, 1fr); color: #e8edf5; }
    header { position: relative; z-index: 2; display: flex; align-items: center; gap: 12px; min-height: 44px; padding: 7px 12px; border-bottom: 1px solid #253248; background: rgba(11, 17, 27, .96); box-shadow: 0 8px 22px rgba(0,0,0,.18); }
    .brand { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: #9cadc6; }
    #status { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; min-height: 30px; padding: 5px 10px; border: 1px solid #33435d; border-radius: 999px; background: #111b2a; font-size: 12px; white-space: nowrap; }
    #status::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: #6f819d; }
    body[data-state="checking"] #status::before { background: #f3b44b; box-shadow: 0 0 0 4px rgba(243,180,75,.12); }
    body[data-state="verified"] #status::before { background: #45d6a8; box-shadow: 0 0 0 4px rgba(69,214,168,.12); }
    body[data-state="needs-fix"] #status::before { background: #ff6f78; box-shadow: 0 0 0 4px rgba(255,111,120,.12); }
    details { max-width: min(62vw, 760px); }
    summary { cursor: pointer; color: #ffbdc2; font-size: 12px; }
    .diagnostic { position: absolute; top: 38px; right: 12px; width: min(760px, calc(100vw - 24px)); max-height: min(44vh, 360px); overflow: auto; padding: 14px; border: 1px solid #6a3440; border-radius: 10px; background: #17131b; box-shadow: 0 14px 48px rgba(0,0,0,.42); }
    pre { margin: 0 0 10px; white-space: pre-wrap; overflow-wrap: anywhere; font: 11px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; color: #f2dfe2; }
    button { min-height: 32px; padding: 5px 10px; border: 1px solid #4a5d79; border-radius: 7px; background: #1a273a; color: #eef4ff; cursor: pointer; }
    main { position: relative; min-height: 0; }
    iframe { display: none; width: 100%; height: 100%; border: 0; background: #fff; }
    body[data-has-artifact="true"] iframe { display: block; }
    #empty { position: absolute; inset: 0; display: grid; place-items: center; padding: 32px; color: #91a2bc; text-align: center; background: radial-gradient(circle at 50% 38%, #15233a 0, #0b111b 55%); }
    body[data-has-artifact="true"] #empty { display: none; }
    @media (prefers-reduced-motion: reduce) { * { scroll-behavior: auto !important; } }
  </style>
</head>
<body data-state="checking" data-has-artifact="false">
  <header>
    <span class="brand">Archify Preview</span>
    <details id="failure" hidden>
      <summary role="button" aria-controls="diagnostic-panel">View diagnostic</summary>
      <div class="diagnostic" id="diagnostic-panel"><pre id="diagnostic"></pre><button id="copy" type="button">Copy diagnostic</button></div>
    </details>
    <span id="status" role="status" aria-live="polite">Checking · generation 1</span>
  </header>
  <main>
    <div id="empty">Waiting for the first verified diagram. Invalid input will stay here with an exact diagnostic.</div>
    <iframe id="artifact" title="Verified Archify diagram"></iframe>
  </main>
  <script>
    (function () {
      'use strict';
      var body = document.body;
      var status = document.getElementById('status');
      var failure = document.getElementById('failure');
      var diagnostic = document.getElementById('diagnostic');
      var artifact = document.getElementById('artifact');
      var lastRevision = 0;

      function render(state) {
        body.dataset.state = state.status;
        if (state.status === 'verified') {
          status.textContent = 'Verified · rev ' + state.revision;
          failure.hidden = true;
          failure.open = false;
          if (state.revision !== lastRevision) {
            lastRevision = state.revision;
            artifact.src = '/artifact.html?revision=' + encodeURIComponent(state.revision) + '&sha=' + encodeURIComponent(state.lastVerified.sha256.slice(0, 12));
            body.dataset.hasArtifact = 'true';
          }
        } else if (state.status === 'needs-fix') {
          status.textContent = 'Needs fix · ' + (state.revision ? 'showing rev ' + state.revision : 'no verified revision');
          diagnostic.textContent = 'Generation ' + state.generation + ' · ' + state.failure.stage + '\\n\\n' + state.failure.message;
          failure.hidden = false;
        } else {
          status.textContent = 'Checking · generation ' + state.generation;
          failure.hidden = true;
        }
      }

      document.getElementById('copy').addEventListener('click', function () {
        if (navigator.clipboard && navigator.clipboard.writeText) {
          navigator.clipboard.writeText(diagnostic.textContent).catch(function () {});
        }
      });

      var events = new EventSource('/events');
      events.addEventListener('state', function (event) {
        try { render(JSON.parse(event.data)); } catch (_) {}
      });
    }());
  </script>
</body>
</html>`;
}

function compactMessage(value) {
  let text = String(value || 'Preview build failed without a diagnostic.').trim();
  const lines = text.split(/\r?\n/);
  const errorLine = lines.findIndex((line) => /^Error:\s/.test(line));
  if (errorLine > 0) text = lines.slice(errorLine).join('\n');
  const relevant = text.split(/\r?\n/);
  const stackLine = relevant.findIndex((line, index) => index > 0 && /^\s*at\s/.test(line));
  if (stackLine > 0) text = relevant.slice(0, stackLine).join('\n');
  return text.length > 6000 ? `${text.slice(0, 6000)}\n… diagnostic truncated` : text;
}

function redactDiagnostic(value, paths) {
  let text = compactMessage(value);
  for (const [absolutePath, replacement] of paths) {
    if (!absolutePath) continue;
    text = text.split(absolutePath).join(replacement);
  }
  return text;
}

function safeJson(value) {
  return JSON.stringify(value).replace(/</g, '\\u003c');
}

function responseHeaders(contentType) {
  return {
    'Cache-Control': 'no-store',
    'Content-Type': contentType,
    'Cross-Origin-Resource-Policy': 'same-origin',
    'Referrer-Policy': 'no-referrer',
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'SAMEORIGIN',
  };
}

function parseReceipt(stdout) {
  try {
    return JSON.parse(stdout);
  } catch {
    return null;
  }
}

export async function startPreview(options) {
  const type = options.type;
  if (!diagramTypes.has(type)) throw new Error(`Unknown diagram type "${type}".`);
  if (options.quality && !['standard', 'showcase'].includes(options.quality)) {
    throw new Error(`Unknown quality profile "${options.quality}".`);
  }
  const inputPath = path.resolve(options.input);
  const outputRequest = {
    requestedOutput: options.output,
    authoredOutput: initialAuthoredOutput(inputPath),
    defaultOutput: `${type}.html`,
    inputPaths: [inputPath],
    inputDescription: 'its JSON input',
    cwd: options.cwd || process.cwd(),
  };
  const { outputPath } = resolveOutputPath(outputRequest);
  const outputDirectory = path.dirname(outputPath);
  const debounceMs = Number.isFinite(options.debounceMs) ? options.debounceMs : defaultDebounceMs;
  const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : defaultPollMs;
  const stopGraceMs = Number.isFinite(options.stopGraceMs) ? Math.max(0, options.stopGraceMs) : defaultStopGraceMs;
  const stopKillMs = Number.isFinite(options.stopKillMs) ? Math.max(0, options.stopKillMs) : defaultStopKillMs;
  const shouldOpen = options.open !== false;

  fs.mkdirSync(outputDirectory, { recursive: true });
  const stagingDirectory = fs.mkdtempSync(path.join(outputDirectory, '.archify-preview-'));

  let port = 0;
  let watcher;
  let debounceTimer;
  let pollTimer;
  let stopGraceTimer;
  let stopKillTimer;
  let child;
  let stopping = false;
  let stopped = false;
  let serverClosing = false;
  let serverClosed = false;
  let queuedHash = null;
  let activeHash = null;
  let lastGoodSourceHash = null;
  let sourceEpoch = 0;
  let activeEpoch = 0;
  let pendingBuild = false;
  let artifactBuffer = null;
  const clients = new Set();
  const state = {
    schemaVersion: 1,
    status: 'checking',
    generation: 0,
    revision: 0,
    lastVerified: null,
    failure: null,
  };

  let resolveClosed;
  const closed = new Promise((resolve) => { resolveClosed = resolve; });

  function publicState() {
    return JSON.parse(JSON.stringify(state));
  }

  function sendState(res) {
    res.write(`event: state\ndata: ${safeJson(publicState())}\n\n`);
  }

  function broadcast() {
    for (const res of clients) sendState(res);
  }

  const page = Buffer.from(previewPage());
  const server = http.createServer((req, res) => {
    const expectedHost = `${loopbackHost}:${port}`;
    if (req.headers.host !== expectedHost) {
      res.writeHead(403, responseHeaders('text/plain; charset=utf-8'));
      res.end('Forbidden host');
      return;
    }
    if (req.method !== 'GET' && req.method !== 'HEAD') {
      res.writeHead(405, { ...responseHeaders('text/plain; charset=utf-8'), Allow: 'GET, HEAD' });
      res.end('Method not allowed');
      return;
    }

    let url;
    try {
      url = new URL(req.url, `http://${expectedHost}`);
    } catch {
      res.writeHead(400, responseHeaders('text/plain; charset=utf-8'));
      res.end('Bad request');
      return;
    }

    if (url.pathname === '/') {
      res.writeHead(200, {
        ...responseHeaders('text/html; charset=utf-8'),
        'Content-Security-Policy': "default-src 'none'; frame-src 'self'; connect-src 'self'; script-src 'unsafe-inline'; style-src 'unsafe-inline'",
        'Content-Length': page.byteLength,
      });
      if (req.method === 'HEAD') res.end();
      else res.end(page);
      return;
    }
    if (url.pathname === '/state') {
      const body = Buffer.from(`${safeJson(publicState())}\n`);
      res.writeHead(200, { ...responseHeaders('application/json; charset=utf-8'), 'Content-Length': body.byteLength });
      if (req.method === 'HEAD') res.end();
      else res.end(body);
      return;
    }
    if (url.pathname === '/artifact.html') {
      if (!artifactBuffer) {
        res.writeHead(404, responseHeaders('text/plain; charset=utf-8'));
        res.end('No verified artifact yet');
        return;
      }
      res.writeHead(200, { ...responseHeaders('text/html; charset=utf-8'), 'Content-Length': artifactBuffer.byteLength });
      if (req.method === 'HEAD') res.end();
      else res.end(artifactBuffer);
      return;
    }
    if (url.pathname === '/events' && req.method === 'GET') {
      res.writeHead(200, {
        ...responseHeaders('text/event-stream; charset=utf-8'),
        Connection: 'keep-alive',
      });
      res.write('retry: 1000\n\n');
      clients.add(res);
      sendState(res);
      req.on('close', () => clients.delete(res));
      return;
    }

    res.writeHead(404, responseHeaders('text/plain; charset=utf-8'));
    res.end('Not found');
  });

  try {
    await new Promise((resolve, reject) => {
      server.once('error', reject);
      server.listen(0, loopbackHost, () => {
        server.off('error', reject);
        port = server.address().port;
        resolve();
      });
    });
  } catch (error) {
    try { server.close(); } catch {}
    fs.rmSync(stagingDirectory, { recursive: true, force: true });
    throw error;
  }

  const url = `http://${loopbackHost}:${port}/`;

  function finishStop() {
    if (stopped || child || !serverClosed) return;
    stopped = true;
    clearTimeout(debounceTimer);
    clearInterval(pollTimer);
    clearTimeout(stopGraceTimer);
    clearTimeout(stopKillTimer);
    try {
      fs.rmSync(stagingDirectory, { recursive: true, force: true });
    } finally {
      resolveClosed();
    }
  }

  function signalActiveChild(signal) {
    if (!child || child.exitCode !== null || child.signalCode !== null) return;
    try {
      if (process.platform !== 'win32' && child.pid) process.kill(-child.pid, signal);
      else child.kill(signal);
    } catch (error) {
      if (error.code === 'ESRCH') return;
      try { child.kill(signal); } catch {}
    }
  }

  function closeServer() {
    if (serverClosing) return;
    serverClosing = true;
    for (const res of clients) res.end();
    clients.clear();
    server.close(() => {
      serverClosed = true;
      finishStop();
    });
    server.closeIdleConnections?.();
  }

  function startBoundedChildDrain() {
    if (!child || stopGraceTimer || stopKillTimer) return;
    stopGraceTimer = setTimeout(() => {
      stopGraceTimer = undefined;
      if (!child) return finishStop();
      signalActiveChild('SIGTERM');
      stopKillTimer = setTimeout(() => {
        stopKillTimer = undefined;
        signalActiveChild('SIGKILL');
      }, stopKillMs);
    }, stopGraceMs);
  }

  async function stop({ force = false } = {}) {
    if (!stopping) {
      stopping = true;
      clearTimeout(debounceTimer);
      clearInterval(pollTimer);
      watcher?.close();
      closeServer();
    }
    if (child && force) {
      clearTimeout(stopGraceTimer);
      clearTimeout(stopKillTimer);
      stopGraceTimer = undefined;
      stopKillTimer = undefined;
      signalActiveChild('SIGKILL');
    } else if (child) {
      startBoundedChildDrain();
    } else {
      finishStop();
    }
    return closed;
  }

  function publishFailure(receipt, stdout, stderr, candidatePath, snapshotPath) {
    const repairDetails = receipt?.diagnostics
      ?.slice(0, 12)
      .map((entry) => {
        const fix = entry.supportedFixes?.length ? `\nFix: ${entry.supportedFixes.join('; ')}` : '';
        return `[${entry.code}] ${entry.message}${fix}`;
      }) || [];
    const checkerDetails = receipt?.checker?.checks
      ?.filter((check) => !check.ok)
      .flatMap((check) => check.details || [])
      .filter(Boolean)
      .slice(0, 12) || [];
    const diagnostic = [
      receipt?.error,
      ...(repairDetails.length ? repairDetails : checkerDetails),
    ].filter(Boolean).join('\n') || stderr || stdout;
    state.status = 'needs-fix';
    state.failure = {
      stage: receipt?.stage || 'render',
      message: redactDiagnostic(
        diagnostic,
        [
          [inputPath, '<input.json>'],
          [outputPath, '<output.html>'],
          [snapshotPath, '<input.json>'],
          [candidatePath, '<candidate.html>'],
          [stagingDirectory, '<preview-staging>'],
          [path.resolve(here, '..'), '<archify-skill>'],
          [path.resolve(options.cwd || process.cwd()), '<working-directory>'],
          ...(options.repoRoot ? [[path.resolve(options.repoRoot), '<repo-root>']] : []),
        ],
      ),
    };
    broadcast();
  }

  function commitCandidate(candidatePath, receipt, generationHash) {
    let candidate;
    try {
      candidate = fs.readFileSync(candidatePath);
      const digest = sha256(candidate);
      if (digest !== receipt?.artifact?.sha256) {
        throw new Error('Verified candidate bytes do not match the delivery receipt.');
      }
      resolveOutputPath(outputRequest);
      const sameArtifact = state.lastVerified?.sha256 === digest;
      let outputMatches = false;
      if (sameArtifact) {
        try { outputMatches = sha256(fs.readFileSync(outputPath)) === digest; } catch {}
      }
      const currentSource = sourceDigest(inputPath);
      if (currentSource.hash !== generationHash) {
        return { committed: false, supersededBy: currentSource };
      }
      if (!sameArtifact || !outputMatches) fs.renameSync(candidatePath, outputPath);
      artifactBuffer = candidate;
      lastGoodSourceHash = generationHash;
      state.status = 'verified';
      if (!sameArtifact) {
        state.revision += 1;
        state.lastVerified = {
          sha256: digest,
          bytes: candidate.byteLength,
          checksPassed: receipt.validation.checksPassed,
          checkCount: receipt.validation.checkCount,
          compositionProfile: receipt.validation.compositionProfile,
          compositionStatus: receipt.validation.compositionStatus,
        };
      }
      state.failure = null;
      broadcast();
      return { committed: true, supersededBy: null };
    } catch (error) {
      publishFailure({ stage: 'commit', error: `Could not publish the verified preview: ${error.message}` }, '', '', candidatePath);
      return { committed: false, supersededBy: null };
    }
  }

  function beginBuild(digest, epoch) {
    if (stopping || child) return;
    activeHash = digest.hash;
    activeEpoch = epoch;
    state.generation += 1;
    state.status = 'checking';
    state.failure = null;
    broadcast();

    const candidatePath = path.join(stagingDirectory, `generation-${state.generation}.html`);
    const snapshotPath = path.join(stagingDirectory, `generation-${state.generation}.json`);
    if (digest.bytes !== null) {
      try {
        fs.writeFileSync(snapshotPath, digest.bytes, { flag: 'wx', mode: 0o600 });
      } catch (error) {
        publishFailure(
          { stage: 'prepare', error: `Could not snapshot the observed input: ${error.message}` },
          '',
          '',
          candidatePath,
          snapshotPath,
        );
        return;
      }
    }
    const args = [options.deliveryCli || cliPath, 'deliver', type, snapshotPath, candidatePath, '--json'];
    if (options.quality) args.push('--quality', options.quality);
    if (options.repoRoot) args.push('--repo-root', path.resolve(options.repoRoot));
    let stdout = '';
    let stderr = '';
    child = spawn(process.execPath, args, {
      cwd: options.cwd || process.cwd(),
      env: process.env,
      stdio: ['ignore', 'pipe', 'pipe'],
      detached: process.platform !== 'win32',
    });
    child.stdout.setEncoding('utf8');
    child.stderr.setEncoding('utf8');
    child.stdout.on('data', (chunk) => { stdout += chunk; });
    child.stderr.on('data', (chunk) => { stderr += chunk; });
    child.on('error', (error) => { stderr += error.message; });
    child.on('close', (code) => {
      const receipt = parseReceipt(stdout);
      const generationEpoch = activeEpoch;
      const generationHash = activeHash;
      const stale = generationEpoch !== sourceEpoch;
      let supersededBy = null;
      child = null;
      clearTimeout(stopGraceTimer);
      clearTimeout(stopKillTimer);
      stopGraceTimer = undefined;
      stopKillTimer = undefined;
      if (!stopping && !stale && code === 0 && receipt?.ok) {
        ({ supersededBy } = commitCandidate(candidatePath, receipt, generationHash));
      } else if (!stopping && !stale) {
        publishFailure(receipt, stdout, stderr, candidatePath, snapshotPath);
      }
      try { fs.rmSync(candidatePath, { force: true }); } catch {}
      try { fs.rmSync(snapshotPath, { force: true }); } catch {}

      if (stopping) {
        finishStop();
      } else if (pendingBuild || stale || supersededBy) {
        pendingBuild = false;
        if (supersededBy && sourceEpoch === generationEpoch) sourceEpoch += 1;
        const digest = supersededBy || sourceDigest(inputPath);
        queueStableBuild(digest.hash, true);
      }
    });
  }

  function queueStableBuild(hash, immediate = false) {
    queuedHash = hash;
    clearTimeout(debounceTimer);
    const launch = () => {
      if (stopping) return;
      const digest = sourceDigest(inputPath);
      if (digest.hash !== queuedHash) {
        queueStableBuild(digest.hash);
        return;
      }
      if (digest.hash === lastGoodSourceHash) {
        if (state.status !== 'verified' && state.lastVerified) {
          state.status = 'verified';
          state.failure = null;
          broadcast();
        }
        return;
      }
      if (child) {
        pendingBuild = true;
        return;
      }
      beginBuild(digest, sourceEpoch);
    };
    debounceTimer = setTimeout(launch, immediate ? 0 : debounceMs);
  }

  function observeSource({ immediate = false } = {}) {
    const digest = sourceDigest(inputPath);
    if (!immediate && digest.hash === queuedHash) return;
    sourceEpoch += 1;
    queueStableBuild(digest.hash, immediate);
  }

  if (options.watch !== false) {
    try {
      watcher = fs.watch(path.dirname(inputPath), (event, filename) => {
        if (!filename || filename.toString() === path.basename(inputPath)) observeSource();
      });
      watcher.on('error', () => {
        const failedWatcher = watcher;
        watcher = undefined;
        failedWatcher?.close();
      });
    } catch (error) {
      await stop();
      throw new Error(`Could not watch the input directory: ${error.message}`);
    }
  }
  pollTimer = setInterval(() => observeSource(), pollMs);

  let opener = null;
  if (shouldOpen) {
    try {
      opener = openLoopbackUrl(url);
    } catch {
      opener = { requested: true, status: 'unsupported', target: url, method: null };
    }
  }

  observeSource({ immediate: true });

  return {
    url,
    input: inputPath,
    output: outputPath,
    opener,
    state: publicState,
    stop,
    closed,
  };
}

export async function runPreview(options) {
  const preview = await startPreview(options);
  console.log(`preview ${preview.url}`);
  console.log(`watching ${preview.input}`);
  console.log(`output ${preview.output}`);
  if (preview.opener && preview.opener.status !== 'opened') {
    console.error(`Could not open the preview (${preview.opener.status}). Open it manually: ${preview.url}`);
  }

  let signalCount = 0;
  const stop = () => {
    signalCount += 1;
    if (signalCount === 1) {
      console.log('\nstopping preview…');
      preview.stop();
    } else {
      console.log('\nforcing preview shutdown…');
      preview.stop({ force: true });
    }
  };
  process.on('SIGINT', stop);
  process.on('SIGTERM', stop);
  await preview.closed;
  process.off('SIGINT', stop);
  process.off('SIGTERM', stop);
}
```

## bin/visual-check.mjs

```js
import { spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import {
  DESKTOP_READABILITY_VIEWPORT,
  MIN_PROJECTED_NODE_TEXT_PX,
} from '../renderers/shared/desktop-readability.mjs';

export const VISUAL_CHECK_VIEWPORTS = Object.freeze([
  DESKTOP_READABILITY_VIEWPORT,
  Object.freeze({ width: 1600, height: 1000 }),
  Object.freeze({ width: 1920, height: 1080 }),
  Object.freeze({ width: 2048, height: 1320 }),
]);

const CAPTURE_VIEWPORTS = Object.freeze([
  VISUAL_CHECK_VIEWPORTS[0],
  VISUAL_CHECK_VIEWPORTS[VISUAL_CHECK_VIEWPORTS.length - 1],
]);
const THEMES = Object.freeze(['light', 'dark']);
const EXIT = Object.freeze({ pass: 0, fail: 1, skipped: 2 });
export const CHROME_NO_SANDBOX_ENV = 'ARCHIFY_CHROME_NO_SANDBOX';

function sha256(buffer) {
  return createHash('sha256').update(buffer).digest('hex');
}

function htmlEscape(value) {
  return String(value).replace(/[&<>"']/g, (char) => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
  })[char]);
}

function safeUnlink(file) {
  try {
    fs.rmSync(file, { force: true });
  } catch {
    // A stale optional sidecar must never make the delivered HTML mutable.
  }
}

function writeAtomic(file, contents) {
  const temporary = `${file}.tmp-${process.pid}`;
  try {
    fs.writeFileSync(temporary, contents, { flag: 'w' });
    fs.renameSync(temporary, file);
  } finally {
    safeUnlink(temporary);
  }
}

function screenshotKey(width, height, theme) {
  return `${width}x${height}:${theme}`;
}

export function sidecarPaths(artifactPath) {
  const artifact = path.resolve(artifactPath);
  const stem = artifact.replace(/\.html?$/i, '');
  const base = `${stem}.visual-check`;
  const screenshots = CAPTURE_VIEWPORTS.flatMap(({ width, height }) => THEMES.map((theme) => ({
    width,
    height,
    theme,
    path: `${base}.${width}x${height}.${theme}.png`,
  })));
  return {
    base,
    receipt: `${base}.json`,
    contactSheet: `${base}.html`,
    screenshots,
  };
}

function cleanupCaptureSidecars(paths) {
  safeUnlink(paths.contactSheet);
  for (const screenshot of paths.screenshots) safeUnlink(screenshot.path);
}

function executable(file, platform = process.platform) {
  if (!file) return null;
  try {
    fs.accessSync(file, platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK);
    return path.resolve(file);
  } catch {
    return null;
  }
}

function findOnPath(command, env, platform) {
  const directories = String(env.PATH || '').split(path.delimiter).filter(Boolean);
  const extensions = platform === 'win32'
    ? String(env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
    : [''];
  for (const directory of directories) {
    for (const extension of extensions) {
      const candidate = path.join(directory, `${command}${extension}`);
      const resolved = executable(candidate, platform);
      if (resolved) return resolved;
    }
  }
  return null;
}

export function findChrome({ env = process.env, platform = process.platform } = {}) {
  if (Object.prototype.hasOwnProperty.call(env, 'ARCHIFY_CHROME')) {
    return executable(env.ARCHIFY_CHROME, platform);
  }

  const fixed = [];
  const commands = [];
  if (platform === 'darwin') {
    fixed.push(
      '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
      '/Applications/Chromium.app/Contents/MacOS/Chromium',
    );
  } else if (platform === 'win32') {
    for (const root of [env.PROGRAMFILES, env['PROGRAMFILES(X86)'], env.LOCALAPPDATA].filter(Boolean)) {
      fixed.push(
        path.join(root, 'Google', 'Chrome', 'Application', 'chrome.exe'),
        path.join(root, 'Chromium', 'Application', 'chrome.exe'),
      );
    }
  } else {
    commands.push('google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser');
  }

  for (const candidate of fixed) {
    const resolved = executable(candidate, platform);
    if (resolved) return resolved;
  }
  for (const command of commands) {
    const resolved = findOnPath(command, env, platform);
    if (resolved) return resolved;
  }
  return null;
}

class PipeCdp {
  constructor(child, { failureDetails = () => '' } = {}) {
    this.child = child;
    this.failureDetails = failureDetails;
    this.nextId = 1;
    this.buffer = '';
    this.pending = new Map();
    this.waiters = [];
    this.writePipe = child.stdio[3];
    this.readPipe = child.stdio[4];
    this.readPipe.setEncoding('utf8');
    this.readPipe.on('data', (chunk) => this.consume(chunk));
    this.writePipe.on('error', (error) => this.failAll(this.failure('write pipe', error)));
    this.readPipe.on('error', (error) => this.failAll(this.failure('read pipe', error)));
    child.once('error', (error) => this.failAll(this.failure('process launch', error)));
    child.once('close', (code, signal) => {
      const ending = signal ? `signal ${signal}` : `exit code ${code}`;
      this.failAll(this.failure('process exit', new Error(`Chrome closed with ${ending}`)));
    });
  }

  failure(stage, error) {
    const code = error?.code ? ` [${error.code}]` : '';
    const details = this.failureDetails();
    return new Error([
      `Chrome DevTools ${stage} failed: ${error?.message || String(error)}${code}`,
      details,
    ].filter(Boolean).join('\n'));
  }

  consume(chunk) {
    this.buffer += chunk;
    let boundary;
    while ((boundary = this.buffer.indexOf('\0')) >= 0) {
      const raw = this.buffer.slice(0, boundary);
      this.buffer = this.buffer.slice(boundary + 1);
      if (!raw) continue;
      let message;
      try {
        message = JSON.parse(raw);
      } catch (error) {
        this.failAll(new Error(`Chrome DevTools returned invalid JSON: ${error.message}`));
        continue;
      }
      if (message.id) {
        const pending = this.pending.get(message.id);
        if (!pending) continue;
        clearTimeout(pending.timer);
        this.pending.delete(message.id);
        if (message.error) pending.reject(new Error(`${pending.method}: ${message.error.message}`));
        else pending.resolve(message.result || {});
        continue;
      }
      for (const waiter of [...this.waiters]) {
        if (waiter.method !== message.method) continue;
        if (waiter.sessionId && waiter.sessionId !== message.sessionId) continue;
        clearTimeout(waiter.timer);
        this.waiters.splice(this.waiters.indexOf(waiter), 1);
        waiter.resolve(message.params || {});
      }
    }
  }

  send(method, params = {}, sessionId = undefined, timeoutMs = 15000) {
    const id = this.nextId++;
    const message = { id, method, params };
    if (sessionId) message.sessionId = sessionId;
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        this.pending.delete(id);
        reject(new Error(`${method}: timed out after ${timeoutMs}ms`));
      }, timeoutMs);
      this.pending.set(id, { method, resolve, reject, timer });
      try {
        this.writePipe.write(`${JSON.stringify(message)}\0`, (error) => {
          if (error) this.failAll(this.failure('write pipe', error));
        });
      } catch (error) {
        this.failAll(this.failure('write pipe', error));
      }
    });
  }

  waitFor(method, sessionId, timeoutMs = 15000) {
    return new Promise((resolve, reject) => {
      const waiter = { method, sessionId, resolve, reject, timer: null };
      waiter.timer = setTimeout(() => {
        this.waiters.splice(this.waiters.indexOf(waiter), 1);
        reject(new Error(`${method}: event timed out after ${timeoutMs}ms`));
      }, timeoutMs);
      this.waiters.push(waiter);
    });
  }

  failAll(error) {
    for (const pending of this.pending.values()) {
      clearTimeout(pending.timer);
      pending.reject(error);
    }
    for (const waiter of this.waiters) {
      clearTimeout(waiter.timer);
      waiter.reject(error);
    }
    this.pending.clear();
    this.waiters = [];
  }
}

export function chromeVisualBrowserArgs(profileRoot, {
  env = process.env,
  getuid = typeof process.getuid === 'function' ? () => process.getuid() : null,
} = {}) {
  const args = [
    '--headless=new',
    '--remote-debugging-pipe',
    '--disable-gpu',
    '--hide-scrollbars',
    '--disable-background-networking',
    '--disable-component-update',
    '--disable-default-apps',
    '--disable-sync',
    '--metrics-recording-only',
    '--no-first-run',
    '--no-default-browser-check',
    '--disable-background-timer-throttling',
    '--disable-backgrounding-occluded-windows',
    '--disable-renderer-backgrounding',
    '--force-device-scale-factor=1',
    `--user-data-dir=${profileRoot}`,
    'about:blank',
  ];
  const rootUser = typeof getuid === 'function' && getuid() === 0;
  const sandboxOptOut = env?.[CHROME_NO_SANDBOX_ENV] === '1';
  if (rootUser || sandboxOptOut) args.unshift('--no-sandbox');
  return args;
}

async function evaluate(cdp, sessionId, expression, awaitPromise = false) {
  const response = await cdp.send('Runtime.evaluate', {
    expression,
    awaitPromise,
    returnByValue: true,
  }, sessionId);
  if (response.exceptionDetails) {
    throw new Error(response.exceptionDetails.exception?.description
      || response.exceptionDetails.text
      || 'Runtime.evaluate failed');
  }
  return response.result?.value;
}

export class ChromeVisualBrowser {
  constructor(chromePath, {
    env = process.env,
    getuid = typeof process.getuid === 'function' ? () => process.getuid() : null,
    spawnImpl = spawn,
  } = {}) {
    this.profileRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-visual-check-profile-'));
    this.stderr = '';
    const args = chromeVisualBrowserArgs(this.profileRoot, { env, getuid });
    this.child = spawnImpl(chromePath, args, { stdio: ['ignore', 'ignore', 'pipe', 'pipe', 'pipe'] });
    this.child.stderr.setEncoding('utf8');
    this.child.stderr.on('data', (chunk) => {
      this.stderr = `${this.stderr}${chunk}`.slice(-8000);
    });
    this.child.stderr.on('error', (error) => {
      this.stderr = `${this.stderr}\nChrome stderr stream failed: ${error.message}`.trim().slice(-8000);
    });
    this.cdp = new PipeCdp(this.child, {
      failureDetails: () => {
        const exit = this.child.signalCode
          ? `signal ${this.child.signalCode}`
          : this.child.exitCode == null ? 'still running' : `exit code ${this.child.exitCode}`;
        const stderr = this.stderr.trim();
        return [
          `Chrome process: ${exit}.`,
          stderr ? `Chrome stderr:\n${stderr}` : '',
        ].filter(Boolean).join('\n');
      },
    });
    this.sessionPromise = this.attach();
  }

  async attach() {
    const targets = await this.cdp.send('Target.getTargets');
    let target = targets.targetInfos?.find((item) => item.type === 'page');
    if (!target) {
      const created = await this.cdp.send('Target.createTarget', { url: 'about:blank' });
      target = { targetId: created.targetId };
    }
    const attached = await this.cdp.send('Target.attachToTarget', {
      targetId: target.targetId,
      flatten: true,
    });
    await this.cdp.send('Page.enable', {}, attached.sessionId);
    await this.cdp.send('Runtime.enable', {}, attached.sessionId);
    return attached.sessionId;
  }

  async inspect({ artifactPath, width, height, theme, screenshotPath }) {
    const sessionId = await this.sessionPromise;
    await this.cdp.send('Emulation.setDeviceMetricsOverride', {
      width,
      height,
      deviceScaleFactor: 1,
      mobile: false,
    }, sessionId);

    const url = new URL(pathToFileURL(artifactPath).href);
    url.searchParams.set('theme', theme);
    const loaded = this.cdp.waitFor('Page.loadEventFired', sessionId);
    const navigation = await this.cdp.send('Page.navigate', { url: url.href }, sessionId);
    if (navigation.errorText) throw new Error(`Chrome navigation failed: ${navigation.errorText}`);
    await loaded;
    await evaluate(this.cdp, sessionId, `(function () {
      document.documentElement.setAttribute('data-motion', 'still');
      var panel = document.querySelector('.diagram-container');
      if (panel) panel.setAttribute('data-detail-level', 'read');
      var fontsReady = document.fonts && document.fonts.ready
        ? document.fonts.ready.catch(function () {})
        : Promise.resolve();
      return fontsReady.then(function () {
        if (window.Archify && Archify.readerLayout && typeof Archify.readerLayout.whenStable === 'function') {
          return Archify.readerLayout.whenStable();
        }
      }).then(function () {
        if (window.Archify && Archify.viewerChromeLayout && typeof Archify.viewerChromeLayout.whenStable === 'function') {
          return Archify.viewerChromeLayout.whenStable();
        }
      }).then(function () {
        if (window.Archify && Archify.readerLayout && typeof Archify.readerLayout.whenStable === 'function') {
          return Archify.readerLayout.whenStable();
        }
      }).then(function () {
        if (window.Archify && Archify.viewerChromeLayout && typeof Archify.viewerChromeLayout.whenStable === 'function') {
          return Archify.viewerChromeLayout.whenStable();
        }
        return new Promise(function (resolve) {
          requestAnimationFrame(function () { requestAnimationFrame(resolve); });
        });
      });
    })()`, true);

    const metrics = await evaluate(this.cdp, sessionId, `(function () {
      var reader = document.querySelector('.container');
      var diagram = document.querySelector('.diagram-container');
      var svg = diagram && (
        diagram.querySelector(':scope > svg') ||
        diagram.querySelector(':scope > .diagram-stage > svg')
      );
      var stage = diagram && (diagram.querySelector(':scope > .diagram-stage') || svg);
      var legend = svg && svg.querySelector('[data-legend]');
      var navigationDock = diagram && diagram.querySelector('.diagram-nav');
      var viewBox = svg && svg.viewBox && svg.viewBox.baseVal;
      var diagramWidth = svg ? svg.getBoundingClientRect().width : 0;
      var viewBoxWidth = viewBox ? viewBox.width : 0;
      var scale = viewBoxWidth > 0 ? Math.min(1, diagramWidth / viewBoxWidth) : 0;
      var minimum = null;
      if (svg && scale > 0) {
        Array.from(svg.querySelectorAll('text[data-node-label], text[data-boundary-label], text[data-detail="context"]')).forEach(function (text) {
          var detail = text.hasAttribute('data-node-label')
            ? 'primary'
            : text.hasAttribute('data-boundary-label') ? 'boundary' : 'context';
          if (detail === 'context' && !text.closest('[data-node-id]')) return;
          var sourceFontPx = parseFloat(text.getAttribute('font-size') || '');
          if (!Number.isFinite(sourceFontPx)) return;
          var projectedFontPx = sourceFontPx * scale;
          if (!minimum || projectedFontPx < minimum.projectedFontPx) {
            minimum = {
              text: (text.textContent || '').trim(),
              detail: detail,
              sourceFontPx: sourceFontPx,
              projectedFontPx: projectedFontPx
            };
          }
        });
      }
      function intersectionArea(a, b) {
        if (!a || !b || !a.width || !a.height || !b.width || !b.height) return 0;
        var width = Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left));
        var height = Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top));
        return width * height;
      }
      var legendRect = legend ? legend.getBoundingClientRect() : null;
      var stageRect = window.Archify && Archify.viewerChromeLayout
        && typeof Archify.viewerChromeLayout.stageRect === 'function'
        ? Archify.viewerChromeLayout.stageRect()
        : (stage ? stage.getBoundingClientRect() : null);
      var navigationDockRect = navigationDock ? navigationDock.getBoundingClientRect() : null;
      var stageDockIntersectionArea = intersectionArea(stageRect, navigationDockRect);
      var viewerChromeReceipt = window.Archify && Archify.viewerChromeLayout
        && typeof Archify.viewerChromeLayout.receipt === 'function'
        ? Archify.viewerChromeLayout.receipt()
        : null;
      return {
        innerWidth: window.innerWidth,
        innerHeight: window.innerHeight,
        scrollWidth: Math.ceil(document.documentElement.scrollWidth),
        scrollHeight: Math.ceil(document.documentElement.scrollHeight),
        resolvedTheme: document.documentElement.getAttribute('data-theme') || '',
        readerWidth: reader ? reader.getBoundingClientRect().width : 0,
        diagramWidth: diagramWidth,
        viewBoxWidth: viewBoxWidth,
        minimumProjectedNodeTextPx: minimum ? minimum.projectedFontPx : null,
        minimumProjectedNodeText: minimum ? minimum.text : null,
        minimumProjectedNodeTextDetail: minimum ? minimum.detail : null,
        hasLegend: Boolean(legendRect && legendRect.width && legendRect.height),
        hasNavigationDock: Boolean(navigationDockRect && navigationDockRect.width && navigationDockRect.height),
        legendDockIntersectionArea: stageDockIntersectionArea > 0
          ? intersectionArea(legendRect, navigationDockRect)
          : 0,
        dockStageIntersectionArea: stageDockIntersectionArea,
        dockStageGap: stageRect && navigationDockRect ? navigationDockRect.top - stageRect.bottom : null,
        viewerChromeRequiredGap: viewerChromeReceipt ? viewerChromeReceipt.gap : null,
        viewerChromeReserve: viewerChromeReceipt ? viewerChromeReceipt.reserve : 0,
        viewerChromeActive: viewerChromeReceipt ? viewerChromeReceipt.active : false
      };
    })()`);
    if (!metrics || !Number.isFinite(metrics.scrollWidth) || !Number.isFinite(metrics.scrollHeight)) {
      throw new Error('Chrome returned incomplete containment metrics.');
    }

    if (screenshotPath) {
      const capture = await this.cdp.send('Page.captureScreenshot', {
        format: 'png',
        fromSurface: true,
        captureBeyondViewport: false,
      }, sessionId, 20000);
      if (!capture.data) throw new Error('Chrome returned an empty screenshot.');
      fs.writeFileSync(screenshotPath, Buffer.from(capture.data, 'base64'));
    }
    return metrics;
  }

  async close() {
    this.cdp.failAll(new Error('visual-check finished'));
    if (this.child.exitCode === null && this.child.signalCode === null) {
      this.child.kill('SIGTERM');
      await new Promise((resolve) => {
        const timer = setTimeout(() => {
          if (this.child.exitCode === null && this.child.signalCode === null) this.child.kill('SIGKILL');
          resolve();
        }, 1500);
        this.child.once('exit', () => {
          clearTimeout(timer);
          resolve();
        });
      });
    }
    try {
      fs.rmSync(this.profileRoot, { recursive: true, force: true });
    } catch {
      // Chrome may briefly retain profile files on Windows; evidence is done.
    }
  }
}

function observation({ width, height, theme, metrics }) {
  const innerWidth = Number(metrics.innerWidth);
  const innerHeight = Number(metrics.innerHeight);
  const scrollWidth = Number(metrics.scrollWidth);
  const scrollHeight = Number(metrics.scrollHeight);
  const overflowX = scrollWidth > innerWidth;
  const overflowY = scrollHeight > innerHeight;
  const minimumProjectedNodeTextPx = metrics.minimumProjectedNodeTextPx == null
    ? null
    : Number(metrics.minimumProjectedNodeTextPx);
  const readabilityOk = minimumProjectedNodeTextPx == null
    || minimumProjectedNodeTextPx >= MIN_PROJECTED_NODE_TEXT_PX;
  const legendDockIntersectionArea = Number(metrics.legendDockIntersectionArea) || 0;
  const dockStageIntersectionArea = Number(metrics.dockStageIntersectionArea) || 0;
  const dockStageGap = metrics.dockStageGap == null ? null : Number(metrics.dockStageGap);
  const receiptDockStageGap = metrics.viewerChromeRequiredGap == null
    ? null
    : Number(metrics.viewerChromeRequiredGap);
  const requiredDockStageGap = Number.isFinite(receiptDockStageGap) ? receiptDockStageGap : 0;
  const viewerChromeStageOk = !metrics.hasNavigationDock || (
    Number.isFinite(dockStageGap)
    && dockStageIntersectionArea <= 0.5
    && dockStageGap >= requiredDockStageGap - 1
  );
  const viewerChromeOk = legendDockIntersectionArea <= 0.5 && viewerChromeStageOk;
  return {
    width,
    height,
    theme,
    innerWidth,
    innerHeight,
    scrollWidth,
    scrollHeight,
    overflowX,
    overflowY,
    ok: !overflowX && !overflowY,
    readerWidth: Number(metrics.readerWidth) || null,
    diagramWidth: Number(metrics.diagramWidth) || null,
    viewBoxWidth: Number(metrics.viewBoxWidth) || null,
    minimumProjectedNodeTextPx,
    minimumProjectedNodeText: metrics.minimumProjectedNodeText || null,
    minimumProjectedNodeTextDetail: metrics.minimumProjectedNodeTextDetail || null,
    minimumRequiredNodeTextPx: MIN_PROJECTED_NODE_TEXT_PX,
    readabilityOk,
    hasLegend: Boolean(metrics.hasLegend),
    hasNavigationDock: Boolean(metrics.hasNavigationDock),
    legendDockIntersectionArea,
    dockStageIntersectionArea,
    dockStageGap,
    requiredDockStageGap,
    viewerChromeStageOk,
    viewerChromeReserve: Number(metrics.viewerChromeReserve) || 0,
    viewerChromeActive: Boolean(metrics.viewerChromeActive),
    viewerChromeOk,
    resolvedTheme: metrics.resolvedTheme || theme,
  };
}

function contactSheetHtml({ artifactPath, receipt, screenshots }) {
  const cards = screenshots.map((entry) => `
      <figure>
        <img src="${htmlEscape(entry.file)}" alt="${htmlEscape(`${entry.theme} ${entry.width} by ${entry.height}`)}">
        <figcaption><strong>${htmlEscape(entry.theme.toUpperCase())}</strong> · ${entry.width}×${entry.height} · containment ${entry.ok ? 'pass' : 'fail'}</figcaption>
      </figure>`).join('');
  return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Archify automated browser evidence · ${htmlEscape(path.basename(artifactPath))}</title>
<style>
*{box-sizing:border-box}body{margin:0;padding:24px;background:#e9eef5;color:#172033;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}header{max-width:1500px;margin:0 auto 18px}h1{margin:0 0 6px;font-size:20px}p{margin:0;color:#526176}.grid{max-width:1500px;margin:auto;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}figure{margin:0;padding:10px;background:white;border:1px solid #c9d4e3;border-radius:12px;box-shadow:0 10px 30px rgba(15,23,42,.08)}img{display:block;width:100%;height:auto;border:1px solid #e2e8f0}figcaption{padding:9px 4px 2px;color:#526176}@media(max-width:900px){.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<header><h1>Automated browser evidence</h1><p>${htmlEscape(path.basename(artifactPath))} · visual-check containment ${htmlEscape(receipt.containment.status)} · perceptual visual review pending</p></header>
<main class="grid">${cards}
</main>
</body>
</html>
`;
}

function viewportSubject(artifact, entry) {
  return {
    artifact,
    viewport: { width: entry.width, height: entry.height, theme: entry.theme },
  };
}

function failureDiagnostic({ code, message, subject, evidence, supportedFixes, severity = 'error' }) {
  return { code, severity, message, subject, evidence, supportedFixes };
}

function observationDiagnostics({ artifact, allObservations, readabilityObservations }) {
  const diagnostics = [];
  for (const entry of allObservations) {
    if (!entry.ok) {
      diagnostics.push(failureDiagnostic({
        code: 'viewer/viewport-overflow',
        message: `The rendered artifact overflows the ${entry.width}x${entry.height} ${entry.theme} viewport.`,
        subject: viewportSubject(artifact, entry),
        evidence: {
          innerWidth: entry.innerWidth,
          innerHeight: entry.innerHeight,
          scrollWidth: entry.scrollWidth,
          scrollHeight: entry.scrollHeight,
          overflowX: entry.overflowX,
          overflowY: entry.overflowY,
        },
        supportedFixes: [
          `contain the rendered layout within ${entry.width}x${entry.height}, then rerun visual-check`,
        ],
      }));
    }
    if (entry.legendDockIntersectionArea > 0.5) {
      diagnostics.push(failureDiagnostic({
        code: 'viewer/chrome-legend-clearance',
        message: `The navigation Dock obscures the SVG Legend at ${entry.width}x${entry.height} (${entry.theme}).`,
        subject: viewportSubject(artifact, entry),
        evidence: { legendDockIntersectionArea: entry.legendDockIntersectionArea },
        supportedFixes: [
          'move the SVG Legend or Viewer Dock until legendDockIntersectionArea is 0, then rerun visual-check',
        ],
      }));
    }
    if (!entry.viewerChromeStageOk) {
      const stageOverlapsDock = entry.dockStageIntersectionArea > 0.5;
      diagnostics.push(failureDiagnostic({
        code: 'viewer/chrome-stage-clearance',
        message: stageOverlapsDock
          ? `Navigation Dock enters the protected SVG stage at ${entry.width}x${entry.height} (${entry.theme}).`
          : `Navigation Dock clearance from the protected SVG stage is below the required gap at ${entry.width}x${entry.height} (${entry.theme}).`,
        subject: viewportSubject(artifact, entry),
        evidence: {
          dockStageIntersectionArea: entry.dockStageIntersectionArea,
          dockStageGap: entry.dockStageGap,
          requiredDockStageGap: entry.requiredDockStageGap,
        },
        supportedFixes: [
          `adjust Viewer stage reservation or clipping until dockStageGap is at least ${entry.requiredDockStageGap} and dockStageIntersectionArea is 0, then rerun visual-check`,
        ],
      }));
    }
  }
  for (const entry of readabilityObservations) {
    if (entry.readabilityOk) continue;
    diagnostics.push(failureDiagnostic({
      code: 'viewer/projected-text-readability',
      message: `Projected ${entry.minimumProjectedNodeTextDetail || 'node'} text is below the readability floor at ${entry.width}x${entry.height}.`,
      subject: viewportSubject(artifact, entry),
      evidence: {
        text: entry.minimumProjectedNodeText,
        detail: entry.minimumProjectedNodeTextDetail,
        minimumProjectedNodeTextPx: entry.minimumProjectedNodeTextPx,
        minimumRequiredNodeTextPx: entry.minimumRequiredNodeTextPx,
      },
      supportedFixes: [
        `increase projected node text to at least ${entry.minimumRequiredNodeTextPx}px at ${entry.width}x${entry.height}, then rerun visual-check`,
      ],
    }));
  }
  return diagnostics;
}

function baseReceipt({ artifactPath, artifact, outputs, chrome }) {
  return {
    schemaVersion: 1,
    ok: false,
    command: 'visual-check',
    evidenceKind: 'automated-browser',
    status: 'fail',
    visualReview: 'pending',
    artifact: {
      path: artifactPath,
      sha256: sha256(artifact),
      bytes: artifact.byteLength,
    },
    state: { detail: 'read', motion: 'still' },
    chrome,
    diagnostics: [],
    containment: { status: 'fail', viewports: [] },
    readability: { status: 'fail', minimumProjectedNodeTextPx: MIN_PROJECTED_NODE_TEXT_PX, viewports: [] },
    viewerChrome: { status: 'fail', viewports: [] },
    captures: { status: 'fail', screenshots: [], contactSheet: null },
    sidecars: {
      receipt: path.basename(outputs.receipt),
      contactSheet: path.basename(outputs.contactSheet),
    },
  };
}

function persistReceipt(outputs, receipt) {
  writeAtomic(outputs.receipt, `${JSON.stringify(receipt, null, 2)}\n`);
}

export async function runVisualCheck({
  artifactPath,
  chromePath,
  resolveChrome = findChrome,
  browserFactory = async (resolvedChrome) => new ChromeVisualBrowser(resolvedChrome),
} = {}) {
  if (!artifactPath) throw new Error('visual-check requires one delivered HTML artifact.');
  const artifact = path.resolve(artifactPath);
  if (!/\.html?$/i.test(artifact)) throw new Error('visual-check requires an .html artifact.');
  const artifactBytes = fs.readFileSync(artifact);
  const outputs = sidecarPaths(artifact);
  cleanupCaptureSidecars(outputs);
  safeUnlink(outputs.receipt);

  const resolvedChrome = chromePath || resolveChrome();
  const receipt = baseReceipt({
    artifactPath: artifact,
    artifact: artifactBytes,
    outputs,
    chrome: resolvedChrome
      ? { status: 'available', executable: resolvedChrome }
      : { status: 'unavailable', executable: null },
  });

  if (!resolvedChrome) {
    receipt.status = 'skipped';
    receipt.containment.status = 'skipped';
    receipt.readability.status = 'skipped';
    receipt.viewerChrome.status = 'skipped';
    receipt.captures.status = 'skipped';
    receipt.error = 'Chrome or Chromium is unavailable. Set ARCHIFY_CHROME to its executable path.';
    receipt.diagnostics = [failureDiagnostic({
      code: 'viewer/chrome-unavailable',
      severity: 'warning',
      message: receipt.error,
      subject: { artifact },
      evidence: { executable: null },
      supportedFixes: ['set ARCHIFY_CHROME to a Chrome or Chromium executable and rerun visual-check'],
    })];
    persistReceipt(outputs, receipt);
    return { exitCode: EXIT.skipped, receipt };
  }

  let browser;
  try {
    browser = await browserFactory(resolvedChrome);
    const observations = new Map();
    const screenshotsByKey = new Map(outputs.screenshots.map((entry) => [
      screenshotKey(entry.width, entry.height, entry.theme),
      entry,
    ]));

    for (const viewport of VISUAL_CHECK_VIEWPORTS) {
      const key = screenshotKey(viewport.width, viewport.height, 'light');
      const screenshot = screenshotsByKey.get(key);
      const metrics = await browser.inspect({
        artifactPath: artifact,
        ...viewport,
        theme: 'light',
        ...(screenshot ? { screenshotPath: screenshot.path } : {}),
      });
      observations.set(key, observation({ ...viewport, theme: 'light', metrics }));
    }
    for (const viewport of CAPTURE_VIEWPORTS) {
      const key = screenshotKey(viewport.width, viewport.height, 'dark');
      const screenshot = screenshotsByKey.get(key);
      const metrics = await browser.inspect({
        artifactPath: artifact,
        ...viewport,
        theme: 'dark',
        screenshotPath: screenshot.path,
      });
      observations.set(key, observation({ ...viewport, theme: 'dark', metrics }));
    }

    const afterBytes = fs.readFileSync(artifact);
    if (sha256(afterBytes) !== receipt.artifact.sha256 || afterBytes.byteLength !== receipt.artifact.bytes) {
      throw new Error('The delivered artifact changed while visual-check was running.');
    }

    receipt.containment.viewports = VISUAL_CHECK_VIEWPORTS.map(({ width, height }) => (
      observations.get(screenshotKey(width, height, 'light'))
    ));
    receipt.readability.viewports = receipt.containment.viewports.map((entry) => ({ ...entry }));
    receipt.viewerChrome.viewports = receipt.containment.viewports.map((entry) => ({ ...entry }));
    receipt.captures.screenshots = outputs.screenshots.map((entry) => ({
      ...observations.get(screenshotKey(entry.width, entry.height, entry.theme)),
      file: path.basename(entry.path),
    }));
    const allObservations = [...observations.values()];
    const containmentPass = allObservations.every((entry) => entry.ok);
    const readabilityPass = receipt.readability.viewports.every((entry) => entry.readabilityOk);
    const viewerChromePass = allObservations.every((entry) => entry.viewerChromeOk);
    receipt.diagnostics = observationDiagnostics({
      artifact,
      allObservations,
      readabilityObservations: receipt.readability.viewports,
    });
    receipt.containment.status = containmentPass ? 'pass' : 'fail';
    receipt.readability.status = readabilityPass ? 'pass' : 'fail';
    receipt.viewerChrome.status = viewerChromePass ? 'pass' : 'fail';
    receipt.captures.status = 'pass';
    receipt.captures.contactSheet = path.basename(outputs.contactSheet);
    receipt.status = containmentPass && readabilityPass && viewerChromePass ? 'pass' : 'fail';
    receipt.ok = containmentPass && readabilityPass && viewerChromePass;
    writeAtomic(outputs.contactSheet, contactSheetHtml({
      artifactPath: artifact,
      receipt,
      screenshots: receipt.captures.screenshots,
    }));
    persistReceipt(outputs, receipt);
    return { exitCode: receipt.ok ? EXIT.pass : EXIT.fail, receipt };
  } catch (error) {
    cleanupCaptureSidecars(outputs);
    receipt.status = 'fail';
    receipt.ok = false;
    receipt.error = error.message;
    receipt.containment.status = 'fail';
    receipt.readability.status = 'fail';
    receipt.viewerChrome.status = 'fail';
    receipt.captures.status = 'fail';
    receipt.captures.screenshots = [];
    receipt.captures.contactSheet = null;
    receipt.diagnostics = [failureDiagnostic({
      code: 'viewer/visual-check-runtime',
      message: 'visual-check could not complete its Chrome inspection.',
      subject: { artifact },
      evidence: { reason: error.message },
      supportedFixes: ['resolve the reported Chrome inspection error, then rerun visual-check'],
    })];
    persistReceipt(outputs, receipt);
    return { exitCode: EXIT.fail, receipt };
  } finally {
    if (browser?.close) await browser.close();
  }
}
```

## brand-marks

```

```

## brand-marks/README.md

# Built-in brand marks

Archify ships a bounded catalogue of 107 commonly used brands for architecture,
workflow, sequence, data-flow, and lifecycle nodes. The mark is optional authored
identity: it never replaces the node's semantic `type`, color, label, or
relationships.

Unknown sites are handled by an explicit two-stage workflow. Run
`node bin/archify.mjs brands capture <url> --json`, then author the returned
digest-pinned `brand` value. Normal render and validate commands do not perform
an unpinned capture, and changed or unavailable content fails closed.

Most vector paths and brand metadata are generated from Simple Icons 16.28.0.
The OpenAI mark is traced to OpenAI's official brand guidelines. Every generated
entry records its source and, when available upstream, its guidelines and license
metadata in `renderers/shared/generated-brand-marks.mjs`.

Brand names and logos may be trademarks of their respective owners. Simple
Icons' CC0 license covers its collection work, not every underlying trademark or
artwork. Contributors must review the recorded source, current brand guidelines,
and intended referential use before adding or updating a mark. Archify does not
imply sponsorship, endorsement, or partnership.

Edit `catalog.json`, then regenerate the committed zero-runtime-dependency bundle:

```bash
npm run generate:brand-marks
npm run check:brand-marks
```

Do not hand-edit `renderers/shared/generated-brand-marks.mjs`.

## brand-marks/catalog.json

```json
{
  "schemaVersion": 1,
  "marks": [
    {
      "id": "openai",
      "title": "OpenAI",
      "category": "ai",
      "aliases": ["chatgpt", "gpt", "codex"],
      "domains": ["openai.com", "chatgpt.com"],
      "custom": {
        "viewBox": 20,
        "hex": "000000",
        "path": "M11.248 18.25q-.825 0-1.568-.314a4.3 4.3 0 0 1-1.32-.874 4 4 0 0 1-1.304.214 4 4 0 0 1-2.046-.544 4.27 4.27 0 0 1-1.518-1.485 4 4 0 0 1-.56-2.095q0-.48.131-1.04A4.4 4.4 0 0 1 2.04 10.71a4.07 4.07 0 0 1 .017-3.4 4.2 4.2 0 0 1 1.056-1.418 3.8 3.8 0 0 1 1.6-.842 3.9 3.9 0 0 1 .76-1.683q.593-.759 1.451-1.188a4.04 4.04 0 0 1 1.832-.429q.825 0 1.567.313.742.314 1.32.875a4 4 0 0 1 1.304-.215q1.106 0 2.046.545a4.14 4.14 0 0 1 1.501 1.485q.578.941.578 2.095 0 .48-.132 1.04.66.61 1.023 1.419.363.792.363 1.666 0 .892-.38 1.717a4.3 4.3 0 0 1-1.072 1.435 3.8 3.8 0 0 1-1.584.825 3.8 3.8 0 0 1-.775 1.683 4.06 4.06 0 0 1-1.436 1.188 4.04 4.04 0 0 1-1.832.429m-4.076-2.062q.825 0 1.435-.347l3.103-1.782a.36.36 0 0 0 .164-.313v-1.42L7.881 14.62a.67.67 0 0 1-.726 0l-3.118-1.798a.5.5 0 0 1-.017.115v.198q0 .841.396 1.551.413.693 1.139 1.089a3.2 3.2 0 0 0 1.617.412m.165-2.69a.4.4 0 0 0 .181.05q.083 0 .165-.05l1.238-.71-3.977-2.31a.7.7 0 0 1-.363-.643v-3.58q-.825.362-1.32 1.122a2.9 2.9 0 0 0-.495 1.65q0 .809.413 1.55.412.743 1.072 1.123zm3.91 3.663q.875 0 1.585-.396a2.96 2.96 0 0 0 1.534-2.64v-3.564a.32.32 0 0 0-.165-.297l-1.254-.726v4.604a.7.7 0 0 1-.363.643l-3.119 1.799a3 3 0 0 0 1.783.577m.627-6.039V8.878L10.01 7.822 8.129 8.878v2.244l1.881 1.056zM7.057 5.859a.7.7 0 0 1 .363-.644l3.119-1.798a3 3 0 0 0-1.782-.578q-.874 0-1.584.396A2.96 2.96 0 0 0 6.05 4.324a3.07 3.07 0 0 0-.396 1.551v3.547q0 .199.165.314l1.237.726zm8.383 7.887q.825-.364 1.303-1.123.495-.758.495-1.65a3.15 3.15 0 0 0-.412-1.55q-.413-.743-1.073-1.123l-3.086-1.782q-.099-.065-.181-.049a.3.3 0 0 0-.165.05l-1.238.692 3.993 2.327a.6.6 0 0 1 .264.264.64.64 0 0 1 .1.363zm-3.317-8.382a.63.63 0 0 1 .726 0l3.135 1.831v-.297q0-.792-.396-1.501a2.86 2.86 0 0 0-1.105-1.155q-.71-.43-1.65-.43-.825 0-1.436.347L8.294 5.941a.36.36 0 0 0-.165.314v1.418z",
        "source": "https://openai.com/brand/",
        "guidelines": "https://openai.com/brand/"
      }
    },
    { "id": "claude", "category": "ai", "simpleIcon": "claude", "aliases": ["claude-ai"], "domains": ["claude.ai"] },
    { "id": "anthropic", "category": "ai", "simpleIcon": "anthropic", "domains": ["anthropic.com"] },
    { "id": "google-gemini", "category": "ai", "simpleIcon": "googlegemini", "aliases": ["gemini"], "domains": ["gemini.google.com"] },
    { "id": "deepseek", "category": "ai", "simpleIcon": "deepseek", "domains": ["deepseek.com"] },
    { "id": "qwen", "category": "ai", "simpleIcon": "qwen", "domains": ["qwen.ai"] },
    { "id": "meta", "category": "ai", "simpleIcon": "meta", "aliases": ["llama"], "domains": ["meta.com"] },
    { "id": "mistral-ai", "category": "ai", "simpleIcon": "mistralai", "aliases": ["mistral"], "domains": ["mistral.ai"] },
    { "id": "hugging-face", "category": "ai", "simpleIcon": "huggingface", "aliases": ["huggingface"], "domains": ["huggingface.co"] },
    { "id": "ollama", "category": "ai", "simpleIcon": "ollama", "domains": ["ollama.com"] },
    { "id": "openrouter", "category": "ai", "simpleIcon": "openrouter", "aliases": ["open-router"], "domains": ["openrouter.ai"] },
    { "id": "perplexity", "category": "ai", "simpleIcon": "perplexity", "domains": ["perplexity.ai"] },
    { "id": "replicate", "category": "ai", "simpleIcon": "replicate", "domains": ["replicate.com"] },

    { "id": "google-cloud", "category": "cloud", "simpleIcon": "googlecloud", "aliases": ["gcp", "googlecloud"], "domains": ["cloud.google.com"] },
    { "id": "cloudflare", "category": "cloud", "simpleIcon": "cloudflare", "domains": ["cloudflare.com"] },
    { "id": "vercel", "category": "cloud", "simpleIcon": "vercel", "domains": ["vercel.com"] },
    { "id": "netlify", "category": "cloud", "simpleIcon": "netlify", "domains": ["netlify.com"] },
    { "id": "digitalocean", "category": "cloud", "simpleIcon": "digitalocean", "aliases": ["digital-ocean"], "domains": ["digitalocean.com"] },
    { "id": "render", "category": "cloud", "simpleIcon": "render", "domains": ["render.com"] },
    { "id": "railway", "category": "cloud", "simpleIcon": "railway", "domains": ["railway.com", "railway.app"] },
    { "id": "fly-io", "category": "cloud", "simpleIcon": "flydotio", "aliases": ["fly.io"], "domains": ["fly.io"] },
    { "id": "cloudinary", "category": "cloud", "simpleIcon": "cloudinary", "domains": ["cloudinary.com"] },
    { "id": "alibaba-cloud", "category": "cloud", "simpleIcon": "alibabacloud", "aliases": ["aliyun"], "domains": ["alibabacloud.com", "aliyun.com"] },
    { "id": "firebase", "category": "cloud", "simpleIcon": "firebase", "domains": ["firebase.google.com"] },
    { "id": "supabase", "category": "cloud", "simpleIcon": "supabase", "domains": ["supabase.com"] },
    { "id": "neon", "category": "cloud", "simpleIcon": "neon", "domains": ["neon.tech"] },

    { "id": "github", "category": "engineering", "simpleIcon": "github", "domains": ["github.com"] },
    { "id": "gitlab", "category": "engineering", "simpleIcon": "gitlab", "domains": ["gitlab.com"] },
    { "id": "bitbucket", "category": "engineering", "simpleIcon": "bitbucket", "domains": ["bitbucket.org"] },
    { "id": "docker", "category": "engineering", "simpleIcon": "docker", "domains": ["docker.com"] },
    { "id": "kubernetes", "category": "engineering", "simpleIcon": "kubernetes", "aliases": ["k8s"], "domains": ["kubernetes.io"] },
    { "id": "terraform", "category": "engineering", "simpleIcon": "terraform", "domains": ["terraform.io"] },
    { "id": "pulumi", "category": "engineering", "simpleIcon": "pulumi", "domains": ["pulumi.com"] },
    { "id": "ansible", "category": "engineering", "simpleIcon": "ansible", "domains": ["ansible.com"] },
    { "id": "jenkins", "category": "engineering", "simpleIcon": "jenkins", "domains": ["jenkins.io"] },
    { "id": "circleci", "category": "engineering", "simpleIcon": "circleci", "aliases": ["circle-ci"], "domains": ["circleci.com"] },
    { "id": "github-actions", "category": "engineering", "simpleIcon": "githubactions" },
    { "id": "argo", "category": "engineering", "simpleIcon": "argo", "aliases": ["argocd", "argo-cd"], "domains": ["argoproj.github.io"] },
    { "id": "helm", "category": "engineering", "simpleIcon": "helm", "domains": ["helm.sh"] },
    { "id": "grafana", "category": "engineering", "simpleIcon": "grafana", "domains": ["grafana.com"] },
    { "id": "prometheus", "category": "engineering", "simpleIcon": "prometheus", "domains": ["prometheus.io"] },
    { "id": "sentry", "category": "engineering", "simpleIcon": "sentry", "domains": ["sentry.io"] },
    { "id": "datadog", "category": "engineering", "simpleIcon": "datadog", "domains": ["datadoghq.com"] },
    { "id": "pagerduty", "category": "engineering", "simpleIcon": "pagerduty", "aliases": ["pager-duty"], "domains": ["pagerduty.com"] },

    { "id": "postgresql", "category": "data", "simpleIcon": "postgresql", "aliases": ["postgres"], "domains": ["postgresql.org"] },
    { "id": "mysql", "category": "data", "simpleIcon": "mysql", "domains": ["mysql.com"] },
    { "id": "mongodb", "category": "data", "simpleIcon": "mongodb", "aliases": ["mongo"], "domains": ["mongodb.com"] },
    { "id": "redis", "category": "data", "simpleIcon": "redis", "domains": ["redis.io"] },
    { "id": "apache-kafka", "category": "data", "simpleIcon": "apachekafka", "aliases": ["kafka"], "domains": ["kafka.apache.org"] },
    { "id": "rabbitmq", "category": "data", "simpleIcon": "rabbitmq", "aliases": ["rabbit-mq"], "domains": ["rabbitmq.com"] },
    { "id": "clickhouse", "category": "data", "simpleIcon": "clickhouse", "domains": ["clickhouse.com"] },
    { "id": "elasticsearch", "category": "data", "simpleIcon": "elasticsearch", "aliases": ["elastic"], "domains": ["elastic.co"] },
    { "id": "opensearch", "category": "data", "simpleIcon": "opensearch", "aliases": ["open-search"], "domains": ["opensearch.org"] },
    { "id": "snowflake", "category": "data", "simpleIcon": "snowflake", "domains": ["snowflake.com"] },
    { "id": "databricks", "category": "data", "simpleIcon": "databricks", "domains": ["databricks.com"] },
    { "id": "planetscale", "category": "data", "simpleIcon": "planetscale", "aliases": ["planet-scale"], "domains": ["planetscale.com"] },
    { "id": "prisma", "category": "data", "simpleIcon": "prisma", "domains": ["prisma.io"] },
    { "id": "sqlite", "category": "data", "simpleIcon": "sqlite", "domains": ["sqlite.org"] },
    { "id": "mariadb", "category": "data", "simpleIcon": "mariadb", "aliases": ["maria-db"], "domains": ["mariadb.org"] },
    { "id": "influxdb", "category": "data", "simpleIcon": "influxdb", "aliases": ["influx-db"], "domains": ["influxdata.com"] },
    { "id": "apache-airflow", "category": "data", "simpleIcon": "apacheairflow", "aliases": ["airflow"], "domains": ["airflow.apache.org"] },

    { "id": "notion", "category": "collaboration", "simpleIcon": "notion", "domains": ["notion.so"] },
    { "id": "figma", "category": "collaboration", "simpleIcon": "figma", "domains": ["figma.com"] },
    { "id": "jira", "category": "collaboration", "simpleIcon": "jira", "domains": ["atlassian.com"] },
    { "id": "linear", "category": "collaboration", "simpleIcon": "linear", "domains": ["linear.app"] },
    { "id": "discord", "category": "collaboration", "simpleIcon": "discord", "domains": ["discord.com"] },
    { "id": "zoom", "category": "collaboration", "simpleIcon": "zoom", "domains": ["zoom.us"] },
    { "id": "trello", "category": "collaboration", "simpleIcon": "trello", "domains": ["trello.com"] },
    { "id": "asana", "category": "collaboration", "simpleIcon": "asana", "domains": ["asana.com"] },
    { "id": "airtable", "category": "collaboration", "simpleIcon": "airtable", "domains": ["airtable.com"] },
    { "id": "miro", "category": "collaboration", "simpleIcon": "miro", "domains": ["miro.com"] },
    { "id": "stripe", "category": "business", "simpleIcon": "stripe", "domains": ["stripe.com"] },
    { "id": "shopify", "category": "business", "simpleIcon": "shopify", "domains": ["shopify.com"] },
    { "id": "hubspot", "category": "business", "simpleIcon": "hubspot", "domains": ["hubspot.com"] },
    { "id": "paypal", "category": "business", "simpleIcon": "paypal", "domains": ["paypal.com"] },
    { "id": "intercom", "category": "business", "simpleIcon": "intercom", "domains": ["intercom.com"] },
    { "id": "zendesk", "category": "business", "simpleIcon": "zendesk", "domains": ["zendesk.com"] },
    { "id": "wordpress", "category": "business", "simpleIcon": "wordpress", "domains": ["wordpress.org", "wordpress.com"] },
    { "id": "woocommerce", "category": "business", "simpleIcon": "woocommerce", "aliases": ["woo-commerce"], "domains": ["woocommerce.com"] },

    { "id": "wechat", "category": "channel", "simpleIcon": "wechat", "aliases": ["weixin", "微信"], "domains": ["weixin.qq.com"] },
    { "id": "youtube", "category": "channel", "simpleIcon": "youtube", "domains": ["youtube.com", "youtu.be"] },
    { "id": "tiktok", "category": "channel", "simpleIcon": "tiktok", "aliases": ["douyin", "抖音"], "domains": ["tiktok.com", "douyin.com"] },
    { "id": "x", "category": "channel", "simpleIcon": "x", "aliases": ["twitter"], "domains": ["x.com", "twitter.com"] },
    { "id": "instagram", "category": "channel", "simpleIcon": "instagram", "domains": ["instagram.com"] },
    { "id": "facebook", "category": "channel", "simpleIcon": "facebook", "domains": ["facebook.com"] },
    { "id": "reddit", "category": "channel", "simpleIcon": "reddit", "domains": ["reddit.com"] },
    { "id": "telegram", "category": "channel", "simpleIcon": "telegram", "domains": ["telegram.org", "t.me"] },
    { "id": "whatsapp", "category": "channel", "simpleIcon": "whatsapp", "domains": ["whatsapp.com"] },
    { "id": "pinterest", "category": "channel", "simpleIcon": "pinterest", "domains": ["pinterest.com"] },

    { "id": "python", "category": "language", "simpleIcon": "python", "domains": ["python.org"] },
    { "id": "typescript", "category": "language", "simpleIcon": "typescript", "aliases": ["ts"], "domains": ["typescriptlang.org"] },
    { "id": "javascript", "category": "language", "simpleIcon": "javascript", "aliases": ["js"] },
    { "id": "go", "category": "language", "simpleIcon": "go", "aliases": ["golang"], "domains": ["go.dev"] },
    { "id": "rust", "category": "language", "simpleIcon": "rust", "domains": ["rust-lang.org"] },
    { "id": "node-js", "category": "framework", "simpleIcon": "nodedotjs", "aliases": ["node", "nodejs"], "domains": ["nodejs.org"] },
    { "id": "react", "category": "framework", "simpleIcon": "react", "aliases": ["reactjs"], "domains": ["react.dev"] },
    { "id": "vue", "category": "framework", "simpleIcon": "vuedotjs", "aliases": ["vuejs", "vue.js"], "domains": ["vuejs.org"] },
    { "id": "next-js", "category": "framework", "simpleIcon": "nextdotjs", "aliases": ["nextjs", "next.js"], "domains": ["nextjs.org"] },
    { "id": "pytorch", "category": "framework", "simpleIcon": "pytorch", "domains": ["pytorch.org"] },
    { "id": "tensorflow", "category": "framework", "simpleIcon": "tensorflow", "domains": ["tensorflow.org"] },
    { "id": "angular", "category": "framework", "simpleIcon": "angular", "domains": ["angular.dev"] },
    { "id": "svelte", "category": "framework", "simpleIcon": "svelte", "domains": ["svelte.dev"] },
    { "id": "django", "category": "framework", "simpleIcon": "django", "domains": ["djangoproject.com"] },
    { "id": "flask", "category": "framework", "simpleIcon": "flask", "domains": ["palletsprojects.com"] },
    { "id": "fastapi", "category": "framework", "simpleIcon": "fastapi", "domains": ["fastapi.tiangolo.com"] },
    { "id": "spring", "category": "framework", "simpleIcon": "spring", "aliases": ["spring-boot"], "domains": ["spring.io"] },
    { "id": "dotnet", "category": "framework", "simpleIcon": "dotnet", "aliases": [".net"], "domains": ["dotnet.microsoft.com"] }
  ]
}
```

## delta

```

```

## delta/architecture-delta.mjs

```js
import { parseRepositoryRemote } from '../renderers/shared/repository-location.mjs';

const COMPARATOR_VERSION = 1;
const CANONICAL_VERSION = 1;

export class ArchitectureDeltaError extends Error {
  constructor(code, message, details = {}) {
    super(message);
    this.name = 'ArchitectureDeltaError';
    this.code = code;
    this.details = details;
  }
}

const codepointOrder = (left, right) => (left < right ? -1 : left > right ? 1 : 0);
const sorted = (values) => [...values].sort((left, right) => codepointOrder(String(left), String(right)));

function canonical(value) {
  if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
  if (value && typeof value === 'object') {
    return `{${Object.keys(value).sort(codepointOrder).map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
  }
  return JSON.stringify(value);
}

const equal = (left, right) => canonical(left) === canonical(right);

function sortedObjects(values) {
  return [...values].sort((left, right) => codepointOrder(canonical(left), canonical(right)));
}

function sortedBy(values, keyFor) {
  return [...values].sort((left, right) => codepointOrder(String(keyFor(left)), String(keyFor(right))));
}

function normalizeRepository(repository) {
  if (!repository) return undefined;
  const location = parseRepositoryRemote(repository.url, { authored: true });
  const url = location?.url || String(repository.url || '');
  return {
    url: location?.provider === 'github' ? url.toLowerCase() : url,
    revision: String(repository.revision || '').toLowerCase(),
    ...(repository.provider !== undefined ? { provider: repository.provider } : {}),
    ...(repository.link_mode !== undefined ? { link_mode: repository.link_mode } : {}),
  };
}

function normalizeComponent(component) {
  return {
    ...component,
    ...(Array.isArray(component.sources) ? { sources: sortedObjects(component.sources) } : {}),
  };
}

function normalizeBoundary(boundary) {
  return { ...boundary, wraps: sorted(boundary.wraps || []) };
}

export function canonicalArchitecture(diagram) {
  const meta = { ...(diagram.meta || {}) };
  delete meta.output;
  if (meta.repository) meta.repository = normalizeRepository(meta.repository);
  return {
    schema_version: diagram.schema_version,
    diagram_type: diagram.diagram_type,
    meta,
    ...(diagram.layout ? { layout: diagram.layout } : {}),
    components: sortedBy((diagram.components || []).map(normalizeComponent), (component) => component.id),
    boundaries: sortedBy((diagram.boundaries || []).map(normalizeBoundary), boundaryKey),
    connections: sortedBy(diagram.connections || [], (connection) => connection.id || ''),
    ...(diagram.cards ? { cards: diagram.cards } : {}),
  };
}

export function canonicalArchitectureJson(diagram) {
  return canonical(canonicalArchitecture(diagram));
}

function fail(code, message, details) {
  throw new ArchitectureDeltaError(code, message, details);
}

function requireComparableShape(diagram, side) {
  if (diagram?.schema_version !== 1) {
    fail('delta/schema-version-mismatch', `${side} must use schema_version 1.`, { side, path: '/schema_version', actual: diagram?.schema_version });
  }
  if (diagram?.diagram_type !== 'architecture') {
    fail('delta/type-mismatch', `${side} must use diagram_type architecture.`, { side, path: '/diagram_type', actual: diagram?.diagram_type });
  }
}

function stableIndex(items, collection, side, missingCode = 'delta/stable-id-required') {
  const index = new Map();
  const missing = [];
  const duplicates = [];
  (items || []).forEach((item, itemIndex) => {
    if (!item?.id) missing.push(`/${collection}/${itemIndex}/id`);
    else if (index.has(item.id)) duplicates.push(item.id);
    else index.set(item.id, item);
  });
  if (missing.length) {
    fail(missingCode, `${side} ${collection} require authored stable ids for comparison.`, {
      side,
      paths: sorted(missing),
      supportedFixes: [`add a unique id to every ${collection} item`],
    });
  }
  if (duplicates.length) {
    fail('delta/duplicate-stable-id', `${side} ${collection} contain duplicate ids.`, {
      side,
      collection,
      ids: sorted(new Set(duplicates)),
      supportedFixes: [`make every ${collection} id unique`],
    });
  }
  return index;
}

const boundaryKey = (boundary) => `${boundary.kind}\u001f${boundary.label}`;

function boundaryIndex(boundaries, side) {
  const index = new Map();
  const ambiguous = [];
  for (const boundary of boundaries || []) {
    const key = boundaryKey(boundary);
    if (index.has(key)) ambiguous.push(`${boundary.kind}:${boundary.label}`);
    else index.set(key, boundary);
  }
  if (ambiguous.length) {
    fail('delta/boundary-key-ambiguous', `${side} boundary kind + label keys must be unique.`, {
      side,
      boundaries: sorted(new Set(ambiguous)),
      supportedFixes: ['rename one duplicate boundary or add stable boundary ids in a future schema version'],
    });
  }
  return index;
}

function normalizedField(item, field) {
  const value = item?.[field];
  if (field === 'sources' && Array.isArray(value)) return sortedObjects(value);
  if (field === 'wraps' && Array.isArray(value)) return sorted(value);
  return value;
}

function fieldChanges(before, after, groups) {
  const classifications = [];
  const changedFields = [];
  for (const [classification, fields] of Object.entries(groups)) {
    const changed = fields.filter((field) => !equal(normalizedField(before, field), normalizedField(after, field)));
    if (changed.length) classifications.push(classification);
    changedFields.push(...changed.map((field) => `/${field}`));
  }
  return { classifications: sorted(classifications), changedFields: sorted(changedFields) };
}

const COMPONENT_FIELDS = {
  semantic: ['type', 'label', 'sublabel', 'tag'],
  evidence: ['sources'],
  geometry: ['row', 'col', 'pos', 'size'],
};
const CONNECTION_FIELDS = {
  topology: ['from', 'to'],
  semantic: ['label', 'variant'],
  geometry: ['fromSide', 'toSide', 'route', 'via', 'labelAt', 'labelDx', 'labelDy', 'labelSegment', 'width'],
};
const BOUNDARY_FIELDS = { scope: ['wraps'], geometry: ['pad'] };

function statusFor(classifications, kind) {
  if (classifications.some((value) => ['topology', 'semantic', 'scope'].includes(value))) return 'changed';
  if (classifications.includes('evidence')) return 'evidence-changed';
  if (classifications.includes('geometry')) return kind === 'connection' ? 'rerouted' : kind === 'component' ? 'moved' : 'geometry-changed';
  return 'same';
}

function compareEntities(baseIndex, headIndex, kind, groups, describe) {
  const changes = [];
  const identityClassification = kind === 'connection' ? 'topology' : kind === 'boundary' ? 'scope' : 'semantic';
  for (const id of sorted(new Set([...baseIndex.keys(), ...headIndex.keys()]))) {
    const base = baseIndex.get(id);
    const head = headIndex.get(id);
    if (!base) changes.push({ ...describe(id, undefined, head), status: 'added', classifications: [identityClassification], changedFields: [] });
    else if (!head) changes.push({ ...describe(id, base, undefined), status: 'removed', classifications: [identityClassification], changedFields: [] });
    else {
      const fields = fieldChanges(base, head, groups);
      const status = statusFor(fields.classifications, kind);
      if (status !== 'same') changes.push({ ...describe(id, base, head), status, ...fields });
    }
  }
  return changes;
}

function summaryFor(changes, shape) {
  const summary = Object.fromEntries(shape.map((key) => [key, 0]));
  for (const change of changes) {
    const key = change.status.replace(/-([a-z])/g, (_all, letter) => letter.toUpperCase());
    if (Object.hasOwn(summary, key)) summary[key] += 1;
  }
  return summary;
}

function presentationChanged(base, head) {
  const basePresentation = {
    title: base.meta?.title,
    subtitle: base.meta?.subtitle,
    animation: base.meta?.animation,
    visual_preset: base.meta?.visual_preset,
    quality_profile: base.meta?.quality_profile,
    engineering_profile: base.meta?.engineering_profile,
    legend: base.meta?.legend,
    views: base.meta?.views,
    viewBox: base.meta?.viewBox,
    layout: base.layout,
    cards: base.cards,
  };
  const headPresentation = {
    title: head.meta?.title,
    subtitle: head.meta?.subtitle,
    animation: head.meta?.animation,
    visual_preset: head.meta?.visual_preset,
    quality_profile: head.meta?.quality_profile,
    engineering_profile: head.meta?.engineering_profile,
    legend: head.meta?.legend,
    views: head.meta?.views,
    viewBox: head.meta?.viewBox,
    layout: head.layout,
    cards: head.cards,
  };
  return !equal(basePresentation, headPresentation);
}

export function compareArchitecture(base, head, evidence = {}) {
  requireComparableShape(base, 'base');
  requireComparableShape(head, 'head');
  const baseComponents = stableIndex(base.components, 'components', 'base');
  const headComponents = stableIndex(head.components, 'components', 'head');
  const shared = sorted([...baseComponents.keys()].filter((id) => headComponents.has(id)));
  if (!shared.length) {
    fail('delta/no-shared-component-id', 'The snapshots share no component id, so Archify cannot prove that they describe the same system.', {
      supportedFixes: ['preserve at least one authored component id across snapshots'],
    });
  }

  const baseConnections = stableIndex(base.connections, 'connections', 'base', 'delta/relationship-id-required');
  const headConnections = stableIndex(head.connections, 'connections', 'head', 'delta/relationship-id-required');
  const baseBoundaries = boundaryIndex(base.boundaries, 'base');
  const headBoundaries = boundaryIndex(head.boundaries, 'head');

  const baseRepository = normalizeRepository(base.meta?.repository);
  const headRepository = normalizeRepository(head.meta?.repository);
  const identity = (repository) => parseRepositoryRemote(repository.url, { authored: true })?.identity || repository.url;
  if (baseRepository && headRepository && identity(baseRepository) !== identity(headRepository)) {
    fail('delta/repository-mismatch', 'The snapshots name different repositories.', {
      baseRepository: baseRepository.url,
      headRepository: headRepository.url,
      supportedFixes: ['compare snapshots from the same repository or remove repository evidence from both inputs'],
    });
  }
  const proofLevel = baseRepository && headRepository
    && evidence.baseVerified && evidence.headVerified
    && /^[a-f0-9]{40}$/.test(baseRepository.revision)
    && /^[a-f0-9]{40}$/.test(headRepository.revision)
    ? 'revision-pinned'
    : 'authored';

  const components = compareEntities(baseComponents, headComponents, 'component', COMPONENT_FIELDS, (id, before, after) => ({
    id,
    baseLabel: before?.label,
    headLabel: after?.label,
  }));
  const connections = compareEntities(baseConnections, headConnections, 'connection', CONNECTION_FIELDS, (id, before, after) => ({
    id,
    ...(before ? { base: { from: before.from, to: before.to, label: before.label || '' } } : {}),
    ...(after ? { head: { from: after.from, to: after.to, label: after.label || '' } } : {}),
  }));
  const boundaries = compareEntities(baseBoundaries, headBoundaries, 'boundary', BOUNDARY_FIELDS, (_key, before, after) => ({
    key: `${(after || before).kind}:${(after || before).label}`,
    kind: (after || before).kind,
    label: (after || before).label,
  }));
  const provenanceChanged = !equal(baseRepository, headRepository);

  return {
    schemaVersion: 1,
    ok: true,
    command: 'compare',
    type: 'architecture',
    comparatorVersion: COMPARATOR_VERSION,
    canonicalVersion: CANONICAL_VERSION,
    completeness: 'complete',
    proofLevel,
    base: {
      title: base.meta?.title || '',
      ...(evidence.baseRawSha256 ? { rawSha256: evidence.baseRawSha256 } : {}),
      ...(evidence.baseSemanticSha256 ? { semanticSha256: evidence.baseSemanticSha256 } : {}),
      ...(Number.isInteger(evidence.baseBytes) ? { bytes: evidence.baseBytes } : {}),
      ...(baseRepository?.revision ? { revision: baseRepository.revision } : {}),
    },
    head: {
      title: head.meta?.title || '',
      ...(evidence.headRawSha256 ? { rawSha256: evidence.headRawSha256 } : {}),
      ...(evidence.headSemanticSha256 ? { semanticSha256: evidence.headSemanticSha256 } : {}),
      ...(Number.isInteger(evidence.headBytes) ? { bytes: evidence.headBytes } : {}),
      ...(headRepository?.revision ? { revision: headRepository.revision } : {}),
    },
    summary: {
      components: summaryFor(components, ['added', 'changed', 'evidenceChanged', 'removed', 'moved']),
      connections: summaryFor(connections, ['added', 'changed', 'removed', 'rerouted']),
      boundaries: summaryFor(boundaries, ['added', 'changed', 'removed', 'geometryChanged']),
      presentationChanged: presentationChanged(base, head),
      provenanceChanged,
    },
    changes: { components, connections, boundaries },
    identity: {
      components: 'components[].id',
      connections: 'connections[].id (required)',
      boundaries: 'boundaries[].kind + boundaries[].label (derived)',
    },
    view: { visualPreset: head.meta?.visual_preset || 'classic' },
    limitations: [
      'Authored Architecture IR only; no runtime impact, causality, risk, or mergeability is inferred.',
      'Boundary identity is conservatively derived from kind + label.',
    ],
  };
}

function esc(value) {
  return String(value ?? '').replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#39;');
}

const safeJson = (value) => JSON.stringify(value, null, 2).replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026');

export function extractArchitectureSvg(html) {
  const match = html.match(/<svg viewBox="0 0 [^"]+" role="img"[\s\S]*?<\/svg>/);
  if (!match) fail('delta/svg-missing', 'A validated Architecture artifact did not contain its primary SVG.');
  return match[0];
}

export function extractArtifactCss(html) {
  const match = html.match(/<style>([\s\S]*?)<\/style>/);
  if (!match) fail('delta/css-missing', 'A validated Architecture artifact did not contain its stylesheet.');
  return match[1];
}

function changeMap(changes) {
  return new Map(changes.map((change) => [change.id, change]));
}

function boundaryChangeMap(changes) {
  return new Map(changes.map((change) => [`${change.kind}:${esc(change.label)}`, change]));
}

function addState(tag, change, side, forcedState) {
  const append = (attributes) => tag.endsWith('/>')
    ? tag.replace(/\/>$/, `${attributes}/>`)
    : tag.replace(/>$/, `${attributes}>`);
  if (!change && !forcedState) return append(' data-delta-state="same"');
  let state = forcedState || change.status;
  if (change?.status === 'added' && side === 'base') state = 'same';
  if (change?.status === 'removed' && side === 'head') state = 'same';
  const classes = change?.classifications?.join(',') || '';
  return append(` data-delta-state="${esc(state)}"${classes ? ` data-delta-classifications="${esc(classes)}"` : ''}`);
}

function markerFor(state) {
  return ({ added: '+', removed: '−', changed: '~', moved: '↔', 'moved-from': '↔', rerouted: '↔', 'geometry-changed': '↔', 'evidence-changed': 'E' })[state] || '';
}

function addNodeMarker(group, state) {
  const symbol = markerFor(state);
  if (!symbol) return group;
  const box = group.match(/<rect x="([^"]+)" y="([^"]+)" width="([^"]+)"/);
  if (!box) return group;
  const x = Number(box[1]) + Number(box[3]) - 9;
  const y = Number(box[2]) + 9;
  return group.replace(/<\/g>$/, `\n          <g class="delta-node-marker" aria-hidden="true"><circle cx="${x}" cy="${y}" r="8"/><text x="${x}" y="${y + 3}" text-anchor="middle">${symbol}</text></g>\n        </g>`);
}

function prefixSvgIds(svg, prefix) {
  const ids = [...svg.matchAll(/\sid="([^"]+)"/g)].map((match) => match[1]);
  let result = svg.replace(/(\s)id="([^"]+)"/g, (_match, space, id) => `${space}id="${prefix}-${id}"`);
  for (const id of ids) {
    result = result.replaceAll(`url(#${id})`, `url(#${prefix}-${id})`).replaceAll(`href="#${id}"`, `href="#${prefix}-${id}"`);
  }
  result = result.replace(/aria-labelledby="([^"]+)"/g, (_match, value) => `aria-labelledby="${value.split(/\s+/).map((id) => `${prefix}-${id}`).join(' ')}"`);
  return result;
}

function staticize(svg) {
  return svg.replaceAll('tabindex="0" role="button"', 'role="group"').replaceAll(' aria-pressed="false"', '').replaceAll('aria-label="Focus ', 'aria-label="');
}

function nodeGroupRanges(svg) {
  const ranges = [];
  const opener = /<g\s+[^>]*\bdata-node-id="([^"]+)"[^>]*>/g;
  let open;
  while ((open = opener.exec(svg))) {
    const tags = /<\/?g\b[^>]*>/g;
    tags.lastIndex = open.index;
    let depth = 0;
    let tag;
    while ((tag = tags.exec(svg))) {
      depth += tag[0].startsWith('</') ? -1 : 1;
      if (depth === 0) {
        ranges.push({ id: open[1], start: open.index, end: tags.lastIndex });
        opener.lastIndex = tags.lastIndex;
        break;
      }
    }
  }
  return ranges;
}

function transformNodeGroups(svg, transform) {
  const ranges = nodeGroupRanges(svg);
  let cursor = 0;
  const parts = [];
  for (const range of ranges) {
    parts.push(svg.slice(cursor, range.start));
    parts.push(transform(svg.slice(range.start, range.end), range.id));
    cursor = range.end;
  }
  parts.push(svg.slice(cursor));
  return parts.join('');
}

const BOUNDARY_FRAME_RE = /<rect data-graph-role="structural-frame"[^>]*data-composition-frame-kind="([^"]+)"[^>]*data-composition-frame-label="([^"]+)"[^>]*\/>/g;
const BOUNDARY_LABEL_RE = /<g data-graph-role="structural-frame-label"[^>]*data-composition-frame-kind="([^"]+)"[^>]*data-composition-frame-label="([^"]+)"[^>]*>[\s\S]*?<\/g>/g;

function boundaryElements(svg) {
  const collect = (pattern, part) => [...svg.matchAll(pattern)].map((match) => ({
    start: match.index,
    end: match.index + match[0].length,
    markup: match[0],
    key: `${match[1]}:${match[2]}`,
    part,
  }));
  return [
    ...collect(BOUNDARY_FRAME_RE, 'frame'),
    ...collect(BOUNDARY_LABEL_RE, 'label'),
  ].sort((left, right) => left.start - right.start);
}

function transformBoundaryElements(svg, transform) {
  let cursor = 0;
  const parts = [];
  for (const element of boundaryElements(svg)) {
    parts.push(svg.slice(cursor, element.start));
    parts.push(transform(element.markup, element.key, element.part));
    cursor = element.end;
  }
  parts.push(svg.slice(cursor));
  return parts.join('');
}

export function annotateArchitectureSideSvg(svg, receipt, side) {
  const nodes = changeMap(receipt.changes.components);
  const edges = changeMap(receipt.changes.connections);
  const boundaries = boundaryChangeMap(receipt.changes.boundaries);
  let result = transformBoundaryElements(svg, (markup, key, part) => {
    const change = boundaries.get(key);
    if (!change) return markup;
    if (part === 'frame') {
      return addState(markup, change, side)
        .replace(/\/>$/, ` data-delta-boundary-key="${esc(change.key)}"/>`);
    }
    return markup.replace(
      /<text[^>]*>/,
      (tag) => tag.replace(/>$/, ` data-delta-state="${change.status}" data-delta-boundary-state="${change.status}" data-delta-boundary-key="${esc(change.key)}">`),
    );
  });
  result = transformNodeGroups(result, (group, id) => {
    const change = nodes.get(id);
    if ((side === 'base' && change?.status === 'added') || (side === 'head' && change?.status === 'removed')) return group;
    const tagged = group.replace(/^<g[^>]+>/, (tag) => addState(tag, change, side));
    return addNodeMarker(tagged, change?.status);
  });
  result = result.replace(/<(?:path|g)\s+[^>]*\bdata-edge-id="([^"]+)"[^>]*>/g, (tag, id) => addState(tag, edges.get(id), side));
  return prefixSvgIds(staticize(result), side);
}

function elementById(svg, kind, id) {
  const safe = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  if (kind === 'node') {
    const range = nodeGroupRanges(svg).find((candidate) => candidate.id === id);
    return range ? svg.slice(range.start, range.end) : '';
  }
  const path = svg.match(new RegExp(`<path\\s+[^>]*\\bdata-edge-id="${safe}"[^>]*/>`))?.[0] || '';
  const label = svg.match(new RegExp(`<g\\s+[^>]*\\bdata-edge-id="${safe}"[^>]*>[\\s\\S]*?<\\/g>`))?.[0] || '';
  return [path, label].filter(Boolean).join('\n');
}

function forceElementState(markup, state, classifications = []) {
  let result;
  if (markup.includes('data-node-id=')) {
    result = markup.replace(/^<g\s+[^>]*>/, (tag) => addState(tag, { classifications }, 'delta', state));
  } else {
    result = markup
      .replace(/<path\s+[^>]*\bdata-edge-id="[^"]+"[^>]*\/>/g, (tag) => addState(tag, { classifications }, 'delta', state))
      .replace(/<g\s+[^>]*\bdata-edge-id="[^"]+"[^>]*>/g, (tag) => addState(tag, { classifications }, 'delta', state));
  }
  result = result.replace(/\bid="node-/, 'id="base-node-');
  if (markup.includes('data-node-id=')) result = addNodeMarker(result, state);
  return result;
}

function boundaryMarkupByKey(svg, key) {
  return boundaryElements(svg)
    .filter((element) => element.key === key)
    .map((element) => element.markup)
    .join('\n');
}

function boundaryMarkupParts(markup) {
  const parts = { frame: '', label: '' };
  for (const element of boundaryElements(markup)) parts[element.part] = element.markup;
  return parts;
}

function forceBoundaryState(markup, state, key, classifications = []) {
  return markup
    .replace(/^<rect[^>]+\/>/, (tag) => addState(tag, { classifications }, 'delta', state).replace(/\/>$/, ` data-delta-boundary-key="${esc(key)}"/>`))
    .replace(
      /<rect data-graph-role="structural-frame-label-mask"[^>]*\/>/,
      (tag) => addState(tag, { classifications }, 'delta', state)
        .replace(/\/>$/, ` data-delta-boundary-state="${state}" data-delta-boundary-mask-key="${esc(key)}"/>`),
    )
    .replace(/<text[^>]*>/, (tag) => tag.replace(/>$/, ` data-delta-state="${state}" data-delta-boundary-state="${state}" data-delta-boundary-key="${esc(key)}">`));
}

function viewBoxSize(svg) {
  const match = svg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/);
  return match ? [Number(match[1]), Number(match[2])] : [0, 0];
}

function edgeSymbolMarkup(markup, state) {
  const symbol = markerFor(state);
  const point = markup.match(/data-composition-points="([\d.-]+),([\d.-]+)/);
  const edgeId = markup.match(/\bdata-edge-id="([^"]+)"/)?.[1];
  if (!symbol || !point) return '';
  return `<text class="delta-edge-marker" data-delta-state="${state}"${edgeId ? ` data-edge-id="${esc(edgeId)}"` : ''} x="${Number(point[1]) + 9}" y="${Number(point[2]) - 7}" aria-hidden="true">${symbol}</text>`;
}

function boundarySymbolMarkup(markup, state) {
  const symbol = markerFor(state);
  const frame = markup.match(/<rect[^>]*\bx="([\d.-]+)"\s+y="([\d.-]+)"\s+width="([\d.-]+)"/);
  if (!symbol || !frame) return '';
  const x = Number(frame[1]) + Number(frame[3]) - 12;
  const y = Number(frame[2]) + 16;
  return `<text class="delta-boundary-marker" data-delta-state="${state}" x="${x}" y="${y}" text-anchor="middle" aria-hidden="true">${symbol}</text>`;
}

export function buildDeltaSvg(baseSvg, headSvg, receipt) {
  const [baseW, baseH] = viewBoxSize(baseSvg);
  const [headW, headH] = viewBoxSize(headSvg);
  const nodes = changeMap(receipt.changes.components);
  const edges = changeMap(receipt.changes.connections);
  const boundaries = boundaryChangeMap(receipt.changes.boundaries);
  const baseNodePhantoms = [];
  const baseEdgePhantoms = [];
  const baseBoundaryFramePhantoms = [];
  const baseBoundaryLabelPhantoms = [];
  const edgeMarkers = [];
  const boundaryMarkers = [];

  for (const change of nodes.values()) {
    if (change.status === 'removed') baseNodePhantoms.push(forceElementState(elementById(baseSvg, 'node', change.id), 'removed', change.classifications));
    else if (change.classifications.includes('geometry')) baseNodePhantoms.push(forceElementState(elementById(baseSvg, 'node', change.id), 'moved-from', change.classifications));
  }
  for (const change of edges.values()) {
    if (change.status === 'removed' || change.classifications.includes('topology')) {
      const phantom = forceElementState(elementById(baseSvg, 'edge', change.id), 'removed', change.classifications);
      baseEdgePhantoms.push(phantom);
      edgeMarkers.push(edgeSymbolMarkup(phantom, 'removed'));
    } else if (change.classifications.includes('geometry')) {
      const phantom = forceElementState(elementById(baseSvg, 'edge', change.id), 'moved-from', change.classifications);
      baseEdgePhantoms.push(phantom);
      edgeMarkers.push(edgeSymbolMarkup(phantom, 'moved-from'));
    }
  }
  for (const change of boundaries.values()) {
    const renderedKey = `${change.kind}:${esc(change.label)}`;
    if (change.status === 'removed') {
      const phantom = forceBoundaryState(boundaryMarkupByKey(baseSvg, renderedKey), 'removed', change.key, change.classifications);
      const parts = boundaryMarkupParts(phantom);
      baseBoundaryFramePhantoms.push(parts.frame);
      baseBoundaryLabelPhantoms.push(parts.label);
      boundaryMarkers.push(boundarySymbolMarkup(phantom, 'removed'));
    }
    else if (change.status === 'changed' || change.status === 'geometry-changed') {
      const phantom = forceBoundaryState(boundaryMarkupByKey(baseSvg, renderedKey), 'moved-from', change.key, change.classifications);
      const parts = boundaryMarkupParts(phantom);
      baseBoundaryFramePhantoms.push(parts.frame);
      baseBoundaryLabelPhantoms.push(parts.label);
    }
  }

  let delta = annotateArchitectureSideSvg(headSvg, receipt, 'head');
  delta = delta.replace(/^<svg[^>]+>/, (tag) => tag.replace(/viewBox="[^"]+"/, `viewBox="0 0 ${Math.max(baseW, headW) + 24} ${Math.max(baseH, headH) + 24}"`));
  delta = delta.replace('        <!-- Boundaries (behind everything) -->', `        <!-- Baseline boundary frame phantoms -->\n${baseBoundaryFramePhantoms.filter(Boolean).join('\n')}\n\n        <!-- Boundaries (behind everything) -->`);
  delta = delta.replace('        <!-- Connection paths (before components for correct z-order) -->', `        <!-- Baseline relationship phantoms -->\n${baseEdgePhantoms.join('\n')}\n\n        <!-- Connection paths (before components for correct z-order) -->`);
  delta = delta.replace('        <!-- Components -->', `        <!-- Baseline boundary label phantoms (below current components) -->\n${baseBoundaryLabelPhantoms.filter(Boolean).join('\n')}\n\n        <!-- Baseline removed and move-from component phantoms -->\n${baseNodePhantoms.join('\n')}\n\n        <!-- Components -->`);

  for (const change of edges.values()) {
    if (change.status === 'added' || change.status === 'changed' || change.status === 'rerouted') {
      const current = elementById(delta, 'edge', change.id);
      edgeMarkers.push(edgeSymbolMarkup(current, change.status === 'changed' && change.classifications.includes('topology') ? 'added' : change.status));
    }
  }
  for (const change of boundaries.values()) {
    if (!['added', 'changed', 'geometry-changed'].includes(change.status)) continue;
    const renderedKey = `${change.kind}:${esc(change.label)}`;
    boundaryMarkers.push(boundarySymbolMarkup(boundaryMarkupByKey(delta, renderedKey), change.status));
  }
  delta = delta.replace('        <!-- Legend -->', `        <!-- Delta relationship symbols -->\n${edgeMarkers.filter(Boolean).join('\n')}\n\n        <!-- Delta boundary symbols -->\n${boundaryMarkers.filter(Boolean).join('\n')}\n\n        <!-- Legend -->`);
  return prefixSvgIds(staticize(delta), 'delta');
}

export function architectureDeltaChangeRows(receipt) {
  const rows = [];
  for (const change of receipt.changes.components) rows.push({ ...change, kind: 'Component', kindKey: 'component', key: `component:${change.id}`, id: change.id });
  for (const change of receipt.changes.connections) rows.push({ ...change, kind: 'Relationship', kindKey: 'relationship', key: `relationship:${change.id}`, id: change.id });
  for (const change of receipt.changes.boundaries) rows.push({ ...change, kind: 'Boundary', kindKey: 'boundary', key: `boundary:${change.key}`, id: change.key });
  return rows.sort((left, right) => codepointOrder(`${left.status}:${left.kind}:${left.id}`, `${right.status}:${right.kind}:${right.id}`));
}

function reviewPrimaryStates(row) {
  if (row.kindKey === 'component' && row.classifications.includes('geometry')) return [row.status, 'moved-from'];
  if (row.kindKey === 'relationship' && row.status === 'changed' && row.classifications.includes('topology')) return ['changed', 'removed'];
  if (row.kindKey === 'relationship' && row.classifications.includes('geometry')) return ['moved-from', row.status];
  if (row.kindKey === 'boundary' && ['changed', 'geometry-changed'].includes(row.status)) return [row.status, 'moved-from'].sort();
  return [row.status];
}

function reviewIdentity(row) {
  const attribute = row.kindKey === 'component' ? 'data-node-id' : row.kindKey === 'relationship' ? 'data-edge-id' : 'data-delta-boundary-key';
  return { attribute, value: esc(row.id) };
}

function reviewTargetTags(deltaMarkup, row) {
  const { attribute, value } = reviewIdentity(row);
  const safeValue = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const identity = new RegExp(`\\b${attribute}="${safeValue}"`);
  return [...deltaMarkup.matchAll(/<([a-z][\w:-]*)\s+[^>]*>/g)]
    .map((match) => ({ name: match[1], tag: match[0] }))
    .filter(({ tag }) => identity.test(tag));
}

function primaryReviewTags(deltaMarkup, row) {
  const tagName = row.kindKey === 'component' ? 'g' : row.kindKey === 'relationship' ? 'path' : 'rect';
  return reviewTargetTags(deltaMarkup, row).filter(({ name }) => name === tagName).map(({ tag }) => tag);
}

function reviewTargetSignature(tags) {
  return tags.map(({ name, tag }) => {
    const state = tag.match(/\bdata-delta-state="([^"]+)"/)?.[1] || '';
    const classifications = tag.match(/\bdata-delta-classifications="([^"]*)"/)?.[1] || '';
    return `${name}:${state}:${classifications}`;
  }).sort(codepointOrder).join('|');
}

function expectedReviewTargetSignature(row) {
  const classifications = row.classifications.join(',');
  const descriptors = [];
  if (row.kindKey === 'component') {
    for (const state of reviewPrimaryStates(row)) descriptors.push(`g:${state}:${classifications}`);
  } else if (row.kindKey === 'boundary') {
    for (const state of reviewPrimaryStates(row)) {
      descriptors.push(`rect:${state}:${classifications}`, `text:${state}:`);
    }
  } else {
    const forms = row.status === 'added'
      ? [{ state: 'added', marker: 'added', label: row.head?.label }]
      : row.status === 'removed'
        ? [{ state: 'removed', marker: 'removed', label: row.base?.label }]
        : row.classifications.includes('topology')
          ? [
              { state: 'removed', marker: 'removed', label: row.base?.label },
              { state: 'changed', marker: 'added', label: row.head?.label },
            ]
          : row.classifications.includes('geometry')
            ? [
                { state: 'moved-from', marker: 'moved-from', label: row.base?.label },
                { state: row.status, marker: row.status, label: row.head?.label },
              ]
            : [{ state: 'changed', marker: 'changed', label: row.head?.label }];
    for (const form of forms) {
      descriptors.push(`path:${form.state}:${classifications}`, `text:${form.marker}:`);
      if (form.label) descriptors.push(`g:${form.state}:${classifications}`);
    }
  }
  return descriptors.sort(codepointOrder).join('|');
}

const total = (summary, key) => summary.components[key] + summary.connections[key] + summary.boundaries[key];

export function renderArchitectureDeltaHtml({ receipt, baseSvg, deltaSvg, headSvg, baseHtml = '', headHtml = '', artifactCss }) {
  const rows = architectureDeltaChangeRows(receipt);
  const changed = total(receipt.summary, 'changed');
  const proof = receipt.proofLevel === 'revision-pinned' ? 'REVISION-PINNED INPUTS' : 'AUTHORED SNAPSHOTS';
  const rowHtml = rows.length ? rows.map((row, index) => {
    const label = row.headLabel || row.baseLabel || row.head?.label || row.base?.label || row.label || row.id;
    const targetSignature = expectedReviewTargetSignature(row);
    return `<li data-change-status="${esc(row.status)}"><button class="change-row" type="button" data-change-index="${index}" data-change-key="${esc(row.key)}" data-change-kind="${esc(row.kindKey)}" data-change-id="${esc(row.id)}" data-change-label="${esc(label)}" data-change-status="${esc(row.status)}" data-change-classifications="${esc(row.classifications.join(', '))}" data-change-target-signature="${esc(targetSignature)}"><span class="token">${esc(markerFor(row.status) || '~')}</span><span>${esc(row.kind)}</span><strong>${esc(label)}</strong><code>${esc(row.id)}</code><span>${esc(row.classifications.join(', '))}</span><span>${esc(row.changedFields.join(', ') || 'identity')}</span></button></li>`;
  }).join('\n') : '<li class="empty">No authored architecture changes.</li>';
  const baseView = baseHtml
    ? `<iframe class="snapshot-frame" title="Before architecture explorer" srcdoc="${esc(baseHtml)}"></iframe>`
    : baseSvg;
  const headView = headHtml
    ? `<iframe class="snapshot-frame" title="After architecture explorer" srcdoc="${esc(headHtml)}"></iframe>`
    : headSvg;
  const html = `<!doctype html>
<html lang="en" data-theme="dark" data-preset="${esc(receipt.view.visualPreset)}">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(receipt.head.title)} Architecture Delta</title>
<style>
${artifactCss}
:root{color-scheme:dark;--d-add:#34d399;--d-remove:#fb7185;--d-change:#fbbf24;--d-move:#7dd3fc;--d-focus:#7dd3fc;--d-ink:#e6edf5;--d-muted:#8aa0b5;--d-line:#25384a}
*{box-sizing:border-box}body{margin:0;overflow-x:hidden;background:#071019;color:var(--d-ink);font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace}.proof-page{width:min(1600px,calc(100vw - 64px));margin:auto;padding:30px 0 42px}.proof-head{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:28px;align-items:end;padding-bottom:20px;border-bottom:1px solid var(--d-line)}.eyebrow{margin:0 0 8px;color:#7dd3fc;font:700 11px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.14em}.proof-head h1{margin:0;font-size:clamp(32px,4vw,56px);line-height:.96;letter-spacing:-.04em}.subtitle{margin:12px 0 0;color:var(--d-muted);font-size:14px}.metrics{display:flex;gap:9px}.metric{min-width:86px;padding:11px 13px;border:1px solid var(--d-line);border-radius:8px;background:#0b1722}.metric strong{display:block;font:700 23px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.metric span{display:block;margin-top:6px;color:var(--d-muted);font:700 9px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.1em}.add strong{color:var(--d-add)}.remove strong{color:var(--d-remove)}.change strong{color:var(--d-change)}
.proof-tools{display:flex;align-items:center;justify-content:space-between;gap:20px;margin:16px 0 10px}.view-switch{display:inline-flex;padding:3px;border:1px solid var(--d-line);border-radius:8px;background:#0a141e}.view-switch button,.utility,.review-step{border:0;border-radius:6px;background:transparent;color:var(--d-muted);padding:8px 14px;font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;cursor:pointer}.view-switch button[aria-selected="true"]{background:#173047;color:#fff}.utility{border:1px solid var(--d-line)}.view-switch button:focus-visible,.utility:focus-visible,.review-step:focus-visible,.change-row:focus-visible{outline:2px solid var(--d-focus);outline-offset:2px}.utility:disabled,.review-step:disabled{cursor:not-allowed;opacity:.45}.legend{display:flex;gap:16px;color:var(--d-muted);font:650 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.legend span{display:inline-flex;align-items:center;gap:6px}.legend i{width:22px;border-top:3px solid currentColor}.legend .add{color:var(--d-add)}.legend .remove{color:var(--d-remove)}.legend .remove i{border-top-style:dashed}.legend .change{color:var(--d-change)}.legend .change i{border-top-style:dotted}.legend .move{color:var(--d-move)}.legend .move i{border-top-style:double}
.review-strip{display:grid;grid-template-columns:auto auto auto auto minmax(0,1fr);align-items:center;gap:5px;margin:0 0 10px;padding:7px 8px;border-block:1px solid var(--d-line);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.review-step{min-height:34px;border:1px solid var(--d-line);padding-inline:11px}.review-step[aria-pressed="true"]{border-color:var(--d-focus);color:var(--d-ink)}.review-status{min-width:0;padding-left:9px;color:var(--d-muted);font-size:10px;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.review-status strong{color:var(--d-ink);font-weight:750}.review-status[data-state="unavailable"]{color:var(--d-remove)}
.canvas{overflow:hidden;border:1px solid var(--d-line);border-radius:10px;background:#09141e;padding:12px;min-height:520px}.canvas svg{display:block;width:100%;height:auto;max-height:72vh}.canvas[hidden]{display:none}.snapshot-frame{display:block;width:100%;height:min(76vh,920px);min-height:620px;border:0;border-radius:6px;background:#071019}.canvas[data-view="base"],.canvas[data-view="head"]{padding:0}.canvas[data-view="delta"] [data-delta-state="same"]{opacity:.38}.canvas[data-delta-review-active]{--review-same-opacity:.14;--review-change-opacity:.28}.canvas[data-delta-review-active] [data-delta-state="same"]{opacity:var(--review-same-opacity)!important}.canvas[data-delta-review-active] [data-delta-state]:not([data-delta-state="same"]):not([data-delta-review-current]){opacity:var(--review-change-opacity)!important}.canvas[data-delta-review-active] [data-delta-review-current]{opacity:1!important;transition:opacity .16s ease-out}g[data-node-id][data-delta-state="added"]>rect:last-of-type{stroke:var(--d-add)!important;stroke-width:3!important}g[data-node-id][data-delta-state="removed"]>rect:last-of-type{stroke:var(--d-remove)!important;stroke-width:3!important;stroke-dasharray:7 5}g[data-node-id][data-delta-state="changed"]>rect:last-of-type{stroke:var(--d-change)!important;stroke-width:3!important;stroke-dasharray:2 3}g[data-node-id][data-delta-state="moved"]>rect:last-of-type,g[data-node-id][data-delta-state="moved-from"]>rect:last-of-type{stroke:var(--d-move)!important;stroke-width:3!important;stroke-dasharray:8 3 2 3}g[data-node-id][data-delta-state="moved-from"],path[data-delta-state="moved-from"]{opacity:.42}path[data-delta-state="added"]{stroke:var(--d-add)!important;stroke-width:3!important}path[data-delta-state="removed"]{stroke:var(--d-remove)!important;stroke-width:3!important;stroke-dasharray:7 5!important}path[data-delta-state="changed"]{stroke:var(--d-change)!important;stroke-width:3!important;stroke-dasharray:2 3!important}path[data-delta-state="rerouted"],path[data-delta-state="moved-from"]{stroke:var(--d-move)!important;stroke-width:2.5!important;stroke-dasharray:8 3 2 3!important}.delta-node-marker circle{fill:#071019;stroke:currentColor;stroke-width:1.5}.delta-node-marker text,.delta-edge-marker,.delta-boundary-marker{fill:currentColor;font:800 9px ui-monospace,SFMono-Regular,Menlo,monospace}[data-delta-state="added"] .delta-node-marker,.delta-edge-marker[data-delta-state="added"],.delta-boundary-marker[data-delta-state="added"]{color:var(--d-add)}[data-delta-state="removed"] .delta-node-marker,.delta-edge-marker[data-delta-state="removed"],.delta-boundary-marker[data-delta-state="removed"]{color:var(--d-remove)}[data-delta-state="changed"] .delta-node-marker,.delta-edge-marker[data-delta-state="changed"],.delta-boundary-marker[data-delta-state="changed"]{color:var(--d-change)}[data-delta-state="moved"] .delta-node-marker,[data-delta-state="moved-from"] .delta-node-marker,.delta-edge-marker[data-delta-state="moved-from"],.delta-edge-marker[data-delta-state="rerouted"],.delta-boundary-marker[data-delta-state="geometry-changed"]{color:var(--d-move)}.delta-edge-marker,.delta-boundary-marker{paint-order:stroke;stroke:#071019;stroke-width:3px}
rect[data-graph-role="structural-frame"][data-delta-state="added"]{stroke:var(--d-add)!important;stroke-width:2.5!important}rect[data-graph-role="structural-frame"][data-delta-state="removed"]{stroke:var(--d-remove)!important;stroke-width:2.5!important;stroke-dasharray:7 5!important}rect[data-graph-role="structural-frame"][data-delta-state="changed"]{stroke:var(--d-change)!important;stroke-width:2.5!important;stroke-dasharray:2 3!important}rect[data-graph-role="structural-frame"][data-delta-state="moved-from"]{stroke:var(--d-move)!important;stroke-width:2!important;stroke-dasharray:8 3 2 3!important;opacity:.42}text[data-delta-boundary-state="added"]{fill:var(--d-add)!important}text[data-delta-boundary-state="removed"]{fill:var(--d-remove)!important}text[data-delta-boundary-state="changed"]{fill:var(--d-change)!important}text[data-delta-boundary-state="moved-from"]{fill:var(--d-move)!important;opacity:.55}
details{margin-top:12px;border:1px solid var(--d-line);border-radius:9px;background:#0a141e}summary{padding:13px 15px;cursor:pointer;font-weight:700}.changes{list-style:none;margin:0;padding:0 8px 8px}.changes li{border-top:1px solid rgba(138,160,181,.16)}.change-row{display:grid;grid-template-columns:30px 90px minmax(140px,1fr) minmax(100px,.7fr) minmax(120px,.8fr) minmax(140px,1.2fr);gap:10px;width:100%;margin:0;padding:9px 7px;border:0;border-radius:5px;background:transparent;color:inherit;font:inherit;font-size:11px;text-align:left;align-items:baseline;cursor:pointer}.change-row:hover{background:rgba(125,211,252,.06)}.change-row[aria-current="step"]{background:rgba(125,211,252,.1);box-shadow:inset 0 0 0 1px var(--d-focus)}.change-row:disabled{cursor:default}.token{font:800 13px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.changes code,.change-row>span:last-child{color:var(--d-muted)}.proof-foot{display:flex;justify-content:space-between;gap:24px;margin-top:14px;color:var(--d-muted);font:650 10px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace}
html[data-theme="dark"] body{background:#071019!important;background-image:none!important}html[data-theme="light"]{color-scheme:light;--d-ink:#10283c;--d-muted:#587187;--d-line:#c8d6e2;--d-focus:#006b8f}html[data-theme="light"] body{background:#eef3f7!important;background-image:none!important;color:var(--d-ink)}html[data-theme="light"] .metric,html[data-theme="light"] .view-switch,html[data-theme="light"] details{background:#fff}html[data-theme="light"] .canvas{background:#f8fbfd}html[data-theme="light"] .view-switch button[aria-selected="true"]{background:#dbeaf5;color:#10283c}html[data-theme="light"] .delta-node-marker circle{fill:#fff}html[data-preset="blueprint"] body{background-image:none!important}
@media(max-width:760px){.proof-page{width:100%;padding:12px}.proof-head{grid-template-columns:1fr;gap:14px;align-items:start}.proof-head h1{font-size:32px}.metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));width:100%}.metric{min-width:0}.proof-tools{align-items:stretch;flex-wrap:wrap;gap:8px}.view-switch{display:flex;flex:1 1 100%}.view-switch button{flex:1;padding-inline:8px}.legend{flex-wrap:wrap;gap:8px}.proof-tools>div:last-child{margin-left:auto}.review-strip{grid-template-columns:auto auto auto auto}.review-status{grid-column:1/-1;padding:4px 2px 0}.canvas{min-height:0;padding:6px;overflow:auto}.canvas svg{min-width:720px;max-height:none}.snapshot-frame{min-width:720px}.changes{overflow-x:auto}.change-row{min-width:820px}.proof-foot{flex-direction:column;gap:4px}}
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important}.canvas[data-delta-review-active] [data-delta-review-current]{transition:none!important}}@media print{body{min-width:0;background:#fff;color:#111}.proof-page{width:100%;padding:0}.proof-tools,.review-strip,details{display:none!important}.canvas{display:none!important}.canvas[data-view="delta"]{display:block!important;border:0}.canvas[data-view="delta"] [data-delta-state="same"]{opacity:1!important;transition:none!important}.canvas[data-delta-review-active]{--review-same-opacity:1;--review-change-opacity:1}.canvas[data-delta-review-active] [data-delta-review-current]{opacity:1!important;transition:none!important}.proof-foot{color:#444}}
</style></head>
<body><main class="proof-page"><header class="proof-head"><div><p class="eyebrow">ARCHITECTURE DELTA · ${proof}</p><h1>See what changed<br>before you merge.</h1><p class="subtitle">${esc(receipt.base.title)} → ${esc(receipt.head.title)}</p></div><div class="metrics"><div class="metric add"><strong>${total(receipt.summary, 'added')}</strong><span>ADDED</span></div><div class="metric remove"><strong>${total(receipt.summary, 'removed')}</strong><span>REMOVED</span></div><div class="metric change"><strong>${changed}</strong><span>CHANGED</span></div></div></header>
<div class="proof-tools"><div class="view-switch" role="tablist" aria-label="Architecture snapshot"><button role="tab" data-target="base" aria-selected="false">Before</button><button role="tab" data-target="delta" aria-selected="true">Delta</button><button role="tab" data-target="head" aria-selected="false">After</button></div><div class="legend"><span class="add"><i></i>+ ADD</span><span class="remove"><i></i>− DEL</span><span class="change"><i></i>~ MOD</span><span class="move"><i></i>↔ MOVE</span></div><div><button class="utility" id="export-svg" type="button">Export SVG</button> <button class="utility" id="share-card" type="button">Share Card</button> <button class="utility" id="preset" type="button">Preset</button> <button class="utility" id="theme" type="button">Theme</button></div></div>
<nav class="review-strip" aria-label="Authored change review"><button class="review-step" id="review-overview" type="button" disabled>Overview</button><button class="review-step" id="review-previous" type="button" aria-label="Previous authored change" disabled>←</button><button class="review-step" id="review-play" type="button" aria-pressed="false"${rows.length ? '' : ' disabled'}>Review</button><button class="review-step" id="review-next" type="button" aria-label="Next authored change" disabled>→</button><div class="review-status" id="review-status" role="status" aria-live="polite">Overview · ${rows.length} authored changes</div></nav>
<section class="canvas" data-view="base" hidden>${baseView}</section><section class="canvas" data-view="delta">${deltaSvg}</section><section class="canvas" data-view="head" hidden>${headView}</section>
<details${rows.length <= 10 ? ' open' : ''}><summary>Exact authored changes · ${rows.length}</summary><ul class="changes">${rowHtml}</ul></details>
<footer class="proof-foot"><span>Stable IDs only · completeness: complete · ${proof}</span><span>Authored IR only · no risk or mergeability inference</span></footer></main>
<script id="archify-compare-receipt" type="application/json">${safeJson(receipt)}</script>
<script>(()=>{
  const REVIEW_DWELL_MS = 1400;
  const tabs = [...document.querySelectorAll('[role="tab"]')];
  const views = [...document.querySelectorAll('[data-view]')];
  const deltaCanvas = document.querySelector('[data-view="delta"]');
  const rowButtons = [...document.querySelectorAll('.change-row')];
  const overviewButton = document.querySelector('#review-overview');
  const previousButton = document.querySelector('#review-previous');
  const playButton = document.querySelector('#review-play');
  const nextButton = document.querySelector('#review-next');
  const status = document.querySelector('#review-status');
  const receiptNode = document.querySelector('#archify-compare-receipt');
  const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
  const exportCss = ${safeJson(artifactCss)};
  let activeIndex = -1;
  let playbackToken = 0;
  let playbackTimer = 0;
  let playing = false;
  let reviewAvailable = false;
  let reviewSources = [];

  function show(id) {
    tabs.forEach((tab) => tab.setAttribute('aria-selected', String(tab.dataset.target === id)));
    views.forEach((view) => { view.hidden = view.dataset.view !== id; });
  }

  function stopPlayback() {
    playbackToken += 1;
    if (playbackTimer) window.clearTimeout(playbackTimer);
    playbackTimer = 0;
    playing = false;
    playButton.setAttribute('aria-pressed', 'false');
    playButton.textContent = activeIndex === rowButtons.length - 1 && rowButtons.length ? 'Replay' : 'Review';
    status.setAttribute('aria-live', 'polite');
  }

  function failReview() {
    stopPlayback();
    reviewAvailable = false;
    activeIndex = -1;
    deltaCanvas?.removeAttribute('data-delta-review-active');
    deltaCanvas?.querySelectorAll('[data-delta-review-current]').forEach((element) => element.removeAttribute('data-delta-review-current'));
    rowButtons.forEach((row) => {
      row.disabled = true;
      row.tabIndex = -1;
      row.removeAttribute('aria-current');
    });
    [overviewButton, previousButton, playButton, nextButton].forEach((button) => { button.disabled = true; });
    status.dataset.state = 'unavailable';
    status.textContent = 'Review unavailable · compare identity mismatch';
  }

  function receiptRows(receipt) {
    const definitions = [
      { collection: 'components', kind: 'component', id: 'id', statuses: ['added', 'changed', 'evidence-changed', 'removed', 'moved'] },
      { collection: 'connections', kind: 'relationship', id: 'id', statuses: ['added', 'changed', 'removed', 'rerouted'] },
      { collection: 'boundaries', kind: 'boundary', id: 'key', statuses: ['added', 'changed', 'removed', 'geometry-changed'] },
    ];
    const rows = [];
    for (const definition of definitions) {
      const changes = receipt.changes[definition.collection];
      if (!Array.isArray(changes)) return null;
      for (const change of changes) {
        if (!change || typeof change !== 'object' || Array.isArray(change)) return null;
        const id = change[definition.id];
        if (typeof id !== 'string' || !id || !definition.statuses.includes(change.status)) return null;
        if (!Array.isArray(change.classifications) || !change.classifications.length || change.classifications.some((value) => typeof value !== 'string')) return null;
        if (new Set(change.classifications).size !== change.classifications.length) return null;
        if (!Array.isArray(change.changedFields) || change.changedFields.some((value) => typeof value !== 'string')) return null;
        rows.push({ ...change, kind: definition.kind, key: definition.kind + ':' + id, id });
      }
    }
    return rows;
  }

  function exactTargets(row) {
    const attribute = row.dataset.changeKind === 'component'
      ? 'data-node-id'
      : row.dataset.changeKind === 'relationship'
        ? 'data-edge-id'
        : 'data-delta-boundary-key';
    return [...deltaCanvas.querySelectorAll('[' + attribute + ']')]
      .filter((element) => element.getAttribute(attribute) === row.dataset.changeId);
  }

  function targetSignature(matches) {
    return matches.map((element) => {
      const tag = element.tagName.toLowerCase();
      const state = element.dataset.deltaState || '';
      const classifications = element.dataset.deltaClassifications || '';
      return tag + ':' + state + ':' + classifications;
    }).sort().join('|');
  }

  function targetsMatch(source, row, matches) {
    if (!source || !matches.length || !row.dataset.changeTargetSignature) return false;
    if (row.dataset.changeKind !== source.kind || row.dataset.changeId !== source.id || row.dataset.changeStatus !== source.status) return false;
    if (row.dataset.changeClassifications !== source.classifications.join(', ')) return false;
    return row.dataset.changeTargetSignature === targetSignature(matches);
  }

  function validateReview() {
    try {
      if (document.querySelectorAll('#archify-compare-receipt').length !== 1 || !receiptNode) return false;
      if (!deltaCanvas || !['base', 'delta', 'head'].every((id) => document.querySelectorAll('[data-view="' + id + '"]').length === 1)) return false;
      if (deltaCanvas.children.length !== 1 || deltaCanvas.firstElementChild?.tagName.toLowerCase() !== 'svg') return false;
      const receipt = JSON.parse(receiptNode.textContent);
      if (!receipt || receipt.schemaVersion !== 1 || receipt.command !== 'compare' || receipt.completeness !== 'complete' || !receipt.changes) return false;
      const sources = receiptRows(receipt);
      if (!sources || sources.length !== rowButtons.length) return false;
      const sourceByKey = new Map(sources.map((source) => [source.key, source]));
      if (sourceByKey.size !== sources.length) return false;
      const seen = new Set();
      const orderedSources = [];
      for (let index = 0; index < rowButtons.length; index += 1) {
        const row = rowButtons[index];
        const source = sourceByKey.get(row.dataset.changeKey);
        if (!source || seen.has(source.key) || row.dataset.changeIndex !== String(index)) return false;
        const matches = exactTargets(row);
        if (!targetsMatch(source, row, matches)) return false;
        seen.add(source.key);
        orderedSources.push(source);
      }
      if (seen.size !== sourceByKey.size) return false;
      reviewSources = orderedSources;
      return true;
    } catch {
      return false;
    }
  }

  function updateControls() {
    overviewButton.disabled = !reviewAvailable || activeIndex < 0;
    previousButton.disabled = !reviewAvailable || activeIndex <= 0;
    nextButton.disabled = !reviewAvailable || activeIndex < 0 || activeIndex >= rowButtons.length - 1;
  }

  function selectChange(index, fromPlayback = false) {
    if (!reviewAvailable || index < 0 || index >= rowButtons.length) return false;
    const row = rowButtons[index];
    const matches = exactTargets(row);
    if (!targetsMatch(reviewSources[index], row, matches)) { failReview(); return false; }
    show('delta');
    deltaCanvas.querySelectorAll('[data-delta-review-current]').forEach((element) => element.removeAttribute('data-delta-review-current'));
    matches.forEach((element) => element.setAttribute('data-delta-review-current', 'true'));
    deltaCanvas.setAttribute('data-delta-review-active', 'true');
    rowButtons.forEach((button, rowIndex) => {
      button.tabIndex = rowIndex === index ? 0 : -1;
      if (rowIndex === index) button.setAttribute('aria-current', 'step');
      else button.removeAttribute('aria-current');
    });
    activeIndex = index;
    updateControls();
    const kind = row.dataset.changeKind.charAt(0).toUpperCase() + row.dataset.changeKind.slice(1);
    status.textContent = String(index + 1).padStart(2, '0') + ' / ' + String(rowButtons.length).padStart(2, '0') + ' · ' + kind + ' · ' + row.dataset.changeLabel + ' [' + row.dataset.changeId + '] · ' + row.dataset.changeClassifications;
    if (fromPlayback) status.setAttribute('aria-live', 'off');
    return true;
  }

  function overview() {
    if (!reviewAvailable) { failReview(); return; }
    stopPlayback();
    activeIndex = -1;
    deltaCanvas.removeAttribute('data-delta-review-active');
    deltaCanvas.querySelectorAll('[data-delta-review-current]').forEach((element) => element.removeAttribute('data-delta-review-current'));
    rowButtons.forEach((row, index) => {
      row.tabIndex = index === 0 ? 0 : -1;
      row.removeAttribute('aria-current');
    });
    show('delta');
    status.removeAttribute('data-state');
    status.textContent = 'Overview · ' + rowButtons.length + ' authored changes';
    updateControls();
  }

  function schedulePlayback(token) {
    if (!playing || token !== playbackToken || activeIndex >= rowButtons.length - 1) {
      if (activeIndex >= rowButtons.length - 1) stopPlayback();
      return;
    }
    playbackTimer = window.setTimeout(() => {
      if (!playing || token !== playbackToken) return;
      if (!selectChange(activeIndex + 1, true)) return;
      schedulePlayback(token);
    }, REVIEW_DWELL_MS);
  }

  function startReview() {
    if (!reviewAvailable) return;
    if (playing) { stopPlayback(); return; }
    stopPlayback();
    if (!selectChange(0, false)) return;
    if (reducedMotion.matches || rowButtons.length < 2) return;
    playing = true;
    const token = ++playbackToken;
    playButton.textContent = 'Pause';
    playButton.setAttribute('aria-pressed', 'true');
    status.setAttribute('aria-live', 'off');
    schedulePlayback(token);
  }

  function canonicalDeltaSvg() {
    const source = deltaCanvas?.querySelector(':scope > svg');
    if (!source) throw new Error('Canonical Delta SVG is unavailable.');
    const clone = source.cloneNode(true);
    clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
    clone.removeAttribute('style');
    clone.querySelectorAll('[data-delta-review-current]').forEach((element) => element.removeAttribute('data-delta-review-current'));
    const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
    style.textContent = exportCss + '\\n' +
      '[data-delta-state="same"]{opacity:.38}' +
      '[data-delta-state="added"]{--delta:#34d399}' +
      '[data-delta-state="removed"]{--delta:#fb7185}' +
      '[data-delta-state="changed"]{--delta:#fbbf24}' +
      '[data-delta-state="moved"],[data-delta-state="moved-from"],[data-delta-state="rerouted"],[data-delta-state="geometry-changed"]{--delta:#7dd3fc}' +
      'g[data-node-id][data-delta-state]:not([data-delta-state="same"])>rect:last-of-type{stroke:var(--delta)!important;stroke-width:3!important}' +
      'path[data-delta-state]:not([data-delta-state="same"]){stroke:var(--delta)!important;stroke-width:3!important}' +
      'rect[data-graph-role="structural-frame"][data-delta-state]:not([data-delta-state="same"]){stroke:var(--delta)!important;stroke-width:2.5!important}' +
      'rect[data-graph-role="structural-frame"][data-delta-state="changed"]{stroke-dasharray:2 3!important}' +
      '[data-delta-state="removed"],[data-delta-state="moved-from"]{stroke-dasharray:7 5!important}' +
      '[data-delta-state="moved-from"]{opacity:.42}' +
      '[data-delta-state]:not([data-delta-state="same"]) .delta-node-marker,.delta-edge-marker[data-delta-state],.delta-boundary-marker[data-delta-state]{color:var(--delta)}' +
      'text[data-delta-boundary-state="added"]{fill:#34d399!important}text[data-delta-boundary-state="removed"]{fill:#fb7185!important}text[data-delta-boundary-state="changed"]{fill:#fbbf24!important}text[data-delta-boundary-state="moved-from"]{fill:#7dd3fc!important;opacity:.55}' +
      '.delta-node-marker circle{fill:#071019;stroke:currentColor;stroke-width:1.5}.delta-node-marker text,.delta-edge-marker,.delta-boundary-marker{fill:currentColor;font:800 9px ui-monospace,SFMono-Regular,Menlo,monospace}';
    clone.insertBefore(style, clone.firstChild);
    return new XMLSerializer().serializeToString(clone);
  }

  function artifactName(suffix) {
    const title = String(JSON.parse(receiptNode.textContent).head.title || 'architecture-delta')
      .toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'architecture-delta';
    return title + suffix;
  }

  function downloadBlob(blob, name) {
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = name;
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  function recordExport(format, blob, dimensions) {
    document.documentElement.dataset.archifyDeltaExport = JSON.stringify({
      ok: true,
      format,
      bytes: blob.size,
      mime: blob.type,
      ...(dimensions || {}),
    });
  }

  function exportCanonicalSvg() {
    const blob = new Blob([canonicalDeltaSvg()], { type: 'image/svg+xml;charset=utf-8' });
    downloadBlob(blob, artifactName('-architecture-delta.svg'));
    recordExport('svg', blob);
    return blob;
  }

  function loadSvgImage(blob) {
    return new Promise((resolve, reject) => {
      const url = URL.createObjectURL(blob);
      const image = new Image();
      image.onload = () => { URL.revokeObjectURL(url); resolve(image); };
      image.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Could not rasterize canonical Delta SVG.')); };
      image.src = url;
    });
  }

  function pngBlob(canvas) {
    return new Promise((resolve, reject) => canvas.toBlob((blob) => {
      if (blob) resolve(blob);
      else reject(new Error('Canvas returned no Share Card PNG.'));
    }, 'image/png'));
  }

  async function shareCard() {
    const receipt = JSON.parse(receiptNode.textContent);
    const canvas = document.createElement('canvas');
    canvas.width = 1200;
    canvas.height = 630;
    const ctx = canvas.getContext('2d');
    if (!ctx) throw new Error('2D canvas context unavailable for Architecture Delta Share Card.');
    ctx.fillStyle = '#071019';
    ctx.fillRect(0, 0, 1200, 630);
    ctx.fillStyle = '#0b1722';
    ctx.fillRect(34, 28, 1132, 574);
    ctx.strokeStyle = '#25384a';
    ctx.lineWidth = 2;
    ctx.strokeRect(34, 28, 1132, 574);
    ctx.font = '700 18px ui-monospace, SFMono-Regular, Menlo, monospace';
    ctx.fillStyle = '#7dd3fc';
    ctx.fillText('ARCHITECTURE DELTA', 66, 68);
    ctx.font = '700 34px ui-monospace, SFMono-Regular, Menlo, monospace';
    ctx.fillStyle = '#e6edf5';
    const title = String(receipt.head.title || 'Architecture').slice(0, 46) + ' Architecture Delta';
    ctx.fillText(title.slice(0, 58), 66, 112);
    ctx.font = '700 17px ui-monospace, SFMono-Regular, Menlo, monospace';
    ctx.fillStyle = '#8aa0b5';
    const componentLine = 'COMPONENTS  +' + receipt.summary.components.added + '  ~' + receipt.summary.components.changed + '  −' + receipt.summary.components.removed;
    const connectionLine = 'CONNECTIONS  +' + receipt.summary.connections.added + '  ~' + receipt.summary.connections.changed + '  −' + receipt.summary.connections.removed;
    const boundaryLine = 'BOUNDARY SCOPE  +' + receipt.summary.boundaries.added + '  ~' + receipt.summary.boundaries.changed + '  −' + receipt.summary.boundaries.removed;
    ctx.fillText(componentLine, 66, 151);
    ctx.fillText(connectionLine, 420, 151);
    ctx.fillText(boundaryLine, 786, 151);
    ctx.font = '650 14px ui-monospace, SFMono-Regular, Menlo, monospace';
    const authoredChanges = ['components', 'connections', 'boundaries'].reduce((sum, collection) => {
      const summary = receipt.summary[collection];
      return sum + summary.added + summary.changed + summary.removed;
    }, 0);
    const movementSummary = '↔ moved ' + receipt.summary.components.moved + ' · rerouted ' + receipt.summary.connections.rerouted + ' · presentation ' + (receipt.summary.presentationChanged ? 'changed' : 'unchanged');
    const secondary = authoredChanges === 0
      ? 'No authored architecture changes · ' + movementSummary
      : movementSummary;
    ctx.fillText(secondary, 66, 181);
    const proofLine = receipt.proofLevel === 'revision-pinned'
      ? 'REV ' + String(receipt.base.revision).slice(0, 8) + ' → ' + String(receipt.head.revision).slice(0, 8) + ' · REVISION-PINNED INPUTS'
      : 'AUTHORED SNAPSHOTS';
    ctx.textAlign = 'right';
    ctx.fillText(proofLine, 1134, 181);
    ctx.textAlign = 'left';
    ctx.fillStyle = '#09141e';
    ctx.fillRect(66, 206, 1068, 358);
    ctx.strokeStyle = '#25384a';
    ctx.strokeRect(66, 206, 1068, 358);
    const svgBlob = new Blob([canonicalDeltaSvg()], { type: 'image/svg+xml;charset=utf-8' });
    const image = await loadSvgImage(svgBlob);
    const ratio = Math.min(1024 / image.width, 322 / image.height);
    const width = image.width * ratio;
    const height = image.height * ratio;
    ctx.drawImage(image, 600 - width / 2, 385 - height / 2, width, height);
    ctx.font = '650 12px ui-monospace, SFMono-Regular, Menlo, monospace';
    ctx.fillStyle = '#8aa0b5';
    ctx.fillText('Stable authored IDs · static complete Delta · no risk or mergeability inference', 66, 588);
    return pngBlob(canvas);
  }

  async function downloadShareCard() {
    const blob = await shareCard();
    downloadBlob(blob, artifactName('-architecture-delta-share-card.png'));
    recordExport('share-card', blob, { width: 1200, height: 630 });
    return blob;
  }

  window.Archify = window.Archify || {};
  window.Archify.deltaExport = { canonicalSvg: canonicalDeltaSvg, shareCard, exportSvg: exportCanonicalSvg, downloadShareCard };
  window.Archify.exportMenu = {
    shareCard,
    run(format) {
      if (format === 'svg') return exportCanonicalSvg();
      if (format === 'share-card') return downloadShareCard();
      throw new Error('Unknown Architecture Delta export format: ' + format);
    },
  };

  tabs.forEach((tab, index) => {
    tab.addEventListener('click', () => { stopPlayback(); show(tab.dataset.target); });
    tab.addEventListener('keydown', (event) => {
      if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
      event.preventDefault();
      stopPlayback();
      const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : (index + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length;
      tabs[next].focus();
      show(tabs[next].dataset.target);
    });
  });

  rowButtons.forEach((row, index) => {
    row.tabIndex = index === 0 ? 0 : -1;
    row.addEventListener('click', () => { stopPlayback(); selectChange(index); });
    row.addEventListener('keydown', (event) => {
      if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();
        stopPlayback();
        selectChange(index);
        return;
      }
      if (!['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) return;
      event.preventDefault();
      stopPlayback();
      const next = event.key === 'Home' ? 0 : event.key === 'End' ? rowButtons.length - 1 : (index + (event.key === 'ArrowDown' ? 1 : -1) + rowButtons.length) % rowButtons.length;
      rowButtons.forEach((button, rowIndex) => { button.tabIndex = rowIndex === next ? 0 : -1; });
      rowButtons[next].focus();
    });
  });

  overviewButton.addEventListener('click', overview);
  previousButton.addEventListener('click', () => { stopPlayback(); selectChange(activeIndex - 1); });
  nextButton.addEventListener('click', () => { stopPlayback(); selectChange(activeIndex + 1); });
  playButton.addEventListener('click', startReview);
  document.addEventListener('focusin', (event) => {
    if (playing && event.target.closest('button')) stopPlayback();
  });
  document.addEventListener('visibilitychange', () => { if (document.hidden) stopPlayback(); });
  window.addEventListener('beforeprint', overview);
  reducedMotion.addEventListener('change', () => { if (reducedMotion.matches) stopPlayback(); });
  document.addEventListener('keydown', (event) => {
    if (event.key === 'Escape' && activeIndex >= 0) {
      event.preventDefault();
      overview();
      playButton.focus();
    }
  });

  document.querySelector('#theme').addEventListener('click', () => { document.documentElement.dataset.theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; });
  const presets = ['classic', 'signal-flow', 'blueprint'];
  document.querySelector('#preset').addEventListener('click', () => { const now = document.documentElement.dataset.preset; document.documentElement.dataset.preset = presets[(presets.indexOf(now) + 1) % presets.length]; });
  document.querySelector('#export-svg').addEventListener('click', exportCanonicalSvg);
  document.querySelector('#share-card').addEventListener('click', () => { downloadShareCard().catch((error) => window.alert(error.message)); });

  reviewAvailable = validateReview();
  if (!reviewAvailable) failReview();
  else {
    status.removeAttribute('data-state');
    updateControls();
  }
})();</script></body></html>`;
  return html.replace(/[ \t]+$/gm, '');
}

export function validateArchitectureDeltaHtml(html, receipt) {
  const failures = [];
  const rows = architectureDeltaChangeRows(receipt);
  const deltaMarkup = html.match(/<section class="canvas" data-view="delta">([\s\S]*?)<\/section>/)?.[1] || '';
  const svgTags = [...deltaMarkup.matchAll(/<\/?svg\b[^>]*>/g)];
  let svgDepth = 0;
  let svgRoots = 0;
  let rootStart = -1;
  let rootEnd = -1;
  let svgBalanced = true;
  for (const match of svgTags) {
    if (match[0].startsWith('</')) {
      svgDepth -= 1;
      if (svgDepth < 0) svgBalanced = false;
      if (svgDepth === 0) rootEnd = match.index + match[0].length;
    } else {
      if (svgDepth === 0) {
        svgRoots += 1;
        if (rootStart < 0) rootStart = match.index;
      }
      svgDepth += 1;
    }
  }
  if (!['base', 'delta', 'head'].every((id) => (html.match(new RegExp(`<section class="canvas" data-view="${id}"`, 'g')) || []).length === 1)) failures.push('expected one Before, Delta, and After canvas');
  if ((html.match(/class="snapshot-frame" title="Before architecture explorer"/g) || []).length !== 1
    || (html.match(/class="snapshot-frame" title="After architecture explorer"/g) || []).length !== 1) {
    failures.push('Before and After must preserve one complete architecture explorer each');
  }
  if (!svgBalanced || svgDepth !== 0 || svgRoots !== 1 || deltaMarkup.slice(0, rootStart).trim() || deltaMarkup.slice(rootEnd).trim()) failures.push('expected exactly one root SVG in the Delta canvas');
  if ((html.match(/id="archify-compare-receipt"/g) || []).length !== 1) failures.push('expected exactly one embedded compare receipt');
  if (!html.includes('aria-label="Authored change review"')) failures.push('missing exact-ID change navigator');
  if ((html.match(/class="change-row"/g) || []).length !== rows.length) failures.push('change navigator row count does not match the receipt');
  if (!html.includes('id="export-svg"') || !html.includes('id="share-card"')
    || !html.includes('window.Archify.deltaExport = { canonicalSvg: canonicalDeltaSvg, shareCard')) {
    failures.push('missing canonical Delta SVG or Share Card export contract');
  }
  for (const [index, row] of rows.entries()) {
    const safeKey = esc(row.key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const rowMatches = [...html.matchAll(new RegExp(`<button class="change-row"[^>]*data-change-key="${safeKey}"[^>]*>`, 'g'))].map((match) => match[0]);
    if (rowMatches.length !== 1) failures.push(`expected exactly one change row ${row.key}`);
    const targets = reviewTargetTags(deltaMarkup, row);
    if (!targets.length) failures.push(`missing Delta identity ${row.key}`);
    if (targets.some(({ tag }) => !/\bdata-delta-state="[^"]+"/.test(tag))) failures.push(`missing Delta target state ${row.key}`);
    const signature = reviewTargetSignature(targets);
    const expectedSignature = expectedReviewTargetSignature(row);
    const storedSignature = rowMatches[0]?.match(/\bdata-change-target-signature="([^"]*)"/)?.[1];
    if (!signature || signature !== expectedSignature || storedSignature !== expectedSignature) failures.push(`ambiguous Delta target signature ${row.key}`);
    if (rowMatches[0]?.match(/\bdata-change-index="([^"]+)"/)?.[1] !== String(index)) failures.push(`incorrect change row order ${row.key}`);
    const primary = primaryReviewTags(deltaMarkup, row);
    const states = primary.map((tag) => tag.match(/\bdata-delta-state="([^"]+)"/)?.[1]).filter(Boolean).sort();
    if (JSON.stringify(states) !== JSON.stringify(reviewPrimaryStates(row).sort())) failures.push(`ambiguous Delta identity ${row.key}`);
    const classifications = row.classifications.join(',');
    if (primary.some((tag) => tag.match(/\bdata-delta-classifications="([^"]*)"/)?.[1] !== classifications)) failures.push(`conflicting Delta classification ${row.key}`);
  }
  // Before/After embed the complete existing explorer runtime. Validate claims
  // made by the Delta shell itself, not implementation vocabulary inside an
  // escaped srcdoc script (for example, "safe scale" in image export code).
  const deltaShell = html.replace(/<iframe\b[^>]*><\/iframe>/g, '');
  if (/\b(?:SAFE|LOW RISK|MERGEABLE|NO IMPACT|VERIFIED PR)\b/i.test(deltaShell)) failures.push('contains a forbidden risk or mergeability claim');
  if (/\b(?:NaN|Infinity)\b/.test(html)) failures.push('contains non-finite output');
  if (receipt.completeness !== 'complete') failures.push('receipt is not complete');
  if (failures.length) fail('delta/artifact-invalid', `Architecture Delta artifact failed validation: ${failures.join('; ')}.`, { failures });
  return { ok: true, checksPassed: 10, checkCount: 10 };
}
```

## examples

```

```

## examples/agent-run.lifecycle.json

```json
{
  "schema_version": 1,
  "diagram_type": "lifecycle",
  "meta": {
    "title": "Agent Run Lifecycle",
    "output": "examples/lifecycle-agent-run.html",
    "viewBox": [1030, 630],
    "animation": "trace",
    "quality_profile": "showcase",
    "views": [
      { "id": "main-lifecycle", "label": "Main lifecycle", "focus": ["queued", "planning", "executing", "reviewing", "completed"], "note": "Follow the ordered phases from accepted request to completed response." },
      { "id": "human-waits", "label": "Human and input waits", "focus": ["executing", "approval", "reviewing", "blocked"], "note": "See where the run pauses without becoming terminal." },
      { "id": "recovery-and-exits", "label": "Recovery and terminal exits", "focus": ["executing", "failed", "blocked", "cancelled", "expired"], "note": "Separate retryable failure from cancellation and expiry." }
    ]
  },
  "lanes": [
    { "id": "main", "label": "Lifecycle phases" },
    { "id": "waiting", "label": "Interruptions" },
    { "id": "exceptions", "label": "Recovery loop" },
    { "id": "terminal", "label": "Terminal exits" }
  ],
  "states": [
    { "id": "queued", "type": "start", "label": "Queued", "sublabel": "request accepted", "lane": "main", "col": 0, "step": "01", "tag": "entry" },
    { "id": "planning", "type": "active", "label": "Planning", "sublabel": "build task graph", "lane": "main", "col": 1, "step": "02", "tag": "model" },
    { "id": "executing", "type": "active", "label": "Executing", "sublabel": "tool calls", "lane": "main", "col": 2, "step": "03", "tag": "work" },
    { "id": "reviewing", "type": "decision", "label": "Reviewing", "sublabel": "quality gate", "lane": "main", "col": 3, "step": "04", "tag": "check" },
    { "id": "completed", "type": "success", "label": "Completed", "sublabel": "final response", "lane": "main", "col": 4, "step": "05", "tag": "done" },
    { "id": "approval", "type": "waiting", "label": "Needs Approval", "sublabel": "human gate", "lane": "waiting", "col": 0, "tag": "pause" },
    { "id": "blocked", "type": "waiting", "label": "Blocked", "sublabel": "missing input", "lane": "waiting", "col": 1, "tag": "wait" },
    { "id": "failed", "type": "failure", "label": "Failed", "sublabel": "recoverable error", "lane": "exceptions", "col": 0, "yOffset": 78, "tag": "retryable" },
    { "id": "cancelled", "type": "failure", "label": "Cancelled", "sublabel": "user stopped", "lane": "terminal", "col": 0, "tag": "terminal" },
    { "id": "expired", "type": "failure", "label": "Expired", "sublabel": "timeout", "lane": "terminal", "col": 1, "tag": "terminal" }
  ],
  "transitions": [
    { "id": "approval-needed", "from": "executing", "to": "approval", "variant": "security", "fromSide": "bottom", "toSide": "top", "route": "straight" },
    { "id": "review-blocked", "from": "reviewing", "to": "blocked", "variant": "default", "route": "drop" },
    { "id": "execution-failed", "from": "executing", "to": "failed", "variant": "security", "fromSide": "left", "toSide": "left", "via": [[320, 157], [320, 385]] },
    { "id": "failed-retry", "from": "failed", "to": "executing", "variant": "emphasis", "fromSide": "left", "toSide": "top", "via": [[20, 385], [20, 80], [402, 80]] },
    { "id": "block-expired", "from": "blocked", "to": "expired", "variant": "security", "fromSide": "bottom", "toSide": "top", "route": "straight" },
    { "id": "approval-cancelled", "from": "approval", "to": "cancelled", "variant": "security", "fromSide": "bottom", "toSide": "top", "via": [[480, 336], [480, 432], [402, 432]] }
  ],
  "cards": [
    {
      "dot": "emerald",
      "title": "Main Path + Waits",
      "items": [
        "The run has five ordered phases from queue to completion",
        "Approval and missing input pause the run without ending it"
      ]
    },
    {
      "dot": "rose",
      "title": "Recovery + Terminal Exits",
      "items": [
        "Failed loops back while retry budget remains",
        "Cancelled and Expired are terminal exits with no return path"
      ]
    }
  ]
}
```

## examples/agent-tool-call.workflow.json

```json
{
  "schema_version": 2,
  "diagram_type": "workflow",
  "meta": {
    "title": "Agent Tool Call Workflow",
    "animation": "trace",
    "visual_preset": "signal-flow",
    "quality_profile": "showcase",
    "views": [
      {
        "id": "happy-path",
        "label": "Request to result",
        "focus": ["user", "chat", "planner", "router", "approval", "tool", "external", "final"],
        "note": "Follow the successful request from user intent to the final reply."
      },
      {
        "id": "safety-gate",
        "label": "Policy and recovery",
        "focus": ["router", "approval", "blocked", "retry"],
        "note": "See where risky work stops, waits for consent, or returns for revision."
      },
      {
        "id": "evidence-loop",
        "label": "Evidence and memory",
        "focus": ["external", "store", "trace"],
        "note": "Isolate the durable trace and context path behind the visible answer."
      }
    ],
    "output": "examples/workflow-agent-tool-call-rendered.html"
  },
  "lanes": [
    { "id": "ui", "label": "User Interface" },
    { "id": "agent", "label": "Agent Runtime" },
    { "id": "policy", "label": "Policy & Recovery", "variant": "exception" },
    { "id": "tools", "label": "Tool Execution & Evidence" }
  ],
  "phases": [
    { "id": "intake", "label": "Intake", "fromCol": 0, "toCol": 1 },
    { "id": "reasoning", "label": "Plan + route", "fromCol": 2, "toCol": 3, "variant": "emphasis" },
    { "id": "execution", "label": "Execute + report", "fromCol": 4, "toCol": 5, "variant": "dashed" }
  ],
  "groups": [
    { "id": "agent_loop", "label": "Planning loop", "lane": "agent", "fromCol": 2, "toCol": 3, "variant": "emphasis" },
    { "id": "exception_path", "label": "Human or policy stop", "lane": "policy", "fromCol": 3, "toCol": 5, "variant": "security" },
    { "id": "evidence_path", "label": "Evidence path", "lane": "tools", "fromCol": 1, "toCol": 2, "variant": "dashed" },
    { "id": "tool_work", "label": "Tool work", "lane": "tools", "fromCol": 4, "toCol": 5, "variant": "dashed" }
  ],
  "mainPath": ["user", "chat", "planner", "router", "approval", "tool", "external", "final"],
  "nodes": [
    { "id": "user", "lane": "ui", "col": 0, "type": "external", "label": "User", "sublabel": "asks for work", "width": 132 },
    { "id": "chat", "lane": "ui", "col": 1, "type": "frontend", "label": "Chat Surface", "sublabel": "thread + files", "width": 132 },
    { "id": "final", "lane": "ui", "col": 5, "type": "backend", "label": "Final Reply", "sublabel": "answer + changes", "width": 132 },
    { "id": "planner", "lane": "agent", "col": 2, "type": "backend", "label": "Agent Planner", "sublabel": "plan next step", "tag": "context aware", "width": 132 },
    { "id": "router", "lane": "agent", "col": 3, "type": "backend", "label": "Tool Router", "sublabel": "choose capability", "width": 132 },
    { "id": "approval", "lane": "policy", "col": 3, "type": "security", "label": "Approval Gate", "sublabel": "scope + consent", "tag": "block risky ops", "width": 132 },
    { "id": "blocked", "lane": "policy", "col": 4, "type": "security", "label": "Blocked", "sublabel": "wait or reject", "width": 132 },
    { "id": "retry", "lane": "policy", "col": 5, "type": "messagebus", "label": "Retry Path", "sublabel": "revise request", "width": 132 },
    { "id": "tool", "lane": "tools", "col": 4, "type": "messagebus", "label": "Tool Call", "sublabel": "shell / browser / MCP", "tag": "structured result", "width": 132 },
    { "id": "external", "lane": "tools", "col": 5, "type": "cloud", "label": "External API", "sublabel": "network service", "width": 132 },
    { "id": "store", "lane": "tools", "col": 1, "type": "database", "label": "Context Store", "sublabel": "repo + memory", "width": 132 },
    { "id": "trace", "lane": "tools", "col": 2, "type": "database", "label": "Trace Log", "sublabel": "events + output", "width": 132 }
  ],
  "edges": [
    { "id": "request-chat", "from": "user", "to": "chat", "variant": "default" },
    { "id": "plan-request", "from": "chat", "to": "planner", "label": "plan", "variant": "emphasis" },
    { "id": "planner-route", "from": "planner", "to": "router", "variant": "default" },
    { "id": "approval-check", "from": "router", "to": "approval", "label": "needs approval?", "variant": "security" },
    { "id": "approved-tool", "from": "approval", "to": "tool", "variant": "emphasis" },
    { "id": "approval-denied", "from": "approval", "to": "blocked", "label": "denied", "variant": "security", "role": "error" },
    { "id": "retry-request", "from": "blocked", "to": "retry", "variant": "dashed", "role": "branch" },
    { "id": "tool-external-call", "from": "tool", "to": "external", "variant": "default" },
    { "id": "external-reply", "from": "external", "to": "final", "variant": "emphasis", "role": "return", "fromSide": "right", "toSide": "right", "route": "outside-right", "width": 1.2 },
    { "id": "record-result", "from": "external", "to": "trace", "label": "record result", "variant": "dashed", "fromSide": "bottom", "toSide": "bottom", "route": "bottom-channel", "labelSegment": 1 },
    { "id": "write-trace-memory", "from": "store", "to": "trace", "label": "trace + memory", "variant": "dashed" }
  ],
  "cards": [
    {
      "dot": "cyan",
      "title": "Compiler Contract",
      "items": [
        "Lanes and columns determine node placement",
        "Labels reserve clearance; routes stay orthogonal"
      ]
    },
    {
      "dot": "rose",
      "title": "Runtime Semantics",
      "items": [
        "Approval gates risky work before tool execution",
        "Evidence returns through isolated trace and memory"
      ]
    }
  ]
}
```

## examples/async-job-roundtrip.sequence.json

```json
{
  "schema_version": 1,
  "diagram_type": "sequence",
  "meta": {
    "title": "Async Job Roundtrip",
    "output": "examples/async-job-roundtrip.html",
    "viewBox": [820, 920],
    "animation": "trace",
    "visual_preset": "signal-flow",
    "quality_profile": "showcase",
    "views": [
      { "id": "accept-and-enqueue", "label": "Accept without blocking", "focus": ["client", "api", "queue"], "note": "The API acknowledges quickly after durable enqueue." },
      { "id": "work-and-retry", "label": "Background work and retry", "focus": ["queue", "worker", "provider"], "note": "Timeouts re-enter the queue instead of holding the original request open." },
      { "id": "observe-final-state", "label": "Observe final consistency", "focus": ["worker", "store", "notify", "client", "api"], "note": "Webhook delivery is primary; polling remains a bounded fallback." }
    ]
  },
  "participants": [
    { "id": "client", "type": "external", "label": "Client", "sublabel": "mobile app" },
    { "id": "api", "type": "backend", "label": "Jobs API", "sublabel": "request edge" },
    { "id": "queue", "type": "messagebus", "label": "Queue", "sublabel": "durable work" },
    { "id": "worker", "type": "backend", "label": "Worker", "sublabel": "background" },
    { "id": "provider", "type": "cloud", "label": "Provider", "sublabel": "external API" },
    { "id": "store", "type": "database", "label": "Job Store", "sublabel": "source of truth" },
    { "id": "notify", "type": "messagebus", "label": "Notifier", "sublabel": "webhook" }
  ],
  "segments": [
    { "from": 150, "to": 288, "label": "Accept" },
    { "from": 306, "to": 538, "label": "Background work" },
    { "from": 554, "to": 800, "label": "Notify + reconcile" }
  ],
  "messages": [
    { "from": "client", "to": "api", "y": 180, "label": "POST /jobs", "variant": "emphasis" },
    { "from": "api", "to": "queue", "y": 222, "label": "enqueue job", "variant": "emphasis" },
    { "from": "api", "to": "client", "y": 264, "label": "202 + job id", "variant": "return" },
    { "from": "queue", "to": "worker", "y": 326, "label": "deliver", "variant": "emphasis" },
    { "from": "worker", "to": "provider", "y": 368, "label": "perform work", "variant": "default" },
    { "from": "provider", "to": "worker", "y": 410, "label": "result / timeout", "variant": "return" },
    { "from": "worker", "to": "queue", "y": 452, "label": "retry if timeout", "variant": "dashed" },
    { "from": "worker", "to": "store", "y": 494, "label": "persist final state", "variant": "emphasis" },
    { "from": "worker", "to": "notify", "y": 566, "label": "job.completed", "variant": "dashed" },
    { "from": "notify", "to": "client", "y": 608, "label": "signed webhook", "variant": "dashed" },
    { "from": "client", "to": "api", "y": 650, "label": "GET /jobs/:id", "variant": "default" },
    { "from": "api", "to": "store", "y": 692, "label": "read status", "variant": "default" },
    { "from": "store", "to": "api", "y": 734, "label": "completed", "variant": "return" },
    { "from": "api", "to": "client", "y": 776, "label": "200 final result", "variant": "return" }
  ],
  "activations": [
    { "participant": "api", "from": 174, "to": 272, "type": "backend" },
    { "participant": "queue", "from": 216, "to": 334, "type": "messagebus" },
    { "participant": "worker", "from": 320, "to": 574, "type": "backend" },
    { "participant": "provider", "from": 362, "to": 416, "type": "cloud" },
    { "participant": "store", "from": 488, "to": 742, "type": "database" },
    { "participant": "notify", "from": 560, "to": 616, "type": "messagebus" },
    { "participant": "api", "from": 644, "to": 784, "type": "backend" }
  ],
  "cards": [
    { "dot": "cyan", "title": "Fast Acknowledgement", "items": ["The caller receives a durable job id before work begins", "Queue ownership is visible in the acceptance contract", "The original connection does not wait for provider latency"] },
    { "dot": "orange", "title": "Bounded Recovery", "items": ["Timeouts re-enter the queue with a retry policy", "Final state is persisted before notification", "The job store remains the source of truth"] },
    { "dot": "emerald", "title": "Two Observation Paths", "items": ["A signed webhook announces completion", "Status polling is a fallback, not a second workflow", "Both paths converge on the same final state"] }
  ]
}
```

## examples/brand-aware-delivery.architecture.json

```json
{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Brand-aware AI delivery",
    "quality_profile": "showcase",
    "viewBox": [1120, 640],
    "views": [
      {
        "id": "delivery-path",
        "label": "Delivery path",
        "focus": ["request", "claude", "github", "container", "edge", "customer"],
        "note": "Follow one delivery from the request through the model, repository, container, and edge."
      },
      {
        "id": "business-data",
        "label": "Business and data",
        "focus": ["container", "database", "billing"],
        "note": "Inspect durable state and billing without losing the main delivery path."
      }
    ]
  },
  "components": [
    { "id": "request", "type": "external", "label": "Product request", "sublabel": "Owner brief", "pos": [38, 260], "size": [138, 68] },
    { "id": "claude", "type": "frontend", "label": "Claude", "sublabel": "Plan and author", "brand": "claude", "pos": [220, 260], "size": [138, 68] },
    { "id": "github", "type": "messagebus", "label": "GitHub", "sublabel": "Review and merge", "brand": "github", "pos": [402, 260], "size": [138, 68] },
    { "id": "container", "type": "backend", "label": "Docker service", "sublabel": "Build and run", "brand": "docker", "pos": [584, 260], "size": [138, 68] },
    { "id": "edge", "type": "cloud", "label": "Cloudflare", "sublabel": "Global delivery", "brand": "cloudflare", "pos": [766, 260], "size": [138, 68] },
    { "id": "customer", "type": "external", "label": "Customers", "sublabel": "Web and mobile", "pos": [948, 260], "size": [138, 68] },
    { "id": "database", "type": "database", "label": "PostgreSQL", "sublabel": "Durable state", "brand": "postgresql", "pos": [584, 420], "size": [138, 68] },
    { "id": "billing", "type": "external", "label": "Stripe", "sublabel": "Billing events", "brand": "stripe", "pos": [766, 420], "size": [138, 68] }
  ],
  "connections": [
    { "id": "brief-to-claude", "from": "request", "to": "claude", "label": "brief", "variant": "emphasis" },
    { "id": "claude-to-github", "from": "claude", "to": "github", "label": "change set", "labelDy": -28 },
    { "id": "github-to-container", "from": "github", "to": "container", "label": "approved build", "labelDy": -28 },
    { "id": "container-to-edge", "from": "container", "to": "edge", "label": "deploy" },
    { "id": "edge-to-customer", "from": "edge", "to": "customer", "label": "HTTPS", "variant": "emphasis" },
    { "id": "container-to-database", "from": "container", "to": "database", "label": "SQL", "fromSide": "bottom", "toSide": "top", "labelAt": [625, 370] },
    { "id": "container-to-billing", "from": "container", "to": "billing", "label": "create charge", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "via": [[653, 374], [835, 374]] },
    { "id": "billing-to-database", "from": "billing", "to": "database", "label": "webhook", "variant": "dashed" }
  ],
  "cards": [
    { "dot": "cyan", "title": "Identity at a glance", "items": ["Semantic color still explains technical role", "Brand badges identify the actual products"] },
    { "dot": "amber", "title": "Portable by default", "items": ["Preset marks ship inside Archify", "Every visual export keeps the same badge"] }
  ]
}
```

## examples/cache-miss-request.sequence.json

```json
{
  "schema_version": 1,
  "diagram_type": "sequence",
  "meta": {
    "title": "Cache Miss Request Sequence",
    "output": "examples/sequence-cache-miss-request.html",
    "viewBox": [1080, 560],
    "column_fit": "spread",
    "animation": "trace",
    "quality_profile": "showcase",
    "views": [
      { "id": "request-and-auth", "label": "Request and identity", "focus": ["user", "web", "api", "auth"], "note": "Follow the user request through the authentication check." },
      { "id": "cache-fallback", "label": "Cache fallback", "focus": ["api", "redis", "db"], "note": "See the cache miss and the source-of-truth query it triggers." },
      { "id": "return-and-trace", "label": "Return and trace", "focus": ["db", "api", "redis", "trace", "web", "user"], "note": "Separate response latency from the non-blocking observability write." }
    ]
  },
  "participants": [
    { "id": "user", "type": "external", "label": "User", "sublabel": "browser session" },
    { "id": "web", "type": "frontend", "label": "Web App", "sublabel": "React UI" },
    { "id": "api", "type": "backend", "label": "API", "sublabel": "request handler" },
    { "id": "auth", "type": "security", "label": "Auth", "sublabel": "JWT verify" },
    { "id": "redis", "type": "database", "label": "Redis", "sublabel": "cache" },
    { "id": "db", "type": "database", "label": "Postgres", "sublabel": "source of truth" },
    { "id": "trace", "type": "messagebus", "label": "Trace", "sublabel": "async event" }
  ],
  "segments": [
    { "from": 150, "to": 250, "label": "Request" },
    { "from": 260, "to": 370, "label": "Fallback" },
    { "from": 380, "to": 480, "label": "Response + trace" }
  ],
  "messages": [
    { "id": "open-page", "from": "user", "to": "web", "y": 160, "label": "open page", "variant": "default" },
    { "id": "dashboard-request", "from": "web", "to": "api", "y": 185, "label": "GET /dashboard", "variant": "emphasis" },
    { "id": "verify-jwt", "from": "api", "to": "auth", "y": 210, "label": "verify JWT", "variant": "security" },
    { "id": "auth-claims", "from": "auth", "to": "api", "y": 238, "label": "claims ok", "variant": "return" },
    { "id": "cache-read", "from": "api", "to": "redis", "y": 270, "label": "read cache", "variant": "default" },
    { "id": "cache-miss", "from": "redis", "to": "api", "y": 298, "label": "miss", "variant": "return" },
    { "id": "profile-query", "from": "api", "to": "db", "y": 330, "label": "query profile + metrics", "variant": "emphasis" },
    { "id": "profile-rows", "from": "db", "to": "api", "y": 358, "label": "rows", "variant": "return" },
    { "id": "cache-write", "from": "api", "to": "redis", "y": 390, "label": "set cache", "variant": "dashed" },
    { "id": "trace-emit", "from": "api", "to": "trace", "y": 418, "label": "emit trace", "variant": "dashed" },
    { "id": "dashboard-response", "from": "api", "to": "web", "y": 443, "label": "200 JSON", "variant": "return" },
    { "id": "page-render", "from": "web", "to": "user", "y": 468, "label": "render", "variant": "return" }
  ],
  "activations": [
    { "participant": "web", "from": 180, "to": 474, "type": "frontend" },
    { "participant": "api", "from": 185, "to": 450, "type": "backend" },
    { "participant": "auth", "from": 205, "to": 244, "type": "security" },
    { "participant": "redis", "from": 265, "to": 304, "type": "database" },
    { "participant": "db", "from": 325, "to": 364, "type": "database" },
    { "participant": "trace", "from": 413, "to": 449, "type": "messagebus" }
  ],
  "cards": [
    {
      "dot": "emerald",
      "title": "Happy Path",
      "items": [
        "The main request is Web App -> API -> data source -> response",
        "Return messages are quieter than forward calls",
        "Activation bars make ownership duration visible"
      ]
    },
    {
      "dot": "rose",
      "title": "Policy + Fallback",
      "items": [
        "JWT verification is colored as a security interaction",
        "Cache miss is visible without overpowering the main path",
        "Database access only appears after cache fallback"
      ]
    },
    {
      "dot": "orange",
      "title": "Async Trace",
      "items": [
        "Trace emission is dashed and secondary",
        "It does not block the response path",
        "The diagram separates user-facing latency from observability"
      ]
    }
  ]
}
```

## examples/checkout-platform.base.architecture.json

```json
{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Checkout Platform — Baseline",
    "visual_preset": "signal-flow"
  },
  "components": [
    { "id": "buyers", "type": "external", "label": "Buyers", "sublabel": "Web + mobile", "pos": [40, 250], "size": [120, 60] },
    { "id": "edge", "type": "cloud", "label": "Edge Gateway", "sublabel": "TLS + routing", "pos": [220, 250], "size": [130, 60] },
    { "id": "checkout", "type": "backend", "label": "Checkout API", "sublabel": "v1 service", "pos": [430, 250], "size": [130, 60] },
    { "id": "cache", "type": "database", "label": "Session Cache", "sublabel": "Redis", "pos": [430, 100], "size": [130, 60] },
    { "id": "orders", "type": "database", "label": "Orders", "sublabel": "PostgreSQL", "pos": [640, 250], "size": [130, 60] },
    { "id": "queue", "type": "messagebus", "label": "Order Events", "sublabel": "durable queue", "pos": [430, 400], "size": [130, 60] },
    { "id": "worker", "type": "backend", "label": "Fulfilment", "sublabel": "async worker", "pos": [640, 400], "size": [130, 60] },
    { "id": "payments", "type": "external", "label": "Payment Rail", "sublabel": "external", "pos": [850, 250], "size": [130, 60] }
  ],
  "boundaries": [
    { "kind": "region", "label": "Production region", "wraps": ["edge", "checkout", "cache", "orders", "queue", "worker"] },
    { "kind": "security-group", "label": "Checkout trust zone", "wraps": ["checkout", "orders"] }
  ],
  "connections": [
    { "id": "buyer-request", "from": "buyers", "to": "edge", "label": "HTTPS", "variant": "emphasis" },
    { "id": "edge-checkout", "from": "edge", "to": "checkout" },
    { "id": "session-read", "from": "checkout", "to": "cache", "label": "session", "fromSide": "top", "toSide": "bottom", "labelDy": -66 },
    { "id": "persist-order", "from": "checkout", "to": "orders", "label": "SQL" },
    { "id": "publish-order", "from": "checkout", "to": "queue", "label": "accepted", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 66 },
    { "id": "consume-order", "from": "queue", "to": "worker" },
    { "id": "authorize-payment", "from": "orders", "to": "payments", "label": "authorize", "variant": "security" }
  ]
}
```

## examples/checkout-platform.head.architecture.json

```json
{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Checkout Platform — Fraud Gate",
    "visual_preset": "signal-flow"
  },
  "components": [
    { "id": "buyers", "type": "external", "label": "Buyers", "sublabel": "Web + mobile", "pos": [40, 250], "size": [120, 60] },
    { "id": "edge", "type": "cloud", "label": "Edge Gateway", "sublabel": "TLS + routing", "pos": [220, 250], "size": [130, 60] },
    { "id": "checkout", "type": "backend", "label": "Checkout API", "sublabel": "v2 idempotent", "pos": [430, 250], "size": [130, 60] },
    { "id": "fraud", "type": "security", "label": "Fraud Gate", "sublabel": "policy scoring", "pos": [640, 100], "size": [130, 60], "tag": "new owner" },
    { "id": "orders", "type": "database", "label": "Orders", "sublabel": "PostgreSQL", "pos": [640, 250], "size": [130, 60] },
    { "id": "queue", "type": "messagebus", "label": "Order Events", "sublabel": "durable queue", "pos": [430, 420], "size": [130, 60] },
    { "id": "worker", "type": "backend", "label": "Fulfilment", "sublabel": "async worker", "pos": [640, 400], "size": [130, 60] },
    { "id": "payments", "type": "external", "label": "Payment Rail", "sublabel": "external", "pos": [850, 250], "size": [130, 60] }
  ],
  "boundaries": [
    { "kind": "region", "label": "Production region", "wraps": ["edge", "checkout", "fraud", "orders", "queue", "worker"] },
    { "kind": "security-group", "label": "Checkout trust zone", "wraps": ["checkout", "fraud", "orders"] }
  ],
  "connections": [
    { "id": "buyer-request", "from": "buyers", "to": "edge", "label": "HTTPS", "variant": "emphasis" },
    { "id": "edge-checkout", "from": "edge", "to": "checkout" },
    { "id": "fraud-check", "from": "checkout", "to": "fraud", "label": "screen", "variant": "security", "fromSide": "top", "toSide": "bottom", "labelDy": -66 },
    { "id": "persist-order", "from": "checkout", "to": "orders", "label": "SQL tx" },
    { "id": "publish-order", "from": "checkout", "to": "queue", "label": "accepted", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 76 },
    { "id": "consume-order", "from": "queue", "to": "worker" },
    { "id": "authorize-payment", "from": "fraud", "to": "payments", "label": "authorize", "variant": "security", "fromSide": "right", "toSide": "top", "via": [[820, 130], [915, 130]] }
  ]
}
```

## examples/dataflow-product-analytics.html

```

```

## examples/deployment-release.lifecycle.json

```json
{
  "schema_version": 1,
  "diagram_type": "lifecycle",
  "meta": {
    "title": "Deployment Release Lifecycle",
    "output": "examples/deployment-release.html",
    "viewBox": [980, 680],
    "animation": "trace",
    "visual_preset": "signal-flow",
    "quality_profile": "showcase",
    "views": [
      { "id": "promotion-rail", "label": "Promotion rail", "focus": ["queued", "building", "verifying", "ready", "live"], "note": "Follow the deployment object from accepted change to healthy production." },
      { "id": "approval-gate", "label": "Approval gate", "focus": ["verifying", "approval", "cancelled", "ready"], "note": "Approval pauses promotion and can terminate the release cleanly." },
      { "id": "rollback-outcomes", "label": "Rollback outcomes", "focus": ["ready", "rollback", "failed", "live", "paused", "rolled_back"], "note": "Separate pre-promotion failure from post-promotion health regression." }
    ]
  },
  "lanes": [
    { "id": "main", "label": "Release phases" },
    { "id": "waiting", "label": "Approval + health wait" },
    { "id": "recovery", "label": "Rollback controller" },
    { "id": "terminal", "label": "Terminal exits" }
  ],
  "states": [
    { "id": "queued", "type": "start", "label": "Queued", "sublabel": "change accepted", "lane": "main", "col": 0, "step": "01", "tag": "pending" },
    { "id": "building", "type": "active", "label": "Building", "sublabel": "immutable image", "lane": "main", "col": 1, "step": "02", "tag": "running" },
    { "id": "verifying", "type": "decision", "label": "Verifying", "sublabel": "tests + policy", "lane": "main", "col": 2, "step": "03", "tag": "gate" },
    { "id": "ready", "type": "waiting", "label": "Ready", "sublabel": "promotion pending", "lane": "main", "col": 3, "step": "04", "tag": "approved" },
    { "id": "live", "type": "success", "label": "Live", "sublabel": "production healthy", "lane": "main", "col": 4, "step": "05", "tag": "success" },
    { "id": "approval", "type": "waiting", "label": "Needs Approval", "sublabel": "release owner", "lane": "waiting", "col": 0, "tag": "pause" },
    { "id": "rollback", "type": "active", "label": "Rolling Back", "sublabel": "last good image", "lane": "recovery", "col": 1, "tag": "automatic" },
    { "id": "paused", "type": "waiting", "label": "Health Paused", "sublabel": "SLO regression", "lane": "waiting", "col": 2, "tag": "observe" },
    { "id": "cancelled", "type": "failure", "label": "Cancelled", "sublabel": "approval denied", "lane": "terminal", "col": 0, "tag": "terminal" },
    { "id": "failed", "type": "failure", "label": "Failed", "sublabel": "rollback failed", "lane": "terminal", "col": 1, "tag": "terminal" },
    { "id": "rolled_back", "type": "success", "label": "Rolled Back", "sublabel": "service restored", "lane": "terminal", "col": 2, "tag": "terminal" }
  ],
  "transitions": [
    { "from": "verifying", "to": "approval", "variant": "security", "route": "straight", "fromSide": "bottom", "toSide": "top" },
    { "from": "approval", "to": "cancelled", "variant": "security", "route": "straight", "fromSide": "bottom", "toSide": "top" },
    { "from": "ready", "to": "rollback", "variant": "security", "route": "straight", "fromSide": "bottom", "toSide": "top" },
    { "from": "rollback", "to": "failed", "variant": "security", "route": "straight", "fromSide": "bottom", "toSide": "top" },
    { "from": "live", "to": "paused", "variant": "dashed", "route": "straight", "fromSide": "bottom", "toSide": "top" },
    { "from": "paused", "to": "rolled_back", "variant": "emphasis", "route": "straight", "fromSide": "bottom", "toSide": "top" }
  ],
  "cards": [
    { "dot": "cyan", "title": "Promotion Rail", "items": ["The release object moves through five ordered phases", "Verification and approval remain distinct states", "Live means production health is currently proven"] },
    { "dot": "amber", "title": "Wait States", "items": ["Human approval can pause without consuming a worker", "A health regression pauses further rollout", "Every wait exposes the event required to continue"] },
    { "dot": "rose", "title": "Explicit Endings", "items": ["Denied approval ends as Cancelled", "Rollback controller failure ends as Failed", "Successful rollback is a terminal restored outcome"] }
  ]
}
```

## examples/event-stream.dataflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "dataflow",
  "meta": {
    "title": "Order Event-stream Topology",
    "output": "examples/event-stream.html",
    "viewBox": [1080, 780],
    "animation": "trace",
    "visual_preset": "signal-flow",
    "quality_profile": "showcase",
    "views": [
      { "id": "order-transit", "label": "Order event transit", "focus": ["checkout", "orders", "validate", "state", "fulfillment"], "note": "Follow an order from producer through ordered processing to fulfillment." },
      { "id": "payment-transit", "label": "Payment event transit", "focus": ["billing", "payments", "enrich", "state", "analytics"], "note": "Track payment facts into the shared materialized state and analytics." },
      { "id": "failure-and-replay", "label": "Failure and replay", "focus": ["validate", "enrich", "dlq", "replay", "ops"], "note": "Isolate dead letters, operator review, and controlled replay ownership." }
    ]
  },
  "stages": [
    { "label": "Producers" },
    { "label": "Transit" },
    { "label": "Processors" },
    { "label": "State + recovery" },
    { "label": "Consumers" }
  ],
  "nodes": [
    { "id": "checkout", "type": "frontend", "label": "Checkout API", "sublabel": "order producer", "stage": 0, "row": 0, "tag": "team commerce" },
    { "id": "billing", "type": "backend", "label": "Billing API", "sublabel": "payment producer", "stage": 0, "row": 2, "tag": "team money" },
    { "id": "orders", "type": "messagebus", "label": "orders.v1", "sublabel": "12 partitions", "stage": 1, "row": 0, "tag": "key: order_id" },
    { "id": "payments", "type": "messagebus", "label": "payments.v2", "sublabel": "8 partitions", "stage": 1, "row": 2, "tag": "key: order_id" },
    { "id": "validate", "type": "backend", "label": "Order Validate", "sublabel": "group fulfillment", "stage": 2, "row": 0, "tag": "ordered" },
    { "id": "enrich", "type": "backend", "label": "Payment Enrich", "sublabel": "group analytics", "stage": 2, "row": 2, "tag": "at-least-once" },
    { "id": "state", "type": "database", "label": "Order State", "sublabel": "materialized view", "stage": 3, "row": 1, "tag": "idempotent" },
    { "id": "dlq", "type": "messagebus", "label": "events.dlq", "sublabel": "poison events", "stage": 3, "row": 4, "tag": "7-day retention" },
    { "id": "fulfillment", "type": "backend", "label": "Fulfillment", "sublabel": "shipping workflow", "stage": 4, "row": 0, "tag": "consumer" },
    { "id": "analytics", "type": "database", "label": "Analytics", "sublabel": "streaming facts", "stage": 4, "row": 2, "tag": "consumer" },
    { "id": "replay", "type": "security", "label": "Replay Tool", "sublabel": "approved batch", "stage": 4, "row": 4, "tag": "operator gate" },
    { "id": "ops", "type": "external", "label": "On-call", "sublabel": "DLQ owner", "stage": 4, "row": 3, "yOffset": -18, "tag": "SRE" }
  ],
  "flows": [
    { "from": "checkout", "to": "orders", "label": "OrderPlaced", "classification": "schema v1", "variant": "emphasis", "route": "straight" },
    { "from": "billing", "to": "payments", "label": "PaymentCaptured", "classification": "schema v2", "variant": "emphasis", "route": "straight" },
    { "from": "orders", "to": "validate", "label": "ordered orders", "classification": "consumer group", "variant": "emphasis", "route": "straight" },
    { "from": "payments", "to": "enrich", "label": "payment facts", "classification": "at-least-once", "variant": "emphasis", "route": "straight" },
    { "from": "validate", "to": "state", "label": "valid order", "classification": "idempotent", "variant": "emphasis", "route": "vertical-channel" },
    { "from": "enrich", "to": "state", "label": "enriched payment", "classification": "idempotent", "variant": "default", "route": "vertical-channel" },
    { "from": "state", "to": "fulfillment", "label": "ready orders", "classification": "read model", "variant": "emphasis", "route": "vertical-channel" },
    { "from": "state", "to": "analytics", "label": "order facts", "classification": "non-PII", "variant": "default", "route": "vertical-channel" },
    { "from": "validate", "to": "dlq", "label": "invalid event", "classification": "dead letter", "variant": "security", "fromSide": "top", "toSide": "top", "via": [[530, 80], [20, 80], [20, 550], [745, 550]], "labelAt": [300, 550] },
    { "from": "enrich", "to": "dlq", "label": "poison event", "classification": "dead letter", "variant": "security", "route": "bottom-channel", "labelDy": 30 },
    { "from": "dlq", "to": "ops", "label": "failure sample", "classification": "restricted", "variant": "security", "route": "vertical-channel" },
    { "from": "dlq", "to": "replay", "label": "approved replay", "classification": "audited batch", "variant": "dashed", "route": "straight", "labelDy": 30 }
  ],
  "cards": [
    { "dot": "amber", "title": "Transit Contract", "items": ["Every event and topic is named", "Partition keys preserve per-order ordering", "Consumer groups expose processing ownership"] },
    { "dot": "emerald", "title": "State + Delivery", "items": ["Processors write an idempotent materialized view", "Fulfillment and analytics consume distinct assets", "At-least-once delivery never implies duplicate business effects"] },
    { "dot": "rose", "title": "Failure Ownership", "items": ["Poison events land in a retained dead-letter topic", "On-call inspects samples before replay", "Replay is gated, batched, and auditable"] }
  ]
}
```

## examples/incident-response.workflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "workflow",
  "meta": {
    "title": "Incident Response Runbook",
    "output": "examples/incident-response.html",
    "animation": "trace",
    "visual_preset": "signal-flow",
    "quality_profile": "showcase",
    "views": [
      { "id": "detect-and-triage", "label": "Detect and establish command", "focus": ["alert", "page", "triage", "declare"], "note": "Follow the first minutes from signal to an owned incident." },
      { "id": "mitigate-and-verify", "label": "Mitigate and prove recovery", "focus": ["triage", "contain", "recover", "verify", "close"], "note": "Keep mitigation separate from the evidence required to close." },
      { "id": "escalate-and-communicate", "label": "Escalation and communication", "focus": ["declare", "escalate", "update", "rollback"], "note": "See who is paged, what stakeholders hear, and when rollback begins." }
    ]
  },
  "lanes": [
    { "id": "signals", "label": "Signals" },
    { "id": "responders", "label": "Incident Command" },
    { "id": "mitigation", "label": "Service Mitigation" },
    { "id": "recovery", "label": "Recovery Evidence" },
    { "id": "communication", "label": "Stakeholder Communication" },
    { "id": "exceptions", "label": "Escalation + Rollback", "variant": "exception" }
  ],
  "phases": [
    { "id": "detect", "label": "Detect", "fromCol": 0, "toCol": 1 },
    { "id": "respond", "label": "Triage + mitigate", "fromCol": 2, "toCol": 3, "variant": "emphasis" },
    { "id": "recover", "label": "Verify + close", "fromCol": 4, "toCol": 5, "variant": "dashed" }
  ],
  "groups": [
    { "id": "command", "label": "Incident command", "lane": "responders", "fromCol": 1, "toCol": 3, "variant": "emphasis" },
    { "id": "exception_actions", "label": "If impact persists", "lane": "exceptions", "fromCol": 3, "toCol": 5, "variant": "security" }
  ],
  "mainPath": ["alert", "page", "triage", "contain", "recover", "verify", "close"],
  "nodes": [
    { "id": "alert", "lane": "signals", "col": 0, "type": "messagebus", "label": "SLO Alert", "sublabel": "burn rate" },
    { "id": "page", "lane": "responders", "col": 1, "width": 76, "type": "external", "label": "Page On-call", "sublabel": "acknowledge" },
    { "id": "triage", "lane": "responders", "col": 2, "width": 64, "type": "backend", "label": "Triage", "sublabel": "scope impact" },
    { "id": "declare", "lane": "responders", "col": 3, "type": "security", "label": "Declare", "sublabel": "assign commander", "tag": "SEV-1/2" },
    { "id": "contain", "lane": "mitigation", "col": 3, "width": 72, "type": "backend", "label": "Contain", "sublabel": "stop growth" },
    { "id": "recover", "lane": "mitigation", "col": 4, "width": 52, "type": "cloud", "label": "Recover", "sublabel": "restore" },
    { "id": "verify", "lane": "recovery", "col": 5, "type": "database", "label": "Verify", "sublabel": "SLO + traces", "tag": "15 min stable" },
    { "id": "close", "lane": "communication", "col": 5, "type": "external", "label": "Resolve", "sublabel": "final update" },
    { "id": "update", "lane": "communication", "col": 3, "type": "frontend", "label": "Status Update", "sublabel": "impact + ETA" },
    { "id": "escalate", "lane": "exceptions", "col": 3, "width": 72, "type": "security", "label": "Escalate", "sublabel": "specialist" },
    { "id": "rollback", "lane": "exceptions", "col": 5, "type": "messagebus", "label": "Rollback", "sublabel": "last good" }
  ],
  "edges": [
    { "from": "alert", "to": "page", "label": "page", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "page", "to": "triage", "route": "bottom-channel", "fromSide": "bottom", "toSide": "bottom" },
    { "from": "triage", "to": "contain", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "contain", "to": "recover", "route": "bottom-channel", "fromSide": "bottom", "toSide": "bottom" },
    { "from": "recover", "to": "verify", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "verify", "to": "close", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "triage", "to": "declare", "variant": "security" },
    { "from": "declare", "to": "update", "variant": "dashed", "fromSide": "top", "toSide": "left", "via": [[430, 16], [20, 16], [20, 615]] },
    { "from": "update", "to": "escalate", "variant": "security", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "verify", "to": "rollback", "variant": "security", "role": "error", "route": "outside-right", "fromSide": "right", "toSide": "right" }
  ],
  "cards": [
    { "dot": "rose", "title": "Ownership First", "items": ["A page is not an incident until someone owns command", "Severity and scope are explicit before mitigation spreads", "Escalation names the missing expertise"] },
    { "dot": "emerald", "title": "Recovery Is Evidence", "items": ["Mitigation can reduce impact without proving recovery", "SLOs and traces must stay healthy for a fixed window", "The final update follows verification, not optimism"] },
    { "dot": "cyan", "title": "Communication Contract", "items": ["Stakeholders receive impact, action, and next update time", "Rollback remains visible as a deliberate response", "Every branch has an owner and observable exit"] }
  ]
}
```

## examples/lifecycle-agent-run.html

```

```

## examples/product-analytics.dataflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "dataflow",
  "meta": {
    "title": "Product Analytics Data Flow",
    "output": "examples/dataflow-product-analytics.html",
    "viewBox": [1080, 520],
    "animation": "trace",
    "quality_profile": "showcase",
    "views": [
      { "id": "collection-path", "label": "Collection path", "focus": ["web", "mobile", "edge", "stream"], "note": "Follow product events from clients into the ordered event stream." },
      { "id": "consent-boundary", "label": "Consent and PII", "focus": ["edge", "consent", "pii"], "note": "Isolate the policy gate and restricted identity store." },
      { "id": "analytics-consumers", "label": "Curated consumers", "focus": ["stream", "warehouse", "dashboard", "features", "model"], "note": "See curated facts, dashboards, and the derived feature path." }
    ]
  },
  "stages": [
    { "label": "Sources" },
    { "label": "Ingest" },
    { "label": "Process" },
    { "label": "Store" },
    { "label": "Consume" }
  ],
  "nodes": [
    { "id": "web", "type": "frontend", "label": "Web App", "sublabel": "browser SDK", "stage": 0, "row": 0, "tag": "events" },
    { "id": "mobile", "type": "frontend", "label": "Mobile", "sublabel": "iOS / Android", "stage": 0, "row": 2, "tag": "events" },
    { "id": "edge", "type": "cloud", "label": "Edge API", "sublabel": "collector", "stage": 1, "row": 1, "tag": "TLS" },
    { "id": "consent", "type": "security", "label": "Consent Gate", "sublabel": "policy filter", "stage": 2, "row": 0, "tag": "PII guard" },
    { "id": "stream", "type": "messagebus", "label": "Event Stream", "sublabel": "Kafka topic", "stage": 2, "row": 2, "tag": "ordered" },
    { "id": "pii", "type": "security", "label": "PII Vault", "sublabel": "encrypted", "stage": 3, "row": 0, "tag": "restricted" },
    { "id": "warehouse", "type": "database", "label": "Warehouse", "sublabel": "analytics tables", "stage": 3, "row": 1, "tag": "curated" },
    { "id": "features", "type": "database", "label": "Feature Store", "sublabel": "daily batch", "stage": 3, "row": 2, "tag": "derived" },
    { "id": "dashboard", "type": "backend", "label": "Dashboards", "sublabel": "product metrics", "stage": 4, "row": 0, "tag": "SQL" },
    { "id": "model", "type": "backend", "label": "ML Model", "sublabel": "ranking job", "stage": 4, "row": 2, "tag": "features" }
  ],
  "flows": [
    { "id": "web-clickstream", "from": "web", "to": "edge", "label": "clickstream", "classification": "user events", "variant": "emphasis", "fromSide": "right", "toSide": "left", "via": [[205, 157], [205, 271]], "labelAt": [204, 190] },
    { "id": "mobile-events", "from": "mobile", "to": "edge", "label": "app events", "classification": "device events", "variant": "default", "fromSide": "right", "toSide": "left", "via": [[222, 385], [222, 271]], "labelAt": [220, 342] },
    { "id": "consent-enrichment", "from": "edge", "to": "consent", "label": "identity + consent", "classification": "PII touch", "variant": "security", "fromSide": "top", "toSide": "left", "via": [[315, 112], [450, 112], [450, 157]], "labelAt": [382, 100] },
    { "id": "accepted-events", "from": "edge", "to": "stream", "label": "accepted events", "classification": "append-only", "variant": "emphasis", "fromSide": "right", "toSide": "left", "via": [[420, 271], [420, 385]], "labelAt": [438, 324] },
    { "id": "identity-map", "from": "consent", "to": "pii", "label": "identity map", "classification": "encrypted PII", "variant": "security", "route": "straight", "labelAt": [638, 144] },
    { "id": "normalized-facts", "from": "stream", "to": "warehouse", "label": "normalized facts", "classification": "non-PII", "variant": "emphasis", "fromSide": "right", "toSide": "left", "via": [[638, 385], [638, 271]], "labelAt": [638, 326] },
    { "id": "daily-aggregates", "from": "warehouse", "to": "features", "label": "daily aggregates", "classification": "batch", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "route": "straight", "labelAt": [745, 326] },
    { "id": "metrics-query", "from": "warehouse", "to": "dashboard", "label": "metrics SQL", "classification": "read-only", "variant": "default", "fromSide": "right", "toSide": "bottom", "via": [[852, 271], [960, 271]], "labelAt": [876, 258] },
    { "id": "feature-vectors", "from": "features", "to": "model", "label": "feature vectors", "classification": "derived", "variant": "dashed", "route": "straight", "labelAt": [852, 372] },
    { "id": "restricted-join", "from": "pii", "to": "dashboard", "label": "restricted join", "classification": "approved only", "variant": "security", "route": "straight", "labelAt": [852, 144] }
  ],
  "cards": [
    {
      "dot": "emerald",
      "title": "Primary Data Path",
      "items": [
        "Events move left to right through source, ingest, process, store, and consume stages",
        "The hot path stays visually clear even with secondary batch flows",
        "Labels name data assets instead of generic API verbs"
      ]
    },
    {
      "dot": "rose",
      "title": "Sensitive Boundary",
      "items": [
        "Consent and PII paths are styled as security flows",
        "PII lands in a restricted vault, separate from the analytics warehouse",
        "Restricted joins are visible without implying default access"
      ]
    },
    {
      "dot": "orange",
      "title": "Derived Consumers",
      "items": [
        "Dashboards read curated facts from the warehouse",
        "Feature vectors are derived by batch from analytics tables",
        "Consumption paths stay distinct from collection and consent handling"
      ]
    }
  ]
}
```

## examples/production-deployment.architecture.json

```json
{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Production Deployment Ownership",
    "output": "examples/production-deployment.html",
    "visual_preset": "blueprint",
    "animation": "trace",
    "quality_profile": "showcase",
    "engineering_profile": "deployment-ownership",
    "views": [
      {
        "id": "request-boundary",
        "label": "Request crosses the edge",
        "focus": ["clients", "edge", "gateway", "api_a", "api_b"],
        "note": "Follow public traffic into the private application network."
      },
      {
        "id": "state-ownership",
        "label": "State and ownership",
        "focus": ["api_a", "api_b", "redis", "postgres", "replica"],
        "note": "Separate stateless platform workloads from data-team-owned state."
      },
      {
        "id": "async-operations",
        "label": "Async and operations",
        "focus": ["api_b", "events", "worker", "audit", "observability"],
        "note": "See the asynchronous work and the evidence it emits."
      }
    ]
  },
  "components": [
    { "id": "clients", "type": "external", "label": "Customers", "sublabel": "web + mobile", "pos": [38, 300], "size": [122, 60] },
    { "id": "edge", "type": "cloud", "label": "Global Edge", "sublabel": "CDN + WAF", "pos": [230, 300], "size": [126, 60], "tag": "edge team" },
    { "id": "gateway", "type": "security", "label": "API Gateway", "sublabel": "public :443", "pos": [430, 300], "size": [128, 60], "tag": "platform" },
    { "id": "api_a", "type": "backend", "label": "API Pods / AZ-a", "sublabel": "private subnet", "pos": [630, 195], "size": [136, 62], "tag": "app team" },
    { "id": "api_b", "type": "backend", "label": "API Pods / AZ-b", "sublabel": "private subnet", "pos": [630, 405], "size": [136, 62], "tag": "app team" },
    { "id": "redis", "type": "database", "label": "Redis", "sublabel": "multi-AZ cache", "pos": [840, 195], "size": [126, 62], "tag": "platform" },
    { "id": "postgres", "type": "database", "label": "PostgreSQL", "sublabel": "primary / encrypted", "pos": [840, 405], "size": [126, 62], "tag": "data team" },
    { "id": "events", "type": "messagebus", "label": "Event Bus", "sublabel": "orders.v1", "pos": [1040, 300], "size": [126, 60], "tag": "platform" },
    { "id": "worker", "type": "backend", "label": "Workers", "sublabel": "private workload", "pos": [1190, 300], "size": [126, 60], "tag": "app team" },
    { "id": "replica", "type": "database", "label": "DR Replica", "sublabel": "eu-west-1", "pos": [1040, 578], "size": [126, 62], "tag": "data team" },
    { "id": "audit", "type": "cloud", "label": "Audit Archive", "sublabel": "immutable objects", "pos": [1190, 450], "size": [126, 62], "tag": "security" },
    { "id": "observability", "type": "external", "label": "Observability", "sublabel": "metrics + traces", "pos": [1190, 85], "size": [126, 62], "tag": "SRE" }
  ],
  "boundaries": [
    { "kind": "region", "label": "AWS us-east-1 / production", "wraps": ["edge", "gateway", "api_a", "api_b", "redis", "postgres", "events", "worker", "audit"], "pad": 20 },
    { "kind": "security-group", "label": "private application network", "wraps": ["api_a", "api_b", "redis", "postgres", "events", "worker"], "pad": 14 },
    { "kind": "region", "label": "AWS eu-west-1 / disaster recovery", "wraps": ["replica"] },
    { "kind": "security-group", "label": "DR private subnet", "wraps": ["replica"], "pad": 14 }
  ],
  "connections": [
    { "from": "clients", "to": "edge", "label": "HTTPS", "variant": "emphasis" },
    { "from": "edge", "to": "gateway", "label": "mTLS", "variant": "security" },
    { "from": "gateway", "to": "api_a", "label": "VPC route", "variant": "emphasis", "route": "orthogonal-h", "labelAt": [594, 275] },
    { "from": "gateway", "to": "api_b", "label": "VPC route", "variant": "emphasis", "route": "orthogonal-h", "labelAt": [594, 385] },
    { "from": "api_a", "to": "redis", "label": "cache", "route": "straight" },
    { "from": "api_b", "to": "postgres", "label": "SQL", "route": "straight" },
    { "from": "api_a", "to": "events", "label": "publish", "variant": "dashed", "fromSide": "top", "toSide": "top", "via": [[698, 170], [1103, 170]] },
    { "from": "api_b", "to": "events", "variant": "dashed", "fromSide": "top", "toSide": "bottom", "via": [[698, 380], [1103, 380]] },
    { "from": "events", "to": "worker", "variant": "emphasis" },
    { "from": "postgres", "to": "replica", "label": "cross-region WAL", "variant": "security", "route": "orthogonal-v", "labelAt": [1003, 529] },
    { "from": "worker", "to": "audit", "label": "evidence", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 },
    { "from": "worker", "to": "observability", "label": "OTLP", "variant": "dashed", "route": "orthogonal-v" }
  ],
  "cards": [
    { "dot": "cyan", "title": "Runtime Ownership", "items": ["Platform owns the edge, gateway, cache, and event bus", "Application teams own API pods and workers", "Data owns primary and disaster-recovery state"] },
    { "dot": "rose", "title": "Named Crossings", "items": ["Public HTTPS terminates at the managed edge", "mTLS crosses into the application network", "Cross-region WAL is explicit and encrypted"] },
    { "dot": "emerald", "title": "Operational Evidence", "items": ["Workers emit traces to SRE-owned observability", "Audit evidence lands in immutable storage", "Unknown placement should remain marked, never invented"] }
  ]
}
```

## examples/release-delivery.workflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "workflow",
  "meta": {
    "title": "Release Delivery Workflow",
    "output": "examples/release-delivery.html",
    "animation": "trace",
    "quality_profile": "showcase",
    "views": [
      { "id": "commit-to-checks", "label": "Commit to green build", "focus": ["commit", "pull_request", "build", "checks"], "note": "Follow the change through reproducible build and blocking quality gates." },
      { "id": "approval-to-production", "label": "Approve and promote", "focus": ["checks", "approval", "deploy", "verify_prod", "announce"], "note": "See who authorizes production and how success is verified." },
      { "id": "rollback-path", "label": "Failure and rollback", "focus": ["checks", "failed", "verify_prod", "rollback", "deploy"], "note": "Isolate the two places where delivery stops or reverses safely." }
    ]
  },
  "lanes": [
    { "id": "dev", "label": "Developer" },
    { "id": "ci", "label": "Continuous Integration" },
    { "id": "approval", "label": "Release Governance" },
    { "id": "environment", "label": "Production Environment" },
    { "id": "communication", "label": "Release Communication" },
    { "id": "exceptions", "label": "Failure + Rollback", "variant": "exception" }
  ],
  "phases": [
    { "id": "change", "label": "Change", "fromCol": 0, "toCol": 1 },
    { "id": "verify", "label": "Build + verify", "fromCol": 2, "toCol": 3, "variant": "emphasis" },
    { "id": "promote", "label": "Promote + observe", "fromCol": 4, "toCol": 5, "variant": "dashed" }
  ],
  "groups": [
    { "id": "blocking_checks", "label": "Blocking checks", "lane": "ci", "fromCol": 2, "toCol": 3, "variant": "emphasis" },
    { "id": "rollback_work", "label": "Recovery path", "lane": "exceptions", "fromCol": 3, "toCol": 5, "variant": "security" }
  ],
  "mainPath": ["commit", "pull_request", "build", "checks", "approval", "deploy", "verify_prod", "announce"],
  "nodes": [
    { "id": "commit", "lane": "dev", "col": 0, "type": "frontend", "label": "Commit", "sublabel": "signed change" },
    { "id": "pull_request", "lane": "dev", "col": 1, "type": "frontend", "label": "Pull Request", "sublabel": "reviewed diff" },
    { "id": "build", "lane": "ci", "col": 2, "type": "backend", "label": "Build", "sublabel": "locked inputs", "tag": "reproducible" },
    { "id": "checks", "lane": "ci", "col": 3, "type": "security", "label": "Quality Gates", "sublabel": "test + scan", "tag": "blocking" },
    { "id": "approval", "lane": "approval", "col": 4, "type": "security", "label": "Approve", "sublabel": "release owner", "tag": "human gate" },
    { "id": "deploy", "lane": "environment", "col": 4, "type": "cloud", "label": "Deploy", "sublabel": "canary 10%", "tag": "production" },
    { "id": "verify_prod", "lane": "environment", "col": 5, "type": "backend", "label": "Verify", "sublabel": "smoke + SLO" },
    { "id": "announce", "lane": "communication", "col": 5, "type": "external", "label": "Announce", "sublabel": "status + notes" },
    { "id": "failed", "lane": "exceptions", "col": 2, "type": "security", "label": "Stop Release", "sublabel": "gate failed" },
    { "id": "rollback", "lane": "exceptions", "col": 4, "width": 64, "type": "messagebus", "label": "Rollback", "sublabel": "last good image", "tag": "owner: on-call" }
  ],
  "edges": [
    { "from": "commit", "to": "pull_request" },
    { "from": "pull_request", "to": "build", "label": "merge", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "build", "to": "checks" },
    { "from": "checks", "to": "approval", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "approval", "to": "deploy", "variant": "security", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "deploy", "to": "verify_prod" },
    { "from": "verify_prod", "to": "announce", "label": "healthy", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "checks", "to": "failed", "label": "red", "variant": "security", "role": "error", "route": "drop", "fromSide": "bottom", "toSide": "top" },
    { "from": "verify_prod", "to": "rollback", "variant": "security", "role": "error", "route": "outside-right", "fromSide": "right", "toSide": "right" },
    { "from": "rollback", "to": "deploy", "label": "restore", "variant": "dashed", "role": "return", "route": "return-left", "fromSide": "left", "toSide": "left" }
  ],
  "cards": [
    { "dot": "emerald", "title": "One Happy Path", "items": ["Every change is reviewed before a reproducible build", "Blocking checks must be green before human approval", "Production is complete only after smoke and SLO verification"] },
    { "dot": "rose", "title": "Stop Conditions", "items": ["Test or security failure stops promotion", "Production health can reverse a release", "Rollback ownership is visible before an incident"] },
    { "dot": "cyan", "title": "Release Evidence", "items": ["Approval, immutable image, and check results are retained", "The release announcement follows verification", "The main path remains readable without hiding failure"] }
  ]
}
```

## examples/sequence-cache-miss-request.html

```

```

## examples/web-app-rendered.html

```

```

## examples/web-app.architecture.json

```json
{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Sample Web App",
    "output": "web-app-rendered.html",
    "quality_profile": "showcase",
    "views": [
      { "id": "request-path", "label": "Primary request path", "focus": ["users", "cdn", "lb", "api", "db"], "note": "Follow the primary customer request from the edge to durable state." },
      { "id": "identity-and-cache", "label": "Identity and cache", "focus": ["auth", "api", "cache"], "note": "Isolate authentication and the read-through cache beside the request path." },
      { "id": "async-work", "label": "Static and async work", "focus": ["cdn", "s3", "api", "queue", "worker"], "note": "See the two secondary paths without adding noise to the main request." }
    ]
  },
  "components": [
    { "id": "users", "type": "external", "label": "Users", "sublabel": "Browser / Mobile", "pos": [40, 300], "size": [120, 60] },
    { "id": "auth", "type": "security", "label": "Auth Provider", "sublabel": "OAuth 2.0", "pos": [40, 110], "size": [120, 64], "tag": "JWT + PKCE" },
    { "id": "cdn", "type": "cloud", "label": "CloudFront", "sublabel": "CDN", "pos": [250, 300], "size": [130, 60] },
    { "id": "lb", "type": "cloud", "label": "Load Balancer", "sublabel": "HTTPS :443", "pos": [460, 300], "size": [130, 60] },
    { "id": "api", "type": "backend", "label": "API Server", "sublabel": "FastAPI :8000", "pos": [670, 300], "size": [130, 60] },
    { "id": "cache", "type": "database", "label": "Redis", "sublabel": "cache :6379", "pos": [670, 150], "size": [130, 60] },
    { "id": "db", "type": "database", "label": "PostgreSQL", "sublabel": "primary :5432", "pos": [880, 300], "size": [130, 60] },
    { "id": "s3", "type": "cloud", "label": "S3", "sublabel": "static assets", "pos": [250, 440], "size": [130, 60], "tag": "OAI protected" },
    { "id": "queue", "type": "messagebus", "label": "SQS", "sublabel": "job queue", "pos": [670, 440], "size": [130, 60] },
    { "id": "worker", "type": "backend", "label": "Worker", "sublabel": "async jobs", "pos": [880, 440], "size": [130, 60] }
  ],
  "boundaries": [
    { "kind": "region", "label": "AWS Region: us-west-2", "wraps": ["cdn", "lb", "api", "cache", "db", "s3", "queue", "worker"] },
    { "kind": "security-group", "label": "sg-api :443/:8000", "wraps": ["lb", "api"] }
  ],
  "connections": [
    { "id": "users-to-cdn", "from": "users", "to": "cdn", "label": "HTTPS", "variant": "emphasis" },
    { "id": "jwt-verification", "from": "auth", "to": "api", "label": "verify JWT", "variant": "security", "fromSide": "right", "toSide": "top", "via": [[620, 142], [620, 246], [735, 246]] },
    { "id": "cdn-to-lb", "from": "cdn", "to": "lb" },
    { "id": "static-assets", "from": "cdn", "to": "s3", "label": "static", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 },
    { "id": "lb-to-api", "from": "lb", "to": "api" },
    { "id": "cache-read-through", "from": "api", "to": "cache", "label": "read-through", "fromSide": "top", "toSide": "bottom", "labelDy": -68 },
    { "id": "api-sql", "from": "api", "to": "db", "label": "SQL" },
    { "id": "enqueue-job", "from": "api", "to": "queue", "label": "enqueue", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 },
    { "id": "queue-to-worker", "from": "queue", "to": "worker" }
  ],
  "cards": [
    { "dot": "cyan", "title": "Edge", "items": ["CloudFront CDN fronts all traffic", "S3 serves static assets via OAI"] },
    { "dot": "emerald", "title": "Application", "items": ["FastAPI behind an HTTPS load balancer", "Redis read-through cache", "Async work drained from SQS by a worker"] },
    { "dot": "rose", "title": "Security", "items": ["OAuth 2.0 with JWT + PKCE", "API + LB isolated in a security group"] }
  ]
}
```

## examples/workflow-agent-tool-call-rendered.html

```

```

## migrations

```

```

## migrations/workflow-v2.mjs

```js
import { compileWorkflow } from '../renderers/workflow/workflow-compiler.mjs';
import {
  createMappedWorkflowCandidate,
  intrinsicWorkflow,
  planningWorkflow,
} from '../renderers/workflow/workflow-migration-geometry.mjs';
import { validateSchema } from '../renderers/shared/validator.mjs';

export { createHorizontalRankMapper } from '../renderers/workflow/workflow-migration-geometry.mjs';

const TARGET_SCHEMA_VERSION = 2;

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

function diagnostic({ code, message, subject = {}, evidence = {}, supportedFixes = [] }) {
  return {
    code,
    severity: 'error',
    message,
    subject,
    evidence,
    supportedFixes,
  };
}

function schemaDiagnostics(workflow) {
  try {
    validateSchema('workflow', workflow);
    return [];
  } catch (error) {
    if (Array.isArray(error?.archifyDiagnostics)) {
      return error.archifyDiagnostics.map((entry) => ({ ...entry }));
    }
    throw error;
  }
}

function legacyLayoutProbe(workflow, qualityProfile) {
  // The probe discovers fixed-v1 rank centers, not authored canvas capacity.
  // Omitting viewBox lets a capacity-only legacy failure reach the v2 compiler,
  // which can measure and monotonically expand the real migrated document.
  const probe = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: {
      title: workflow.meta.title,
      ...(workflow.meta.locale ? { locale: workflow.meta.locale } : {}),
      legend: { mode: 'hidden' },
    },
    lanes: clone(workflow.lanes),
    nodes: [{
      id: 'migration_probe',
      lane: workflow.lanes[0].id,
      col: 0,
      type: 'backend',
      label: 'Probe',
    }],
    edges: [],
  };
  return compileWorkflow({ workflow: probe, qualityProfile });
}

function legacyRequirementProbe(workflow, qualityProfile) {
  // Measure the complete fixed-v1 document without treating an authored
  // viewBox as its intrinsic requirement. The authored viewBox remains a
  // migration capacity and is preserved separately on the migrated document.
  const probe = clone(workflow);
  delete probe.meta.viewBox;
  return compileWorkflow({ workflow: probe, qualityProfile });
}

function requiredViewBoxFrom(result) {
  if (Array.isArray(result?.receipt?.requiredViewBox)) {
    return [...result.receipt.requiredViewBox];
  }
  const required = result?.diagnostics
    ?.map((entry) => entry?.evidence?.requiredViewBox)
    .find((candidate) => Array.isArray(candidate) && candidate.length === 2);
  return required ? [...required] : null;
}

function expandableViewBox(result) {
  if (result.ok || !result.diagnostics?.length) return null;
  if (!result.diagnostics.every((entry) => entry.code === 'workflow/viewbox-capacity')) return null;
  return requiredViewBoxFrom(result);
}

function result({
  ok,
  document,
  fromSchemaVersion = 1,
  preExistingDiagnostics = [],
  migrationDiagnostics = [],
  newSchemaDiagnostics = [],
  changedCoordinates = [],
  oldRequiredViewBox = null,
  newRequiredViewBox = null,
}) {
  return {
    ok,
    ...(document ? { document } : {}),
    fromSchemaVersion,
    toSchemaVersion: TARGET_SCHEMA_VERSION,
    preExistingDiagnostics,
    migrationDiagnostics,
    newSchemaDiagnostics,
    changedCoordinates,
    oldRequiredViewBox,
    newRequiredViewBox,
  };
}

export function migrateWorkflowDocument(inputWorkflow) {
  if (!inputWorkflow || typeof inputWorkflow !== 'object' || Array.isArray(inputWorkflow)) {
    return result({
      ok: false,
      migrationDiagnostics: [diagnostic({
        code: 'migration/source-document',
        message: 'Workflow migration requires one parsed JSON object.',
        supportedFixes: ['provide one workflow schema v1 JSON document'],
      })],
    });
  }
  // Migration has no quality override: the authored policy (or effective
  // standard default) must validate the document after it leaves this process.
  const qualityProfile = inputWorkflow.meta?.quality_profile || 'standard';

  const workflow = clone(inputWorkflow);
  const preExistingDiagnostics = schemaDiagnostics(workflow);
  if (preExistingDiagnostics.length) {
    return result({
      ok: false,
      fromSchemaVersion: workflow.schema_version,
      preExistingDiagnostics,
    });
  }
  if (workflow.schema_version === TARGET_SCHEMA_VERSION) {
    const compiled = compileWorkflow({ workflow: clone(workflow), qualityProfile });
    const requiredViewBox = requiredViewBoxFrom(compiled);
    if (!compiled.ok) {
      return result({
        ok: false,
        fromSchemaVersion: TARGET_SCHEMA_VERSION,
        preExistingDiagnostics: compiled.diagnostics,
        oldRequiredViewBox: requiredViewBox,
        newRequiredViewBox: requiredViewBox,
      });
    }
    return result({
      ok: true,
      document: workflow,
      fromSchemaVersion: TARGET_SCHEMA_VERSION,
      oldRequiredViewBox: requiredViewBox,
      newRequiredViewBox: requiredViewBox,
    });
  }
  if (workflow.schema_version !== 1) {
    return result({
      ok: false,
      fromSchemaVersion: workflow.schema_version,
      migrationDiagnostics: [diagnostic({
        code: 'migration/source-schema-version',
        message: 'Workflow migration to schema v2 requires a schema v1 or v2 source.',
        subject: { path: '/schema_version' },
        evidence: { actual: workflow.schema_version, expected: [1, 2] },
        supportedFixes: ['use an unchanged schema v1 workflow or an already migrated schema v2 workflow as the source'],
      })],
    });
  }

  const legacy = compileWorkflow({ workflow: clone(workflow), qualityProfile });
  const legacyProbe = legacyLayoutProbe(workflow, qualityProfile);
  if (!legacyProbe.ok) {
    return result({
      ok: false,
      preExistingDiagnostics: legacy.ok ? [] : legacy.diagnostics,
      migrationDiagnostics: legacyProbe.diagnostics,
    });
  }
  const legacyRequirement = legacyRequirementProbe(workflow, qualityProfile);
  const oldRequiredViewBox = requiredViewBoxFrom(legacyRequirement)
    || requiredViewBoxFrom(legacyProbe)
    || requiredViewBoxFrom(legacy);
  const preExistingLayoutDiagnostics = legacy.ok ? [] : legacy.diagnostics;

  let planned = compileWorkflow({ workflow: intrinsicWorkflow(workflow), qualityProfile });
  if (!planned.ok) {
    // Old absolute pins can be invalid at the new rank centers before their X
    // coordinates are mapped. Obtain the same rank plan from an automatic-route
    // projection, then validate every authored pin again after mapping.
    planned = compileWorkflow({ workflow: planningWorkflow(workflow), qualityProfile });
  }
  if (!planned.ok) {
    return result({
      ok: false,
      preExistingDiagnostics: preExistingLayoutDiagnostics,
      newSchemaDiagnostics: planned.diagnostics,
      oldRequiredViewBox,
      newRequiredViewBox: requiredViewBoxFrom(planned),
    });
  }

  let mappedCandidate;
  try {
    mappedCandidate = createMappedWorkflowCandidate(
      workflow,
      legacyProbe.receipt.columns,
      planned.receipt.columns,
    );
  } catch (error) {
    return result({
      ok: false,
      preExistingDiagnostics: preExistingLayoutDiagnostics,
      migrationDiagnostics: [diagnostic({
        code: 'migration/rank-mapping',
        message: 'Could not construct a stable horizontal rank mapping.',
        evidence: { reason: error.message },
        supportedFixes: ['report the workflow and compiler receipts to the Archify maintainers'],
      })],
      oldRequiredViewBox,
      newRequiredViewBox: requiredViewBoxFrom(planned),
    });
  }

  const { document: migrated, changedCoordinates } = mappedCandidate;

  let compiled = compileWorkflow({ workflow: migrated, qualityProfile });
  const requiredExpansion = migrated.meta.viewBox ? expandableViewBox(compiled) : null;
  if (requiredExpansion) {
    const current = migrated.meta.viewBox;
    const expanded = [
      Math.max(current[0], requiredExpansion[0]),
      Math.max(current[1], requiredExpansion[1]),
    ];
    if (expanded[0] > current[0] || expanded[1] > current[1]) {
      migrated.meta.viewBox = expanded;
      compiled = compileWorkflow({ workflow: migrated, qualityProfile });
    }
  }

  const newRequiredViewBox = requiredViewBoxFrom(compiled) || requiredViewBoxFrom(planned);
  if (!compiled.ok) {
    return result({
      ok: false,
      preExistingDiagnostics: preExistingLayoutDiagnostics,
      newSchemaDiagnostics: compiled.diagnostics,
      changedCoordinates,
      oldRequiredViewBox,
      newRequiredViewBox,
    });
  }

  const migratedSchemaDiagnostics = schemaDiagnostics(migrated);
  if (migratedSchemaDiagnostics.length) {
    return result({
      ok: false,
      preExistingDiagnostics: preExistingLayoutDiagnostics,
      newSchemaDiagnostics: migratedSchemaDiagnostics,
      changedCoordinates,
      oldRequiredViewBox,
      newRequiredViewBox,
    });
  }

  return result({
    ok: true,
    document: migrated,
    preExistingDiagnostics: preExistingLayoutDiagnostics,
    changedCoordinates,
    oldRequiredViewBox,
    newRequiredViewBox,
  });
}

export function serializeMigratedWorkflow(workflow) {
  return `${JSON.stringify(workflow, null, 2)}\n`;
}
```

## package-lock.json

```json
{
  "name": "archify",
  "version": "2.17.0-dev.1",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "archify",
      "version": "2.17.0-dev.1",
      "license": "MIT",
      "bin": {
        "archify": "bin/archify.mjs"
      },
      "devDependencies": {
        "ajv": "^8.17.1",
        "parse5": "7.3.0",
        "saxes": "6.0.0",
        "simple-icons": "16.28.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/ajv": {
      "version": "8.20.0",
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
      "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "fast-deep-equal": "^3.1.3",
        "fast-uri": "^3.0.1",
        "json-schema-traverse": "^1.0.0",
        "require-from-string": "^2.0.2"
      },
      "funding": {
        "type": "github",
        "url": "https://github.com/sponsors/epoberezkin"
      }
    },
    "node_modules/ajv/node_modules/fast-uri": {
      "version": "3.1.5",
      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
      "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/fastify"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/fastify"
        }
      ],
      "license": "BSD-3-Clause"
    },
    "node_modules/entities": {
      "version": "6.0.1",
      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
      "dev": true,
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=0.12"
      },
      "funding": {
        "url": "https://github.com/fb55/entities?sponsor=1"
      }
    },
    "node_modules/fast-deep-equal": {
      "version": "3.1.3",
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/json-schema-traverse": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/parse5": {
      "version": "7.3.0",
      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "entities": "^6.0.0"
      },
      "funding": {
        "url": "https://github.com/inikulin/parse5?sponsor=1"
      }
    },
    "node_modules/require-from-string": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/saxes": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
      "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
      "dev": true,
      "license": "ISC",
      "dependencies": {
        "xmlchars": "^2.2.0"
      },
      "engines": {
        "node": ">=v12.22.7"
      }
    },
    "node_modules/simple-icons": {
      "version": "16.28.0",
      "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.28.0.tgz",
      "integrity": "sha512-sQPR5AtK/ijRjou7zw7mlLp08oB6FH7i0lOy5XJ2zp9mJs/yejgiOn7KvQoe2q4YJIx6VmgUSW5AOefebPt5kg==",
      "dev": true,
      "funding": [
        {
          "type": "opencollective",
          "url": "https://opencollective.com/simple-icons"
        },
        {
          "type": "github",
          "url": "https://github.com/sponsors/simple-icons"
        }
      ],
      "license": "CC0-1.0",
      "engines": {
        "node": ">=0.12.18"
      }
    },
    "node_modules/xmlchars": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
      "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
      "dev": true,
      "license": "MIT"
    }
  }
}
```

## package.json

```json
{
  "name": "archify",
  "version": "2.17.0-dev.1",
  "private": true,
  "type": "module",
  "description": "JSON-IR diagram renderers (architecture / workflow / sequence / dataflow / lifecycle).",
  "license": "MIT",
  "bin": {
    "archify": "./bin/archify.mjs"
  },
  "engines": {
    "node": ">=18"
  },
  "scripts": {
    "generate:viewer": "node ../scripts/generate-viewer.mjs",
    "check:viewer": "node ../scripts/generate-viewer.mjs --check",
    "generate:brand-marks": "node scripts/generate-brand-marks.mjs",
    "check:brand-marks": "node scripts/generate-brand-marks.mjs --check",
    "generate:validators": "node scripts/generate-validators.mjs",
    "check:validators": "node scripts/generate-validators.mjs --check",
    "check:release-identity": "node ../scripts/check-release-identity.mjs",
    "build:gallery": "node ../scripts/build-gallery.mjs ../docs",
    "build:guide": "node ../scripts/build-guide.mjs ../docs/guide.html",
    "build:start": "node ../scripts/build-start.mjs ../docs/start.html",
    "build:readme-showcase": "node ../scripts/build-readme-showcase.mjs",
    "test:webm": "node test/webm-artifact.smoke.mjs && node --test test/site-language-integration.mjs",
    "test": "npm run check:viewer && npm run check:brand-marks && npm run check:validators && npm run check:release-identity && node test/golden.mjs && node ../scripts/run-tests.mjs",
    "render:examples": "node scripts/render-examples.mjs ../examples"
  },
  "devDependencies": {
    "ajv": "^8.17.1",
    "parse5": "7.3.0",
    "saxes": "6.0.0",
    "simple-icons": "16.28.0"
  },
  "overrides": {
    "fast-uri": "3.1.5"
  }
}
```

## recipes

```

```

## recipes/scenarios.mjs

```js
const RAW_RECIPES = [
  {
    id: 'system-overview', type: 'architecture', proof: 'web-app',
    presentation: { preset: 'classic', motion: 'static', views: 'optional' },
    start: {
      en: { descriptionPrompt: 'Use Archify to turn this plain-language system description into a high-level architecture diagram: [describe the users, core components, primary path, external dependencies, and boundaries]. No repository is required. Ask only for missing facts that would materially change the diagram, mark any remaining unknowns instead of inventing them, and keep one obvious primary path across 8–12 core components.' },
      zh: { descriptionPrompt: '用 Archify 把下面这段自然语言系统描述画成高层架构图：[在这里描述用户、核心组件、主要路径、外部依赖和边界]。不需要代码库。只追问会实质影响图的缺失信息，其余不确定内容要标明而不是编造；保留 8–12 个核心组件和一条一眼可见的主路径。' },
    },
    signals: [['system overview', 12], ['architecture', 10], ['components', 6], ['services', 4], ['repository', 5], ['trust boundary', 8], ['架构', 10], ['系统总览', 12], ['组件', 6], ['服务', 4], ['仓库', 5], ['信任边界', 8]],
    en: {
      title: 'System overview', question: 'What exists, who owns it, and how is it connected?',
      summary: 'A bounded map of core components, external dependencies, primary paths, and trust boundaries.',
      useWhen: 'Onboarding, design reviews, repository orientation, or explaining a service landscape.',
      avoidWhen: 'The audience needs exact call order, state transitions, or row-level data lineage.',
      include: ['8–12 core components', 'one primary path', 'external dependencies', 'trust boundaries'],
      prompt: 'Analyze this repository, then use Archify to create a high-level architecture diagram. Show 8–12 core runtime components, one primary request or data path, external dependencies, ownership or trust boundaries, and put supporting detail in cards instead of adding more edges.',
    },
    zh: {
      title: '系统总览', question: '系统里有什么、归谁负责、彼此如何连接？',
      summary: '用一张有边界的图展示核心组件、外部依赖、主路径和信任边界。',
      useWhen: '适合新人上手、方案评审、仓库梳理和服务全景说明。',
      avoidWhen: '如果重点是精确调用顺序、状态流转或字段级血缘，请换其他配方。',
      include: ['8–12 个核心组件', '一条主路径', '外部依赖', '归属或信任边界'],
      prompt: '分析这个仓库，然后用 Archify 生成高层系统架构图。展示 8–12 个核心运行时组件、一条主要请求或数据路径、外部依赖、归属或信任边界；支持性细节放进卡片，不要继续堆连线。',
    },
  },
  {
    id: 'deployment-ownership', type: 'architecture', proof: 'deployment-ownership',
    presentation: { preset: 'blueprint', motion: 'trace', views: 'recommended' },
    signals: [['deployment topology', 14], ['region', 7], ['vpc', 9], ['cluster', 6], ['availability zone', 8], ['ownership', 7], ['cloud deployment', 12], ['部署拓扑', 14], ['区域', 6], ['集群', 6], ['可用区', 8], ['资源归属', 9], ['跨区', 8]],
    en: {
      title: 'Deployment ownership', question: 'Where does each workload run, and what crosses a boundary?',
      summary: 'A deployment-focused map of regions, networks, clusters, workloads, stores, and cross-boundary mechanisms.',
      useWhen: 'Cloud reviews, production readiness, multi-region planning, or infrastructure ownership handoffs.',
      avoidWhen: 'Deployment facts are unknown or the real question is application behavior rather than placement.',
      include: ['regions and networks', 'workload ownership', 'stateful services', 'named boundary crossings'],
      prompt: 'Use Archify to draw the production deployment topology. Group resources by region, network, cluster, and owner; show workloads and stateful services; label every cross-boundary mechanism. Do not invent deployment facts—mark unknown areas explicitly. If the user wants a fail-closed deployment review, ask before setting meta.engineering_profile to deployment-ownership; otherwise leave the engineering profile unset.',
    },
    zh: {
      title: '部署与归属', question: '每个工作负载运行在哪里，哪些连接跨越了边界？',
      summary: '围绕 Region、网络、集群、工作负载、存储和跨边界机制组织部署图。',
      useWhen: '适合云上评审、生产就绪、多区域规划和基础设施交接。',
      avoidWhen: '部署事实不清楚，或真正问题是应用行为而不是资源位置时不要使用。',
      include: ['区域与网络', '工作负载归属', '有状态服务', '明确的跨边界机制'],
      prompt: '用 Archify 绘制生产部署拓扑。按区域、网络、集群和负责人分组，展示工作负载与有状态服务，并标注每一种跨边界机制。不要编造部署事实，不确定的区域要明确标出。如果用户需要失败即阻断的部署评审，先征得确认，再把 meta.engineering_profile 设为 deployment-ownership；否则不要启用工程画像。',
    },
  },
  {
    id: 'agent-tool-call', type: 'workflow', proof: 'agent-tool-call',
    presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' },
    start: {
      en: { descriptionPrompt: 'Use Archify workflow mode to turn this description into a diagram: [paste the actors, main steps, decisions, approvals, and exception paths]. Use lanes for distinct owners, keep one unmistakable happy path, and mark missing ownership or unresolved branches instead of inventing them.' },
      zh: { descriptionPrompt: '用 Archify 工作流模式把下面的描述画成图：[粘贴参与者、主要步骤、决策、审批和异常路径]。不同负责方使用独立泳道，保留一条明确的成功主路径，缺失的负责人或未定分支要标明而不是编造。' },
    },
    signals: [['agent tool call', 16], ['tool call', 12], ['approval gate', 10], ['human in the loop', 9], ['mcp', 7], ['planner', 6], ['agent loop', 10], ['智能体工具调用', 16], ['工具调用', 12], ['审批门', 10], ['人在回路', 9], ['规划器', 6], ['智能体循环', 10]],
    en: {
      title: 'Agent tool-call loop', question: 'How does an agent plan, get permission, act, recover, and report?',
      summary: 'A lane-based agent loop with policy gates, tool execution, exception recovery, evidence, and final response.',
      useWhen: 'Explaining agent runtimes, MCP/tool orchestration, approvals, retries, or observability.',
      avoidWhen: 'The goal is only to show static agent components or exact API message timing.',
      include: ['request and planning', 'policy or approval gate', 'tool execution', 'exception and evidence paths'],
      prompt: 'Use Archify workflow mode to explain this agent tool-call loop. Separate user surface, agent runtime, policy boundary, exception handling, tool execution, and observability into lanes. Make the successful path primary and show approval, retry, blocked, and evidence paths explicitly.',
    },
    zh: {
      title: '智能体工具调用', question: '智能体如何规划、获批、执行、恢复并汇报？',
      summary: '用泳道表达策略门、工具执行、异常恢复、证据和最终回复。',
      useWhen: '适合解释 Agent Runtime、MCP/工具编排、审批、重试和可观测性。',
      avoidWhen: '如果只想看静态组件，或重点是精确 API 消息时序，请换其他配方。',
      include: ['请求与规划', '策略或审批门', '工具执行', '异常与证据路径'],
      prompt: '用 Archify 工作流模式解释这段智能体工具调用。把用户界面、Agent Runtime、策略边界、异常处理、工具执行和可观测性分成泳道；突出成功主路径，并明确展示审批、重试、阻塞和证据路径。',
    },
  },
  {
    id: 'delivery-workflow', type: 'workflow', proof: 'delivery-workflow',
    presentation: { preset: 'classic', motion: 'trace', views: 'optional' },
    signals: [['ci/cd', 14], ['release workflow', 14], ['deployment pipeline', 11], ['pull request', 7], ['staging', 7], ['rollback', 8], ['发布流程', 14], ['流水线', 9], ['上线', 7], ['预发', 7], ['回滚', 8], ['审批发布', 10]],
    en: {
      title: 'Delivery workflow', question: 'How does a change move safely from commit to production?',
      summary: 'A delivery flow with build, checks, environments, approvals, smoke tests, rollback, and ownership lanes.',
      useWhen: 'CI/CD design, release reviews, deployment governance, or onboarding developers to delivery.',
      avoidWhen: 'The question is where infrastructure runs or what states a deployment object can occupy.',
      include: ['trigger and build', 'blocking checks', 'approval and environments', 'rollback and verification'],
      prompt: 'Use Archify workflow mode to draw this delivery process from commit to production. Separate developer, CI, approval, environment, and exception lanes; mark blocking checks, smoke tests, ownership, and the rollback path. Keep one unmistakable happy path.',
    },
    zh: {
      title: '研发交付流程', question: '一次变更如何安全地从提交走到生产？',
      summary: '展示构建、检查、环境、审批、冒烟、回滚和负责人泳道。',
      useWhen: '适合 CI/CD 设计、发布评审、部署治理和研发新人上手。',
      avoidWhen: '如果重点是基础设施位置或部署对象的状态集合，请换架构图或生命周期图。',
      include: ['触发与构建', '阻断检查', '审批与环境', '回滚与验证'],
      prompt: '用 Archify 工作流模式绘制从代码提交到生产发布的流程。拆分开发者、CI、审批、环境和异常泳道；标出阻断检查、冒烟测试、负责人和回滚路径，并保留一条一眼可见的成功主路径。',
    },
  },
  {
    id: 'incident-runbook', type: 'workflow', proof: 'incident-runbook',
    presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' },
    signals: [['incident response', 15], ['runbook', 12], ['outage', 9], ['triage', 8], ['mitigation', 8], ['escalation', 7], ['事故处置', 15], ['故障', 9], ['应急预案', 12], ['排障', 9], ['缓解', 7], ['升级响应', 8]],
    en: {
      title: 'Incident runbook', question: 'How do responders detect, triage, mitigate, verify, and escalate?',
      summary: 'An operational workflow that separates signals, responders, mitigation, communications, and recovery proof.',
      useWhen: 'Incident playbooks, on-call handoffs, reliability reviews, and tabletop exercises.',
      avoidWhen: 'The audience needs live metrics or a post-incident component topology instead of response actions.',
      include: ['detection signal', 'triage owner', 'mitigation and rollback', 'verification and communication'],
      prompt: 'Use Archify workflow mode to turn this incident runbook into responder lanes. Show detection, triage, mitigation, escalation, communication, rollback, and recovery verification. Separate decision gates from actions and make missing ownership visible.',
    },
    zh: {
      title: '事故处置 Runbook', question: '响应者如何发现、分诊、缓解、验证并升级？',
      summary: '把信号、响应者、缓解动作、沟通和恢复证据拆成可执行流程。',
      useWhen: '适合故障预案、On-call 交接、稳定性评审和桌面演练。',
      avoidWhen: '如果受众需要实时指标仪表盘或事故后的组件拓扑，而不是响应动作，请换其他视图。',
      include: ['发现信号', '分诊负责人', '缓解与回滚', '恢复验证与沟通'],
      prompt: '用 Archify 工作流模式把事故处置预案画成响应者泳道。展示发现、分诊、缓解、升级、沟通、回滚和恢复验证；把决策门与操作分开，并让缺失的负责人清晰可见。',
    },
  },
  {
    id: 'api-request', type: 'sequence', proof: 'cache-miss',
    presentation: { preset: 'classic', motion: 'trace', views: 'optional' },
    start: {
      en: { descriptionPrompt: 'Use Archify sequence mode to draw this interaction: [paste the participants, calls, returns, fallback, and asynchronous side effects]. Keep message order unambiguous, labels short, and unknown behavior explicit. No repository is required.' },
      zh: { descriptionPrompt: '用 Archify 时序模式绘制下面的交互：[粘贴参与者、调用、返回、回退和异步副作用]。确保消息顺序无歧义、标签简短，并明确标注未知行为。不需要代码库。' },
    },
    signals: [['api request', 14], ['request response', 12], ['call chain', 11], ['cache miss', 13], ['jwt', 8], ['who calls whom', 12], ['api 请求', 14], ['请求响应', 12], ['调用链', 11], ['缓存未命中', 13], ['谁调用谁', 12], ['鉴权链路', 9]],
    en: {
      title: 'API request chain', question: 'Who calls whom, in what order, and what returns?',
      summary: 'A time-ordered request path with authentication, cache fallback, persistence, return traffic, and async trace.',
      useWhen: 'API documentation, debugging request latency, auth reviews, or explaining cache fallback.',
      avoidWhen: 'Order is unimportant and the audience only needs the stable service topology.',
      include: ['callers and callees', 'request and return messages', 'fallback or error path', 'async side effects'],
      prompt: 'Use Archify sequence mode to show this request from caller to final response. Include authentication, cache hit or miss, persistence fallback, return messages, and asynchronous trace or event emission. Keep message labels short and order unambiguous.',
    },
    zh: {
      title: 'API 请求链', question: '谁调用谁、顺序如何、最终返回什么？',
      summary: '按时间展示鉴权、缓存回退、持久化、返回流量和异步追踪。',
      useWhen: '适合 API 文档、请求耗时排查、鉴权评审和缓存回退说明。',
      avoidWhen: '如果顺序不重要，受众只需要稳定的服务拓扑，请用架构图。',
      include: ['调用方与被调用方', '请求与返回消息', '回退或错误路径', '异步副作用'],
      prompt: '用 Archify 时序模式展示从调用方到最终响应的完整请求。包含鉴权、缓存命中或未命中、持久化回退、返回消息，以及异步 Trace 或事件上报；消息标签保持简短，顺序必须明确。',
    },
  },
  {
    id: 'async-roundtrip', type: 'sequence', proof: 'async-roundtrip',
    presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' },
    signals: [['async roundtrip', 14], ['webhook', 10], ['callback', 10], ['acknowledgement', 8], ['timeout', 7], ['retry message', 8], ['异步回调', 14], ['回调', 10], ['确认消息', 8], ['超时', 7], ['消息重试', 9], ['webhook', 10]],
    en: {
      title: 'Async roundtrip', question: 'What happens after the initial request returns?',
      summary: 'A sequence view of enqueue, acknowledgement, background work, callbacks, retries, timeout, and final consistency.',
      useWhen: 'Webhooks, jobs, queues, payment callbacks, eventual consistency, or async API contracts.',
      avoidWhen: 'The primary question is topic topology and consumer ownership rather than time order.',
      include: ['initial acknowledgement', 'queue or scheduler', 'background work', 'callback, retry, and timeout'],
      prompt: 'Use Archify sequence mode to explain this asynchronous roundtrip. Show the initial acknowledgement, enqueue or scheduling step, background processing, callback or polling, retry and timeout behavior, and the point where the caller can observe final consistency.',
    },
    zh: {
      title: '异步往返链路', question: '初始请求返回之后，后台还会发生什么？',
      summary: '按时间展示入队、确认、后台处理、回调、重试、超时和最终一致。',
      useWhen: '适合 Webhook、后台任务、队列、支付回调、最终一致和异步 API 契约。',
      avoidWhen: '如果重点是 Topic 拓扑和消费者归属，而不是时间顺序，请用事件数据流配方。',
      include: ['初始确认', '队列或调度器', '后台处理', '回调、重试与超时'],
      prompt: '用 Archify 时序模式解释这段异步往返链路。展示初始确认、入队或调度、后台处理、回调或轮询、重试与超时，以及调用方何时能观察到最终一致结果。',
    },
  },
  {
    id: 'data-lineage', type: 'dataflow', proof: 'product-analytics',
    presentation: { preset: 'classic', motion: 'trace', views: 'recommended' },
    signals: [['data lineage', 15], ['etl', 12], ['warehouse', 9], ['pii', 11], ['governance', 9], ['analytics pipeline', 12], ['数据血缘', 15], ['数据管道', 11], ['数仓', 9], ['治理', 9], ['隐私数据', 10], ['用户同意', 9]],
    en: {
      title: 'Data lineage', question: 'Where does data come from, how does it change, and who consumes it?',
      summary: 'A governed path from sources through consent, transforms, sensitive stores, warehouse, and consumers.',
      useWhen: 'Analytics architecture, ETL/ELT review, PII assessment, warehouse design, or model feature lineage.',
      avoidWhen: 'The audience needs request timing or operational task ownership rather than data assets.',
      include: ['sources and assets', 'transform stages', 'classification or consent', 'stores and consumers'],
      prompt: 'Use Archify dataflow mode to map this data lineage. Name every data asset and transform, show consent or classification boundaries, distinguish streaming from batch paths, and identify stores plus downstream consumers. Do not use unlabeled flows.',
    },
    zh: {
      title: '数据血缘', question: '数据从哪里来、如何变化、最终被谁消费？',
      summary: '从来源经过同意、转换、敏感存储、数仓直到消费者的治理路径。',
      useWhen: '适合分析架构、ETL/ELT 评审、PII 评估、数仓设计和特征血缘。',
      avoidWhen: '如果受众需要请求时序或操作负责人，而不是数据资产，请换其他配方。',
      include: ['数据来源与资产', '转换阶段', '分类或同意边界', '存储与消费者'],
      prompt: '用 Archify 数据流模式梳理这段数据血缘。为每个数据资产和转换命名，展示用户同意或数据分类边界，区分流式与批处理路径，并标明存储和下游消费者；所有数据流都必须有标签。',
    },
  },
  {
    id: 'event-stream', type: 'dataflow', proof: 'event-stream',
    presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' },
    start: {
      en: { descriptionPrompt: 'Use Archify dataflow mode to map this data journey: [paste the sources, data assets, transforms, stores, boundaries, and consumers]. Label every flow, distinguish streaming from batch where relevant, and mark unknown classifications or ownership instead of inventing them.' },
      zh: { descriptionPrompt: '用 Archify 数据流模式梳理下面的数据路径：[粘贴来源、数据资产、转换、存储、边界和消费者]。为每条数据流标注名称，在有意义时区分流式与批处理，未知的分类或归属要标明而不是编造。' },
    },
    signals: [['event stream', 15], ['kafka topology', 14], ['topic', 8], ['consumer group', 11], ['dead letter', 10], ['dlq', 10], ['事件流', 15], ['kafka 拓扑', 14], ['主题', 7], ['消费者组', 11], ['死信', 10], ['事件地铁图', 12]],
    en: {
      title: 'Event-stream topology', question: 'Which events move through which topics, processors, groups, and failure paths?',
      summary: 'A stream map of producers, topics, ordered processors, consumer groups, state, replay, and DLQ.',
      useWhen: 'Kafka/event-platform design, stream processing reviews, ownership, replay, and failure handling.',
      avoidWhen: 'Topic names, consumer groups, and delivery semantics are not known—use a generic workflow instead.',
      include: ['producers and event names', 'topics and ordering', 'processors and consumer groups', 'state, replay, and DLQ'],
      prompt: 'Use Archify dataflow mode to draw this event-stream topology. Name producers, events, topics, ordered processors, consumer groups, state stores, replay paths, and the DLQ. Show ownership and delivery semantics only when supported by evidence.',
    },
    zh: {
      title: '事件流拓扑', question: '哪些事件经过哪些 Topic、处理器、消费者组和失败路径？',
      summary: '展示生产者、Topic、有序处理器、消费者组、状态、重放和 DLQ。',
      useWhen: '适合 Kafka/事件平台设计、流处理评审、归属、重放和失败处理。',
      avoidWhen: '如果 Topic、消费者组和投递语义都不清楚，请先用通用工作流，不要编造事件拓扑。',
      include: ['生产者与事件名', 'Topic 与顺序', '处理器与消费者组', '状态、重放与 DLQ'],
      prompt: '用 Archify 数据流模式绘制这段事件流拓扑。命名生产者、事件、Topic、有序处理器、消费者组、状态存储、重放路径和 DLQ；只有在证据充分时才标注归属和投递语义。',
    },
  },
  {
    id: 'object-lifecycle', type: 'lifecycle', proof: 'agent-run',
    presentation: { preset: 'classic', motion: 'trace', views: 'optional' },
    start: {
      en: { descriptionPrompt: 'Use Archify lifecycle mode to model this object: [paste its states, transition events, waits, retries, cancellation, and terminal outcomes]. Separate active, waiting, recoverable-failure, and terminal states, and never hide an ending. No repository is required.' },
      zh: { descriptionPrompt: '用 Archify 生命周期模式建模这个对象：[粘贴它的状态、转换事件、等待、重试、取消和终态]。分开执行、等待、可恢复失败和终态，不要隐藏任何结束方式。不需要代码库。' },
    },
    signals: [['state machine', 15], ['object lifecycle', 14], ['status transition', 11], ['terminal state', 9], ['retry state', 8], ['状态机', 15], ['生命周期', 13], ['状态流转', 11], ['终态', 9], ['等待态', 8], ['重试状态', 8]],
    en: {
      title: 'Object lifecycle', question: 'Which states exist, what events move between them, and how does it end?',
      summary: 'A state model with active work, waits, retries, cancellation, failure, and explicit terminal outcomes.',
      useWhen: 'Tasks, orders, tickets, subscriptions, jobs, agent runs, or any durable object with status.',
      avoidWhen: 'The object has no durable state and the real question is participant interaction over time.',
      include: ['start and active states', 'event-labelled transitions', 'wait and retry states', 'all terminal outcomes'],
      prompt: 'Use Archify lifecycle mode to model this object. Separate main progress, waiting or interruption states, and terminal outcomes. Label transitions with events, include retry, cancellation, timeout, success, and failure where real, and never hide an ending.',
    },
    zh: {
      title: '对象生命周期', question: '有哪些状态、什么事件触发流转、最终如何结束？',
      summary: '展示执行、等待、重试、取消、失败以及明确终态的状态模型。',
      useWhen: '适合任务、订单、工单、订阅、作业、Agent Run 等带持久状态的对象。',
      avoidWhen: '对象没有持久状态，真正问题是参与者随时间的交互时，请使用时序图。',
      include: ['开始与执行态', '带事件的转换', '等待与重试态', '所有终态'],
      prompt: '用 Archify 生命周期模式建模这个对象。分开主进度、等待或中断状态和终态；用事件标注转换，并在真实存在时展示重试、取消、超时、成功和失败，不能隐藏任何结束方式。',
    },
  },
  {
    id: 'deployment-lifecycle', type: 'lifecycle', proof: 'deployment-lifecycle',
    presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' },
    signals: [['deployment lifecycle', 15], ['release state', 10], ['promotion state', 9], ['approval status', 8], ['rollback state', 10], ['部署生命周期', 15], ['发布状态', 10], ['晋级', 7], ['审批状态', 8], ['回滚状态', 10]],
    en: {
      title: 'Deployment lifecycle', question: 'What state is a release in, and what can happen next?',
      summary: 'A deployment state model covering queued, building, verifying, approval, promotion, rollback, and terminal outcomes.',
      useWhen: 'Release controllers, GitOps reconciliation, environment promotion, or deployment status APIs.',
      avoidWhen: 'The question is the human/CI sequence of delivery actions rather than the deployment object state.',
      include: ['queued and running states', 'verification and approval', 'promotion and rollback', 'success, failure, cancellation'],
      prompt: 'Use Archify lifecycle mode to model the deployment object. Show queued, building, verifying, waiting for approval, promoting, rolling back, and every terminal outcome. Label the events and guards that permit each transition.',
    },
    zh: {
      title: '部署生命周期', question: '一次发布当前处于什么状态，下一步可能发生什么？',
      summary: '覆盖排队、构建、验证、审批、晋级、回滚和终态的部署状态模型。',
      useWhen: '适合发布控制器、GitOps 对账、环境晋级和部署状态 API。',
      avoidWhen: '如果重点是人员与 CI 的交付动作顺序，而不是部署对象状态，请用交付工作流。',
      include: ['排队与执行态', '验证与审批', '晋级与回滚', '成功、失败与取消'],
      prompt: '用 Archify 生命周期模式建模部署对象。展示排队、构建、验证、等待审批、晋级、回滚以及所有终态，并标注允许每次状态转换的事件和守卫条件。',
    },
  },
];

export const SCENARIO_RECIPES = Object.freeze(RAW_RECIPES.map((recipe) => Object.freeze({
  ...recipe,
  presentation: Object.freeze({ ...recipe.presentation }),
  ...(recipe.start ? { start: Object.freeze({
    en: Object.freeze({ ...recipe.start.en }),
    zh: Object.freeze({ ...recipe.start.zh }),
  }) } : {}),
  signals: Object.freeze(recipe.signals.map((signal) => Object.freeze(signal.slice()))),
  en: Object.freeze({ ...recipe.en, include: Object.freeze(recipe.en.include.slice()) }),
  zh: Object.freeze({ ...recipe.zh, include: Object.freeze(recipe.zh.include.slice()) }),
})));

export function detectGuideLanguage(value = '') {
  return /[\u3400-\u9fff]/u.test(value) ? 'zh' : 'en';
}

export function startPromptsFor(recipe, lang = 'en') {
  const language = lang === 'zh' ? 'zh' : 'en';
  const copy = recipe[language];
  const descriptionPrompt = recipe.start?.[language]?.descriptionPrompt;
  if (!descriptionPrompt) {
    throw new Error(`Scenario recipe ${JSON.stringify(recipe.id)} does not define a ${language} start prompt.`);
  }
  const repositoryPrompt = recipe.type === 'architecture'
    ? copy.prompt
    : language === 'zh'
      ? `先检查这个仓库里的相关证据，然后${copy.prompt}不要编造代码无法支持的行为。`
      : `Inspect this repository for evidence, then ${copy.prompt.charAt(0).toLowerCase()}${copy.prompt.slice(1)} Do not invent behavior that the code does not support.`;
  return { descriptionPrompt, repositoryPrompt };
}

function normalized(value) {
  return String(value || '').normalize('NFKC').toLowerCase().replace(/[\s_]+/g, ' ').trim();
}

function localized(recipe, lang) {
  const copy = recipe[lang === 'zh' ? 'zh' : 'en'];
  return {
    id: recipe.id,
    type: recipe.type,
    proof: recipe.proof,
    presentation: { ...recipe.presentation },
    ...copy,
    include: copy.include.slice(),
  };
}

export function listScenarioRecipes(lang = 'en') {
  return SCENARIO_RECIPES.map((recipe) => localized(recipe, lang));
}

function scoreRecipe(recipe, query) {
  const text = normalized(query);
  if (!text) return { recipe, score: 0, matched: [] };
  if (text === recipe.id || text === recipe.id.replace(/-/g, ' ')) {
    return { recipe, score: 100, matched: [recipe.id] };
  }
  let score = 0;
  const matched = [];
  for (const [signal, weight] of recipe.signals) {
    if (text.includes(normalized(signal))) {
      score += weight;
      matched.push(signal);
    }
  }
  return { recipe, score, matched };
}

export function recommendScenario(query, options = {}) {
  const lang = options.lang === 'zh' || options.lang === 'en' ? options.lang : detectGuideLanguage(query);
  const ranked = SCENARIO_RECIPES.map((recipe) => scoreRecipe(recipe, query))
    .sort((left, right) => right.score - left.score || SCENARIO_RECIPES.indexOf(left.recipe) - SCENARIO_RECIPES.indexOf(right.recipe));
  const winner = ranked[0].score > 0 ? ranked[0] : { recipe: SCENARIO_RECIPES[0], score: 0, matched: [] };
  const confidence = winner.score >= 14 ? 'high' : winner.score >= 7 ? 'medium' : 'low';
  return {
    ok: true,
    mode: 'recommendation',
    lang,
    query: String(query || ''),
    confidence,
    matchedSignals: winner.matched.slice(),
    recommendation: localized(winner.recipe, lang),
    alternatives: ranked.filter((entry) => entry.recipe.id !== winner.recipe.id && entry.score > 0)
      .slice(0, 2)
      .map((entry) => ({ ...localized(entry.recipe, lang), score: entry.score })),
  };
}

export function formatScenarioList(lang = 'en') {
  const isZh = lang === 'zh';
  const heading = isZh
    ? `Archify 场景配方（${SCENARIO_RECIPES.length}）`
    : `Archify scenario recipes (${SCENARIO_RECIPES.length})`;
  const intro = isZh
    ? '先选择你要回答的问题，再选择图表类型。可运行：archify guide "你的场景"'
    : 'Choose the question before the diagram type. Run: archify guide "your scenario"';
  return [heading, '', intro, '', ...listScenarioRecipes(lang).flatMap((recipe) => [
    `${recipe.id}  [${recipe.type}]  ${recipe.title}`,
    `  ${recipe.question}`,
  ])].join('\n');
}

export function formatScenarioRecommendation(result) {
  const isZh = result.lang === 'zh';
  const recipe = result.recommendation;
  const labels = isZh ? {
    heading: '推荐', question: '要回答的问题', use: '适合', avoid: '不要这样用', include: '必须包含', presentation: '表现建议', prompt: '可直接复制的提示词', alternatives: '其他可能', confidence: '置信度',
  } : {
    heading: 'Recommendation', question: 'Question answered', use: 'Use when', avoid: 'Avoid when', include: 'Must include', presentation: 'Presentation', prompt: 'Copy-ready prompt', alternatives: 'Other possible fits', confidence: 'Confidence',
  };
  const lines = [
    `${labels.heading}: ${recipe.title}  [${recipe.type}]`,
    `${labels.confidence}: ${result.confidence}`,
    `${labels.question}: ${recipe.question}`,
    '',
    `${labels.use}: ${recipe.useWhen}`,
    `${labels.avoid}: ${recipe.avoidWhen}`,
    `${labels.include}: ${recipe.include.join(isZh ? '、' : '; ')}`,
    `${labels.presentation}: ${recipe.presentation.preset} · ${recipe.presentation.motion} · views ${recipe.presentation.views}`,
    '',
    `${labels.prompt}:`,
    recipe.prompt,
  ];
  if (result.alternatives.length) {
    lines.push('', `${labels.alternatives}: ${result.alternatives.map((item) => `${item.title} [${item.type}]`).join(' · ')}`);
  }
  return lines.join('\n');
}

export function publicGuideData() {
  return SCENARIO_RECIPES.map((recipe) => ({
    ...localized(recipe, 'en'),
    en: recipe.en,
    zh: recipe.zh,
    signals: recipe.signals.map(([signal, weight]) => [signal, weight]),
  }));
}
```

## references

```

```

## references/authoring-contract.md

# Authoring contract

Read this reference only after the Fast authoring path calls for more detail. The schemas and examples remain authoritative.

## Schema lookup

Read both the mode schema and `schemas/common.schema.json`. The mode schemas use `$ref`, so the common file is where shared enums live.

- `componentType`: `frontend`, `backend`, `database`, `cloud`, `security`, `messagebus`, `external`
- `variant`: `default`, `emphasis`, `security`, `dashed`
- Relationship IDs use the shared identifier pattern and must be unique in their collection.

Do not invent fields. Use the nearest matching example for structure, then author fresh IDs, wording, facts, and layout.

## Workflow layout contracts

Use schema v2 for new workflows and keep schema v1 when an existing source must
retain fixed geometry. In both versions, `col` stays in `0..5` and semantic
edge labels are never deleted as a spacing repair. Do not change only
`schema_version` when absolute coordinates exist: follow the canonical
[migration and layout-receipt contract](../renderers/workflow/README.md#migration-and-layout-receipt).
The complete normative invariants live in the workflow renderer's
[layout contracts](../renderers/workflow/README.md#layout-contracts).

## Legend contract

Omit `meta.legend` for the truthful default: `auto` lists only semantic kinds
present in typed IR. Use `mode: "all"` for a renderer reference or
`mode: "hidden"` to remove the full legend. Under `entries`, only keys listed
by the selected mode schema are valid; each key accepts `label`, `visible`, or
both. `visible: true` may show an unused supported convention, while
`visible: false` hides it. `hidden` cannot be overridden.

A label override changes reader wording only. Never infer a kind from prose or
use the legend to compensate for missing nodes, states, messages, or flows.
Long labels are measured and wrap into deterministic rows. Architecture's
implicit automatic viewBox grows from that same measured footprint. For
backwards compatibility, a legacy document with no `meta.legend` may omit an
implicit auto legend that cannot fit its explicit viewBox; this never changes
its typed topology. Adding `meta.legend` makes the presentation intentional and
strict: if its resolved labels cannot fit the authored viewBox, shorten or hide
them, or widen the viewBox using the emitted diagnostic.

## Language consistency

Choose one primary authored language. An explicit user choice wins; otherwise
use the language of the request, or the conversation's dominant language when
the request itself is language-neutral. Separately choose the Viewer locale.
For supported languages, always write the matching `meta.locale`: `"en"` for
English or `"zh-CN"` for Simplified Chinese. The renderer consumes the authored
locale without inferring language from diagram strings. Documents that omit it
remain valid and default to English.

`meta.locale` controls only renderer-owned reader surfaces: `<html lang>`, the
document-title suffix, default SVG description and focus labels, default legend
labels, and fixed Viewer controls, statuses, accessibility names, and errors.
It never translates authored content. Apply the primary language separately to
titles, subtitles, node and relationship copy, boundaries, lanes, groups,
guided views, legend label overrides, and cards. A bilingual diagram still
chooses one primary locale for the Viewer; follow an explicit primary-language
request, then prompt order or conversation dominance.

For a requested language outside `en` and `zh-CN`, do not write an unsupported
locale. Keep every reader-facing authored string in the requested language,
omit `meta.locale` so the renderer safely uses English, and explicitly tell the
user that fixed Viewer UI and `<html lang>` remain English and the artifact is
not fully localized. The fallback applies only to renderer-owned surfaces; it
never permits authored copy to fall back to English. Do not silently substitute
`zh-CN` for another language or Chinese locale.

Keep exact product names, code identifiers, commands, protocols, API paths, and
environment names intact. Those terms may remain English inside localized copy,
but surrounding explanatory prose must still use the selected language.
Renderer-owned default legend labels follow `meta.locale`; author a
`meta.legend.entries.*.label` override only when the diagram needs different
domain wording, and keep that authored override in the primary language.

## Visual preset default

Omit `meta.visual_preset` by default. The renderer then opens the diagram in
`classic` for both light and dark color modes. Color mode and visual preset are
independent viewer state: switching Light / Dark must preserve the current
preset. Author `signal-flow`, `blueprint`, or `editorial` only when the user
explicitly requests that visual style.

## Engineering profile default

Omit `meta.engineering_profile` for an ordinary system architecture. Region,
cluster, and security boundary wording do not by themselves enable an
engineering profile. Enable `deployment-ownership` only when the user
explicitly asks for a production deployment topology, ownership handoff, or
fail-closed deployment review and the source facts are known. Once enabled,
do not remove the engineering profile merely to pass validation; repair the
authored facts or report the diagnostics truthfully.

## Title hierarchy

Use one concise title and let the diagram carry the explanation. Omit
`meta.subtitle` by default, and never use it to restate the title, nodes, edges,
or cards. Include one short supporting line only when the user explicitly asks
for a subtitle; an omitted or blank subtitle must not leave an empty visual row
in the generated viewer.

## Executable geometry rules

- Node anchors start at side midpoints. `left`/`right` change the horizontal endpoint; `top`/`bottom` change the vertical endpoint. For an automatic Architecture relationship, unobstructed facing ports whose axis offset is under 16px may share one horizontal or vertical axis when both endpoints retain the 16px corner gutter. If exactly one endpoint belongs to a spread group, only its unshared counterpart moves; relationships spread at both endpoints keep their distinct ports and outside bridge.
- A side is a direction contract. The first and final route segment must be perpendicular and outward/inward in the named direction.
- Automatic Port Spread is a default renderer behavior for architecture, workflow, data-flow, and lifecycle diagrams. Shared automatic endpoints spread deterministically and symmetrically with a 16px corner gutter. It does not apply to sequence messages, single relationships, or explicit `via`, `channelX`, `channelY`, `labelAt`, or non-`auto` routes.
- Showcase route rhythm: every nonzero segment must be at least 8px; every interior segment must be at least 16px. When spread ports are nearly parallel, the router uses a 24px endpoint stub and a 16px outside bridge instead of manufacturing a tiny dogleg.
- Shared endpoint corridors are allowed only when they remain semantically unambiguous. Unrelated collinear overlap of 8px or more fails showcase.
- Container borders are intentional pass-through geometry, but a long edge running along a structural border is not.
- An edge crossing an unrelated opaque node is always a hard failure, independent of quality profile.

### Spacing and labels

Spacing recommendations mean clear gap between boxes, not center distance. A 200px center distance between 165px-wide nodes leaves only 35px of clear gap.

For a relationship label, require:

```text
clear gap > label mask width + 8px breathing room
label mask width ≈ 6.5px × ASCII units + 13px
CJK characters count as two units
```

Relationship labels are semantic data. If the gap is too small, move the label,
adjust the route or spacing, then shorten the wording while preserving meaning.
Omit only wording already fully implied by both endpoints and carrying no
protocol, action, direction, synchronous/asynchronous behavior, or
cross-boundary mechanism. Preserve every meaningful label.
Deleting it is not a spacing repair. If a relationship starts unlabeled because
its endpoints fully imply it, explain why the wording is redundant; this is a
semantic authoring choice, not a spacing repair. In workflow v2, let the compiler
allocate its measured mask before applying a diagnosed `labelAt`,
`labelDx`/`labelDy`, or `labelSegment`. Apply one diagnosed geometry control at
a time.

### Repair order

1. Fix missing/invalid `meta.quality_profile` and schema errors.
2. Fix node overlap or out-of-range placement.
3. Fix edge-through-node and endpoint-direction errors.
4. Fix crossings, ambiguous corridors, border runs, and route rhythm.
5. Fix label-to-node, label-to-label, then label-to-route clearance.

Run `validate` after every edit. Consume `diagnostics[]` by stable `code`, exact `subject`, measured `evidence`, and `supportedFixes`. If the diagnostic gives `labelAt`, use that point instead of estimating another offset.

## Mode placement

### Architecture

Use one left-to-right spine with short vertical branches. Prefer 6–12 primary components and group only real ownership, trust, process, or deployment boundaries. Boundaries do not replace relationships.

Grid placement is preferred when the schema supports it. Free positions are appropriate for a bounded exception, not for prose-level coordinate planning. Keep external actors outside the system boundary when that is factually true.

### Workflow

Lanes express responsibility or phase. Columns `0..5` express logical
progression. Start new workflows on `readable-v2`; retain `fixed-v1` only for
legacy geometry compatibility. Keep the happy path monotonic, preserve semantic
edge labels, and route retries and exception returns outside the main lane
corridor.

### Sequence

Participants are ordered by conversation role. Messages own their vertical order. Use return/async/security variants for meaning, not decoration; sequence does not use Automatic Port Spread.

### Dataflow

Stages express transformation or custody. Rows separate parallel streams. Label only data contracts, classifications, or cross-boundary movement that is not obvious.

### Lifecycle

Main phases use columns `0..4`; event and terminal bands use columns `0..2`.
Event/terminal column `N` aligns to the same x coordinate as main column
`N + 2`. A recoverable failure needs a real transition back to an active state.
A card or guided view saying “retry” is not topology.

## Repository evidence

When an architecture diagram must reflect real code, inspect repository
entrypoints, runtime boundaries, storage, transports, and deployment
configuration before authoring. Record only evidence you actually verified.
`--repo-root <path>` is architecture-only and is accepted by architecture
`render`, `validate`, `deliver`, `preview`, and `compare`; workflow, sequence,
dataflow, and lifecycle reject it. Never infer runtime causality from file
proximity or naming alone.

Declare `meta.repository.url` and one full 40-character `revision`, then attach
`components[].sources` with repository-relative `path`, optional `line`,
`end_line`, and `label`. Verification reads blobs at that commit, independently
of working-tree edits. A matching local origin, available commit, bounded path,
blob, and valid line range are required in every link mode. Verification is
local and makes no remote requests; it establishes neither public availability
nor the current reader's access rights.

`link_mode` defaults to `web`. GitHub and Gitee HTTPS repository URLs generate
revision-pinned links; their public hosts select the provider automatically.
Optional `provider: "github"` or `"gitee"` must agree with the host. Existing
GitHub declarations and default delivery receipt fields remain compatible.

```json
{
  "url": "https://gitee.com/team/service",
  "revision": "0123456789abcdef0123456789abcdef01234567",
  "provider": "gitee"
}
```

For an internal or unsupported forge, select `link_mode: "local-only"`. The
Viewer retains SRC markers, searchable file paths, line ranges, and revision
labels without repository or source hyperlinks. The evidence receipt adds
`linkMode: "local-only"`. `url` remains required as the expected origin identity;
local-only disables links, not identity verification. A repository without an
origin is not supported.

```json
{
  "url": "http://git.internal:3000/Platform/Services/service",
  "revision": "0123456789abcdef0123456789abcdef01234567",
  "link_mode": "local-only"
}
```

Local-only accepts HTTP(S), `git@host:path`, and `ssh://git@host[:port]/path`
addresses, including nested namespaces. Declare a credential-free address;
HTTP(S) credentials on the checkout's origin are ignored for identity and
redacted from diagnostics. Hostnames compare case-insensitively; repository
paths retain case except for the existing GitHub behavior. A trailing slash
normalizes away. Only GitHub and Gitee normalize a terminal `.git` and match
standard HTTPS/443 with Git SSH/22. For other hosts, use the actual clone address:
transport, port, `.git` suffix, and remote-relative versus absolute paths must
match. For example, `git@host:Team/repo` differs from
`ssh://git@host/Team/repo`; `git@host:/Team/repo` matches the latter. SCP-style
paths preserve literal percent escapes, while URI paths decode them. SSH host
aliases and forge-specific browse/clone prefixes are not guessed.
GitLab/Gitea/Forgejo/Bitbucket web links are not implemented in this version;
use local-only until a tested link provider is available. Unknown web providers
fail with a diagnostic rather than emitting a guessed link.

## Hand-placed fallback

Use only when no renderer can run. Start from `assets/template.html`, keep semantic CSS classes, preserve the inline SVG/accessibility structure, and run the delivery visual checklist. Never introduce inline literal colors that break dark/light parity.

## references/brand-marks.md

# Brand marks

Use a brand mark only when a real product, provider, model family, channel, or
service identity helps the reader. Semantic `type` still explains what the node
does; `brand` explains whose product it is.

## Agent decision path

1. Search the built-in catalogue when the request names a recognizable brand:

   ```bash
   node bin/archify.mjs brands "Claude" --json
   ```

2. Put the returned canonical ID in the node, participant, or state:

   ```json
   {
     "id": "planner",
     "type": "backend",
     "label": "Claude",
     "brand": "claude"
   }
   ```

3. If there is no catalogue match and the user supplied the official website,
   capture its icon explicitly:

   ```bash
   node bin/archify.mjs brands capture "https://partner.example.com" --json
   ```

   Put the command's digest-pinned `brand` value in the authored node:

   ```json
   {
     "id": "partner",
     "type": "external",
     "label": "Partner portal",
     "brand": {
       "url": "https://partner.example.com",
       "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
     }
   }
   ```

4. If there is no match and no user-provided URL, omit `brand`. Do not invent a
   URL or silently assign a visually similar company.

Known-brand URLs resolve to the bundled vector instead of using the network.
Unknown URL capture accepts only bounded raster image formats, blocks
credentials, nonstandard public ports, and private or link-local destinations,
uses bounded concurrency and one total deadline, and returns the captured
content digest. Later render and validate operations require that exact digest;
blocked, unavailable, changed, oversized, or unsafe content fails closed instead
of silently changing the artifact.

The final artifact never fetches a brand asset when opened. Preset vectors and
digest-verified captured site icons remain embedded in SVG, PNG, WebP, JPEG,
Share Card, and WebM exports.

Use `node bin/archify.mjs brands --json` to inspect all canonical IDs, aliases,
categories, domains, and provenance. Current categories cover AI, cloud,
engineering, data, collaboration, business systems, channels, languages, and
frameworks.

## references/delivery-contract.md

# Delivery contract

## Validate and deliver

Use `validate` after every candidate edit. CLI HTML output paths must end in `.html`, including after symbolic-link resolution.
Compare receipt paths must end in `.json`. Explicit CLI paths may be absolute or
outside the current working directory; authored `meta.output` remains confined
to that directory. A type mismatch fails before writing with
`output/cli-extension` or `output/cli-resolved-extension`. These checks prevent
accidental file-type overwrites; they do not sandbox explicit CLI directories
or prevent replacement of an existing artifact of the expected type.

Use final atomic delivery only after the candidate is frozen:

```bash
node bin/archify.mjs deliver <type> <candidate.json> <output.html> --quality showcase --json
```

Deliver reads the specification once, writes those exact bytes to a private same-directory candidate snapshot, renders that snapshot, runs the complete artifact checker, and only replaces the target after all artifact checks pass. The JSON receipt includes SHA-256 and byte counts for both `specification` and `artifact`. Renderer, checker, receipt, or commit failure exits non-zero, removes private state, preserves the previous trusted artifact, and never invokes an opener.

Run `visual-check` only after `deliver` exits zero for the current candidate. If
delivery fails and the output path already exists, that path still names the
previous trusted artifact; running `visual-check` then would measure and capture
stale output, not the rejected candidate. Report the delivery diagnostics and
repair the source before collecting new visual evidence.

The delivery interface exposes three separate claims:

1. `deliver` proves deterministic artifact checks and byte identity.
2. `visual-check` collects automated browser evidence from the exact artifact.
3. Perceptual visual review records a human or image-capable reviewer's judgment.

Passing one claim never implies either of the others. Never claim that the deterministic receipt includes visual review. It does not include browser evidence either.

## Automated browser evidence

After delivery, inspect the exact trusted HTML without rerendering or modifying
it:

```bash
node bin/archify.mjs visual-check <output.html> --json
```

The zero-dependency command uses Chrome/Chromium through the DevTools pipe. It
measures light-theme containment at 1440×900, 1600×1000, 1920×1080, and
2048×1320, then captures light/dark screenshots at 1440×900 and 2048×1320. It
writes four PNG sidecars, one relative-path HTML contact sheet, and one JSON
receipt beside the artifact. The receipt binds the source artifact SHA-256 and
byte count, identifies `evidenceKind: "automated-browser"`, records READ plus
Still runtime state, and always reports `visualReview: "pending"`; automated
browser evidence cannot claim perceptual review.

`browser_evidence` in the handoff records only the outcome of this automated command:

- `passed` maps from exit 0 and receipt `status: "pass"` only after every required measurement and capture completes and passes.
- `failed` maps from exit 1 and receipt `status: "fail"` when the inspection finds a defect, the command fails, or a runtime/capture error leaves the evidence incomplete.
- `skipped` maps only from exit 2 and receipt `status: "skipped"` when Chrome/Chromium is unavailable and the inspection does not run.

Runtime or capture failures leave incomplete evidence and must not be normalized to `skipped`. Failed or skipped capture runs remove stale
image/contact-sheet sidecars rather than presenting prior evidence as current.
They do not invalidate an already successful deterministic delivery, and they
do not turn a perceptual visual review into passed or failed. Retry an
environmental failure through the supported command in a browser-capable
execution context when practical. Keep the packaged transport unchanged unless
the failure reproduces through that seam in a capable environment.

## Optional opening

Add `--open` only when the user wants an immediate local preview. It runs after that atomic commit, uses one argument-array OS opener with a five-second bound, and records `open.status`. Keep it off for CI, unattended agents, and non-interactive environments. Failure or unsupported opening does not invalidate delivery; its status proves only whether the local opener invocation succeeded.

## Last-Good Live Preview

For an active desktop authoring loop only:

```bash
node bin/archify.mjs preview <type> <input>.json <output>.html --quality showcase
```

Preview watches one explicit input on loopback, binds each stable digest to a private snapshot, and advances only after the existing verified delivery pipeline passes. Invalid, half-written, deleted, or superseded input leaves the previous verified revision on screen and on disk. Identical bytes do not rebuild or reload.

The preview runtime ships inside the zero-dependency Skill ZIP and must work without `node_modules`.

Never start it by default. Do not use it for CI, unattended agents, remote sharing, or mobile use. `--no-open` is only for a user who will open the printed local URL or for loop testing. Stop it with Ctrl-C before handoff. Server state, port, source path, diagnostics, error text, and reload tokens must never enter the generated artifact or any export.

## Perceptual delivery gate

Automated validation and browser evidence cannot prove visual polish. After deterministic delivery, inspect the actual HTML in a capable browser or render the evidence screenshots with an image reader. Check both themes when changed, the default READ view, line crossings/corridors, label masks, node/card fit, focus/search/passport closure, and export cleanliness.

For the default standalone desktop viewer, measure 1440×900, 1600×1000, and 1920×1080. When the artifact is intended for a large desktop display, also measure 2048×1320. A first-screen pass requires `document.documentElement.scrollWidth <= window.innerWidth` and `scrollHeight <= window.innerHeight` at every checked size. At the largest checked viewport, inspect the rendered composition for a conspicuous empty lower band: the main panel and necessary conclusion cards should use the available height as a balanced whole, not collapse into a shallow strip. If a desktop viewport overflows, repair the authored composition by removing only genuinely redundant content or compacting spacing before shrinking nodes, labels, or the main panel. Do not hide overflow, clip content, introduce an internal diagram scroller, or reduce node/label typography to make the measurement pass. Narrow/mobile containment may retain vertical page scrolling.

A manual browser record is supplementary to the automated status. Reproducing the same coverage requires all four exact viewport measurements, both endpoint themes, and an artifact-bound record of the inspected SHA-256 and byte count. It never changes `browser_evidence`: when Chrome/Chromium is unavailable, that status remains `skipped` even when the manual browser record is complete and `visual_review: passed`; an automated `failed` result likewise remains `failed`. An unconstrained browser glance can support perceptual review only.

Report exactly one truthful status:

- `visual_review: passed` — only after inspecting the rendered artifact.
- `visual_review: skipped (image reader unavailable)` — when no capable visual surface exists.
- `visual_review: failed` — with the concrete visible defect.

Use `correction_rounds: 0`, `correction_rounds: 1`, or `correction_rounds: 2`; never exceed a maximum of two focused correction rounds. Never report `visual_review: passed` without inspecting the artifact.

If visual review changes the candidate, validation and delivery must run again because the prior frozen specification receipt is no longer current.

## Handoff receipt

Return:

```text
diagram_type: architecture|workflow|sequence|dataflow|lifecycle
output: /absolute/path/to/file.html
specification_sha256: <receipt value>
artifact_sha256: <receipt value>
validation: 9/9 showcase, 0 errors, 0 warnings
browser_evidence: passed|failed|skipped
visual_review: passed|skipped (image reader unavailable)|failed
correction_rounds: 0|1|2
```

Derive `browser_evidence` only from the latest artifact-bound `visual-check` receipt. Record any manual browser work separately with its artifact binding, viewport/theme scope, and observations; never use it or `visual_review` to overwrite the automated status.

Opening, preview status, Share Cards, and other viewer exports are not validation claims.

## references/viewer-runtime.md

# Viewer Runtime reference

Read this only when the user asks for a reader-facing capability. Ordinary generation does not require implementing or re-documenting these features; they are already in the generated HTML.

## Exploration

- Diagram Guide lists current actions and shortcuts.
- Reading Depth starts at READ at the default 100% scale, reveals FULL detail at 175%, and falls back to MAP only below 100%. Focus, story, route, and semantic interactions reveal their exact facts at any scale.
- Semantic Lens summarizes selected node/relationship kinds without changing authored geometry.
- Intent Trace previews a fine-pointer or keyboard target before committed focus.
- Node Finder searches labels and stable IDs.
- Semantic Passport opens on focus, shows authored upstream/downstream facts, supports a copyable deep link, has an explicit close action, closes on true outside activation and Escape, and never enters canonical export.
- Semantic Radar mirrors the visible viewport and authored graph without becoming a second source of truth.
- Direct Relationship Pin makes a unique compiled relationship operable while preserving the authored line and stable relationship identity. It must fail closed on conflicting source/target/label/ID metadata.
- Route Probe resolves exactly two endpoints over authored directed relationships. It never infers a route from geometry.

## Guided views and story

`meta.views` may define at most five curated chapters using stable node IDs. The Named Chapter Rail, Chapter Delta Preview, Story Beat Navigator, Story Follow Camera, Story Director Strip, Story Horizon, and Shareable Story Moment links all derive from that one authored array; none owns parallel topology or layout.

Story transitions classify only the exact relationship between adjacent authored stops: forward, reverse, multiple, or grouped/no direct link. Never infer a transitive edge, verb, causality, or runtime behavior from proximity, kinds, or story order. Playback is reader-started, bounded, stale-safe, and motion-governed.

## Motion and presentation

`meta.animation: "trace"` enables a finite reader-controlled Live/Still trace. Static is the default. Still, reduced motion, page hiding, print, and canonical export preserve complete static meaning. Presentation Stage changes viewer chrome and framing, never authored geometry. This is not a mobile product feature; narrow layouts get containment only.

## Canonical exports

The export menu can copy/download full-diagram PNG, download JPEG/WebP, download a dual-theme SVG, and record a trace-enabled WebM. Viewer state—Guide, Lens, finder, focus, route, story, camera, radar, presentation, motion ownership, and temporary overlays—must be removed from canonical export.

### Share Card

The optional 1200×630 Share Card PNG is for README, release, social, or launch previews. It uses the current theme and visual preset, contains the complete canonical diagram without cropping, and never claims validation. Copy Share Card reuses the same canonical PNG when clipboard image writes are supported.

### Route Share Card

After a real directed Route Probe resolves, the reader may use **Export → Route Share Card**. It reuses the exact ordered route snapshot and the shared Share Card seam: `format=share-card`, `variant=route`. The isolated clone may use only static `data-share-route-*` decoration. It is download-only, fails closed for stale/unreachable/conflicting routes, and never becomes the canonical artifact.

### Reach Share Card

After a non-empty authored reachability query, the reader may use **Export → Reach Share Card**. It consumes the already resolved upstream/downstream node and edge set without rerunning traversal: `format=share-card`, `variant=reach`. The isolated clone may use only static `data-share-reach-*` decoration. It is download-only. Call it authored reachability—not impact, blast radius, breakage, or runtime causality.

## Truth boundary

Viewer exports are communication assets. They do not replace the checked HTML, the deterministic delivery receipt, or a real visual review. Do not add a hosted service, storage surface, dependency, schema branch, or mobile product surface for these viewer-only capabilities.

## renderers

```

```

## renderers/architecture

```

```

## renderers/architecture/grid.mjs

```js
/** Grid placement for architecture IR (#8). Not auto-layout — fixed cell math only. */

export const DEFAULT_GRID = {
  mode: 'grid',
  origin: [40, 80],
  cols: 4,
  gapX: 30,
  gapY: 40,
  cellW: 130,
  cellH: 64,
};

export function gridLayout(arch) {
  const raw = arch.layout;
  if (!raw || raw.mode !== 'grid') return null;
  return { ...DEFAULT_GRID, ...raw };
}

export function resolveComponentPos(component, grid) {
  if (Array.isArray(component.pos) && component.pos.length === 2) {
    return component.pos;
  }
  if (!grid) return [NaN, NaN];
  if (!Number.isInteger(component.row) || !Number.isInteger(component.col)) {
    return [NaN, NaN];
  }
  const [ox, oy] = grid.origin;
  const stepX = grid.cellW + grid.gapX;
  const stepY = grid.cellH + grid.gapY;
  return [ox + component.col * stepX, oy + component.row * stepY];
}

export function validateGridPlacement(arch, grid, problems) {
  if (!grid) return;
  if (arch.layout !== undefined && arch.layout.mode !== 'grid') {
    problems.push('layout.mode must be "grid" when layout is set (free placement omits layout entirely).');
    return;
  }
  const seen = new Map();
  for (const c of arch.components ?? []) {
    const hasPos = Array.isArray(c.pos) && c.pos.length === 2;
    const hasCell = Number.isInteger(c.row) && Number.isInteger(c.col);
    if (hasPos) continue; // pos wins; row/col are optional hints only
    if (!hasPos && !hasCell) {
      problems.push(`Component "${c.id}" needs pos [x,y] or grid row/col when layout.mode is "grid".`);
      continue;
    }
    if (c.row < 0 || c.col < 0) {
      problems.push(`Component "${c.id}" row/col must be non-negative integers.`);
      continue;
    }
    if (c.col >= grid.cols) {
      problems.push(`Component "${c.id}" col ${c.col} exceeds layout.cols ${grid.cols} (valid: 0..${grid.cols - 1}).`);
    }
    const key = `${c.row},${c.col}`;
    if (seen.has(key)) {
      problems.push(`Components "${seen.get(key)}" and "${c.id}" share grid cell row ${c.row} col ${c.col}.`);
    } else {
      seen.set(key, c.id);
    }
  }
}
```

## renderers/architecture/render-architecture.mjs

```js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs';
import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs';
import { componentBox, boundaryBox, connectionPath } from '../shared/layout-report.mjs';
import { throwDiagnosticProblems } from '../shared/diagnostics.mjs';
import { legendFootprint, relationshipLegendObstacles, resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs';
import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs';
import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs';
import { minimumReadableSourceTextPx } from '../shared/desktop-readability.mjs';
import { translateMessage as i18nText } from '../shared/i18n.mjs';
import { gridLayout, resolveComponentPos, validateGridPlacement } from './grid.mjs';
import {
  asArray,
  isFinitePoint,
  rectsOverlap,
  segmentIntersectsRect,
  cleanEndpointSideProblems,
  cleanFlowProblems,
  cleanCrossingProblems,
  cleanAmbiguousCorridorProblems,
  cleanBorderRunProblems,
  cleanRouteRhythmProblems,
  cleanLabelRouteClearanceProblems,
  suggestLabelObstacleFix,
  suggestComponentSeparation,
  anchor,
  automaticPortSpread,
  automaticPortRhythmBridge,
  defaultFromSide,
  defaultToSide,
  chosenSide,
  routeHonorsEndpointSides,
  normalizeRoutePoints,
  polylinePath,
  routePointsValue,
  roundedPath,
  labelPoint,
  componentFill,
  componentText,
  arrowClassMap,
  variantAccent,
} from '../shared/geometry.mjs';

const componentTextFit = {
  sublabelPreferred: 9,
  sublabelMinimum: 6,
  tagPreferred: 7,
  tagMinimum: 6,
};

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const layoutJsonMode = process.argv.includes('--layout-json');
const cliArgs = process.argv.filter((arg) => arg !== '--layout-json');
const { diagram: arch, template, outPath, sourceEvidence } = await loadDiagramWithBrandMarks({
  rendererDir: __dirname,
  diagramType: 'architecture',
  defaultExample: 'web-app.architecture.json',
  argv: cliArgs,
});

const grid = gridLayout(arch);

const layout = {
  defaultW: 120,
  defaultH: 60,
  margin: 40,
  // Boundary padding — the 30/50 rule that was a hand-arithmetic footgun
  // (CHANGELOG v2.2.1): 30px on top/left/right, plus 20px extra at the bottom.
  boundaryPad: 30,
  boundaryExtraBottom: 20,
  boundaryLabelBaseline: 18,
  boundaryLabelClearance: 4,
  boundaryLabelFontPreferred: 9,
  boundaryLabelFontMinimum: 6,
  boundaryLabelMaskHeight: 16,
  boundaryLabelRailGap: 2,
  boundaryLabelFrameInset: 4,
  legendH: 28,
};

const LEGEND_CATALOG = [
  'frontend',
  'backend',
  'database',
  'cloud',
  'security',
  'messagebus',
  'external',
].map((kind) => ({ kind, label: i18nText(arch.meta.locale, `legend.architecture.${kind}`) }));

// ---- Measure components from free coordinates --------------------------------
function measureComponent(c) {
  const [x, y] = resolveComponentPos(c, grid);
  const [w, h] = Array.isArray(c.size) ? c.size : [layout.defaultW, layout.defaultH];
  return { ...c, x, y, width: w, height: h, cx: x + w / 2, cy: y + h / 2 };
}

const components = new Map(asArray(arch.components).map((c) => [c.id, measureComponent(c)]));
const enforcesBoundaryTitleComposition = Boolean(arch.meta?.quality_profile);
const componentSteps = new Map();
for (const [index, conn] of asArray(arch.connections).entries()) {
  if (!componentSteps.has(conn.from)) componentSteps.set(conn.from, index);
  if (!componentSteps.has(conn.to)) componentSteps.set(conn.to, index + 1);
}
for (const [index, c] of asArray(arch.components).entries()) {
  if (!componentSteps.has(c.id)) componentSteps.set(c.id, index);
}

// ---- Boundaries computed from the `wraps` id list ---------------------------
function boundaryRect(boundary) {
  const members = asArray(boundary.wraps).map((id) => components.get(id)).filter(Boolean);
  if (!members.length) return null;
  const minX = Math.min(...members.map((m) => m.x));
  const minY = Math.min(...members.map((m) => m.y));
  const maxX = Math.max(...members.map((m) => m.x + m.width));
  const maxY = Math.max(...members.map((m) => m.y + m.height));
  const pad = boundary.pad ?? layout.boundaryPad;
  const topPad = Math.max(
    pad,
    layout.boundaryLabelBaseline + layout.boundaryLabelClearance,
  );
  return {
    ...boundary,
    x: minX - pad,
    y: minY - topPad,
    width: maxX - minX + pad * 2,
    height: maxY - minY + topPad + layout.boundaryExtraBottom,
    memberTop: minY,
  };
}

function rectContains(outer, inner) {
  const epsilon = 1e-9;
  return outer.x <= inner.x + epsilon
    && outer.y <= inner.y + epsilon
    && outer.x + outer.width + epsilon >= inner.x + inner.width
    && outer.y + outer.height + epsilon >= inner.y + inner.height;
}

function boundaryLabelWidth(label, fontSize) {
  return Math.max(30, textUnits(label) * fontSize * 0.6 + 10);
}

const architectureLegendEntries = resolveLegend(
  arch.meta?.legend,
  LEGEND_CATALOG,
  new Set([...components.values()].map((component) => component.type)),
);

function autoViewBoxFor(candidateBoundaries) {
  const maxX = Math.max(
    0,
    ...[...components.values()].map((component) => component.x + component.width),
    ...candidateBoundaries.map((boundary) => boundary.x + boundary.width),
  );
  const maxY = Math.max(
    0,
    ...[...components.values()].map((component) => component.y + component.height),
    ...candidateBoundaries.map((boundary) => boundary.y + boundary.height),
  );
  let width = Math.ceil(maxX + layout.margin);
  let footprint = legendFootprint(architectureLegendEntries, {
    width: Math.max(1, width - layout.margin * 2),
  });
  if (footprint.minWidth > width - layout.margin * 2) {
    width = Math.ceil(footprint.minWidth + layout.margin * 2);
    footprint = legendFootprint(architectureLegendEntries, {
      width: width - layout.margin * 2,
    });
  }
  return [
    width,
    Math.ceil(maxY + layout.margin + layout.legendH + footprint.extraHeight),
  ];
}

function resolvedViewBoxWidth(candidateBoundaries) {
  if (Array.isArray(arch.meta?.viewBox) && Number.isFinite(arch.meta.viewBox[0])) {
    return arch.meta.viewBox[0];
  }
  return autoViewBoxFor(candidateBoundaries)[0];
}

function expandBoundaryForReadableTitle(boundary, minimumFontSize) {
  if (!enforcesBoundaryTitleComposition) return boundary;
  const requiredWidth = boundaryLabelWidth(boundary.label, minimumFontSize)
    + layout.boundaryLabelFrameInset * 2;
  const extra = Math.max(0, requiredWidth - boundary.width);
  if (!extra) return boundary;
  return {
    ...boundary,
    x: boundary.x - extra / 2,
    width: boundary.width + extra,
  };
}

function measureBoundaryTitle(boundary, minimumFontSize) {
  const availableWidth = Math.max(0, boundary.width - layout.boundaryLabelFrameInset * 2);
  const units = textUnits(boundary.label);
  const fitted = units > 0
    ? (availableWidth - 10) / (units * 0.6)
    : layout.boundaryLabelFontPreferred;
  const preferredFontSize = Math.max(layout.boundaryLabelFontPreferred, minimumFontSize);
  const fontSize = Math.max(
    minimumFontSize,
    Math.min(preferredFontSize, fitted),
  );
  const desiredWidth = boundaryLabelWidth(boundary.label, fontSize);
  const height = Math.max(layout.boundaryLabelMaskHeight, Math.ceil(fontSize + 7));
  return {
    x: boundary.x + layout.boundaryLabelFrameInset,
    y: boundary.memberTop
      - layout.boundaryLabelClearance
      - height,
    width: Math.min(availableWidth, desiredWidth),
    height,
    fontSize,
    minimumFontSize,
    baselineOffset: fontSize + 4,
    availableWidth,
    minimumWidth: boundaryLabelWidth(boundary.label, minimumFontSize),
  };
}

function horizontalOverlap(left, right) {
  return left.x < right.x + right.width && left.x + left.width > right.x;
}

function layoutBoundaryTitles(rawBoundaries, minimumFontSize) {
  const placedTitles = [];
  const measured = new Map();
  const ordered = rawBoundaries
    .map((boundary, index) => ({ boundary, index }))
    .sort((left, right) => {
      const areaDelta = left.boundary.width * left.boundary.height
        - right.boundary.width * right.boundary.height;
      return areaDelta || left.index - right.index;
    });

  for (const entry of ordered) {
    const { index } = entry;
    const boundary = expandBoundaryForReadableTitle(entry.boundary, minimumFontSize);
    const title = measureBoundaryTitle(boundary, minimumFontSize);
    let guard = 0;
    while (guard < rawBoundaries.length + components.size + 1) {
      guard += 1;
      const blockers = [
        ...placedTitles,
        ...components.values(),
      ].filter((candidate) => horizontalOverlap(title, candidate) && rectsOverlap(title, candidate));
      if (!blockers.length) break;
      title.y = Math.min(
        ...blockers.map((blocker) => blocker.y - layout.boundaryLabelRailGap - title.height),
      );
    }
    placedTitles.push(title);
    measured.set(index, { boundary, title });
  }

  return rawBoundaries.map((_boundary, index) => {
    const { boundary, title } = measured.get(index);
    const bottom = boundary.y + boundary.height;
    // Profile-less schema-v1 inputs keep their legacy boundary geometry. A
    // quality profile opts into the stricter title-composition contract and
    // may expand the frame to contain an adapted title rail.
    const y = enforcesBoundaryTitleComposition
      ? Math.min(boundary.y, title.y - layout.boundaryLabelFrameInset)
      : boundary.y;
    return {
      ...boundary,
      y,
      height: bottom - y,
      title,
    };
  });
}

const rawBoundaries = asArray(arch.boundaries).map(boundaryRect).filter(Boolean);
function resolveBoundaryTitles() {
  if (!enforcesBoundaryTitleComposition || rawBoundaries.length === 0) {
    return {
      boundaries: layoutBoundaryTitles(rawBoundaries, layout.boundaryLabelFontMinimum),
      readabilityProblem: null,
    };
  }

  const maximumIterations = 32;
  let candidateBoundaries = rawBoundaries;
  for (let iteration = 0; iteration < maximumIterations; iteration += 1) {
    const budgetViewBoxWidth = resolvedViewBoxWidth(candidateBoundaries);
    const minimumFontSize = Math.max(
      layout.boundaryLabelFontMinimum,
      minimumReadableSourceTextPx(budgetViewBoxWidth) + 1e-6,
    );
    const nextBoundaries = layoutBoundaryTitles(rawBoundaries, minimumFontSize);
    const finalViewBoxWidth = resolvedViewBoxWidth(nextBoundaries);
    const finalMinimumFontSize = Math.max(
      layout.boundaryLabelFontMinimum,
      minimumReadableSourceTextPx(finalViewBoxWidth),
    );
    if (minimumFontSize >= finalMinimumFontSize) {
      return { boundaries: nextBoundaries, readabilityProblem: null };
    }
    candidateBoundaries = nextBoundaries;
  }

  const finalViewBoxWidth = resolvedViewBoxWidth(candidateBoundaries);
  return {
    boundaries: candidateBoundaries,
    readabilityProblem: `[composition/desktop-readability] Boundary title layout did not converge after ${maximumIterations} iterations for the final ${finalViewBoxWidth}px viewBox — shorten boundary labels, provide a wider authored viewBox, or move wrapped components closer to the left edge.`,
  };
}

const resolvedBoundaryTitles = resolveBoundaryTitles();
const boundaries = resolvedBoundaryTitles.boundaries;
const compositionFrames = boundaries.map((boundary, index) => ({
  ...boundary,
  id: boundary.id || index,
  kind: boundary.kind || 'boundary',
  radius: boundary.kind === 'security-group' ? 8 : 12,
}));

function componentContext(component) {
  const scopes = boundaries
    .filter((boundary) => asArray(boundary.wraps).includes(component.id))
    .sort((a, b) => (b.width * b.height) - (a.width * a.height))
    .map((boundary) => boundary.label);
  return scopes.length ? scopes.join(' › ') : i18nText(arch.meta.locale, 'node.context.architecture');
}

// ---- Auto viewBox: fit all geometry + the measured resolved legend ----------
const viewBox = arch.meta?.viewBox || autoViewBoxFor(boundaries);
const legendY = () => viewBox[1] - 16;

// ---- Validation: mechanical correctness, never layout taste -----------------
function validateArchitecture() {
  const problems = [];
  if (resolvedBoundaryTitles.readabilityProblem) {
    problems.push(resolvedBoundaryTitles.readabilityProblem);
  }
  const requiresNestedBoundaryMembership = arch.meta?.engineering_profile === 'deployment-ownership';
  if (components.size !== asArray(arch.components).length) problems.push('Component ids must be unique.');
  if (grid) {
    validateGridPlacement(arch, grid, problems);
  } else {
    for (const c of asArray(arch.components)) {
      if (!Array.isArray(c.pos) || c.pos.length !== 2) {
        problems.push(`Component "${c.id}" must include pos [x, y] when layout.mode is omitted (free placement).`);
      }
    }
  }

  for (const c of components.values()) {
    if (!isFinitePoint(c.x, c.y, c.width, c.height)) {
      problems.push(`Component "${c.id}" has non-finite pos/size — pos and size must be [number, number].`);
      continue;
    }
    if (c.width <= 0 || c.height <= 0) {
      problems.push(`Component "${c.id}" has invalid size ${c.width}x${c.height} — width and height must be greater than 0.`);
      continue;
    }
    if (c.x < 0 || c.y < 0 || c.x + c.width > viewBox[0] || c.y + c.height > viewBox[1]) {
      problems.push(`Component "${c.id}" falls outside the viewBox ${viewBox[0]}x${viewBox[1]} — adjust pos/size or set a larger meta.viewBox.`);
    }
    const estLabelW = textUnits(c.label) * 6.6;
    if (estLabelW > c.width + 8) {
      problems.push(`Label "${c.label}" (~${Math.round(estLabelW)}px) is wider than component "${c.id}" (${c.width}px) — shorten the label or widen size.`);
    }
    const brandRailProblem = brandTopRailProblem(c, c.width, 8, 'Component');
    if (brandRailProblem) problems.push(brandRailProblem);
    // sublabel and tag render as single unwrapped <text> elements; shrink-to-fit
    // handles the ordinary case, this rejects what it cannot rescue.
    const availableTextW = availableNodeTextWidth(c.width);
    for (const [field, value, minimum] of [
      ['Sublabel', c.sublabel, componentTextFit.sublabelMinimum],
      ['Tag', c.tag, componentTextFit.tagMinimum],
    ]) {
      if (!value) continue;
      const minimumW = minimumNodeTextWidth(value, minimum);
      if (minimumW > availableTextW) {
        problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but component "${c.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or widen size.`);
      }
    }
  }

  // Component overlap — the highest-traffic hand-placement failure mode.
  const list = [...components.values()];
  for (let i = 0; i < list.length; i += 1) {
    for (let j = i + 1; j < list.length; j += 1) {
      if (rectsOverlap(list[i], list[j], 8)) {
        problems.push(`Components "${list[i].id}" and "${list[j].id}" are less than 8px apart — move one or shrink its size.\n${suggestComponentSeparation(list[i], list[j], 8)}`);
      }
    }
  }

  // Boundaries: every wrapped id must exist; the computed box must stay in view.
  for (const boundary of asArray(arch.boundaries)) {
    for (const id of asArray(boundary.wraps)) {
      if (!components.has(id)) problems.push(`Boundary "${boundary.label}" wraps unknown component "${id}".`);
    }
  }
  const viewBoxRect = { x: 0, y: 0, width: viewBox[0], height: viewBox[1] };
  for (const boundary of boundaries) {
    if (!enforcesBoundaryTitleComposition) continue;
    if (boundary.title.minimumWidth > boundary.title.availableWidth) {
      problems.push(
        `Boundary label "${boundary.label}" needs ~${Math.ceil(boundary.title.minimumWidth)}px to fit at the `
        + `${Number(boundary.title.minimumFontSize.toFixed(2))}px desktop-readable source minimum, but its frame provides ${Math.floor(boundary.title.availableWidth)}px — `
        + 'shorten the boundary label, increase pad, or widen the wrapped component layout.',
      );
    }
    if (!rectContains(boundary, boundary.title)) {
      problems.push(
        `Boundary label "${boundary.label}" extends outside its final frame — shorten the label or increase boundary pad.`,
      );
    }
    if (!rectContains(viewBoxRect, boundary.title)) {
      problems.push(
        `Boundary label "${boundary.label}" extends outside the viewBox — move wrapped components away from the canvas edge, shorten the label, or increase the viewBox.`,
      );
    }
    for (const component of components.values()) {
      if (!rectsOverlap(boundary.title, component)) continue;
      problems.push(
        `Boundary label "${boundary.label}" overlaps component "${component.id}" — move the component, increase boundary title space, or shorten the label.`,
      );
    }
  }
  for (let leftIndex = 0; leftIndex < boundaries.length; leftIndex += 1) {
    const left = boundaries[leftIndex];
    const leftMembers = new Set(asArray(left.wraps));
    for (let rightIndex = leftIndex + 1; rightIndex < boundaries.length; rightIndex += 1) {
      const right = boundaries[rightIndex];
      if (enforcesBoundaryTitleComposition && rectsOverlap(left.title, right.title)) {
        problems.push(
          `Boundary labels "${left.label}" and "${right.label}" overlap — shorten a label or increase boundary title space.`,
        );
      }
      // Ordinary architecture boundaries are sets, not an implied ownership
      // tree: orthogonal scopes such as runtime and compliance may share some
      // components while each contains others. The opt-in deployment profile
      // does promise hierarchical region/private-scope membership, so only it
      // receives the stricter membership-to-frame containment contract.
      if (!requiresNestedBoundaryMembership) continue;
      const rightMembers = new Set(asArray(right.wraps));
      const shared = [...leftMembers].filter((id) => rightMembers.has(id));
      const leftNested = [...leftMembers].every((id) => rightMembers.has(id));
      const rightNested = [...rightMembers].every((id) => leftMembers.has(id));
      if (shared.length && !leftNested && !rightNested) {
        const leftOnly = [...leftMembers].filter((id) => !rightMembers.has(id));
        const rightOnly = [...rightMembers].filter((id) => !leftMembers.has(id));
        problems.push(
          `Boundary "${left.label}" crosses boundary "${right.label}" because their memberships partially overlap `
          + `(shared: ${shared.map((id) => `"${id}"`).join(', ')}; `
          + `only in "${left.label}": ${leftOnly.map((id) => `"${id}"`).join(', ')}; `
          + `only in "${right.label}": ${rightOnly.map((id) => `"${id}"`).join(', ')}) — `
          + 'keep one boundary fully nested by removing outside members, or split the boundary.',
        );
        continue;
      }

      if (!rectsOverlap(left, right)) continue;
      const leftContainsRight = rectContains(left, right);
      const rightContainsLeft = rectContains(right, left);
      if (!leftContainsRight && !rightContainsLeft) {
        problems.push(
          `Boundary "${left.label}" and boundary "${right.label}" final frames partially overlap — `
          + 'adjust wraps, pad, or component positions so the frames are disjoint or one fully contains the other.',
        );
        continue;
      }

      if (!shared.length) {
        problems.push(
          `Boundary "${left.label}" and boundary "${right.label}" final frames overlap even though their memberships are disjoint — `
          + 'adjust pad or component positions so the frames are disjoint, or make wraps express the intended nesting.',
        );
        continue;
      }

      const containmentMatchesMembership = (leftNested && rightContainsLeft)
        || (rightNested && leftContainsRight);
      if (!containmentMatchesMembership) {
        problems.push(
          `Boundary "${left.label}" and boundary "${right.label}" final frame containment contradicts their wraps membership — `
          + 'reduce the inner boundary pad, move its components, or correct wraps so geometry and nesting agree.',
        );
      }
    }
  }
  for (const b of boundaries) {
    if (b.x < 0 || b.y < 0 || b.x + b.width > viewBox[0] || b.y + b.height > viewBox[1]) {
      problems.push(`Boundary "${b.label}" extends outside the viewBox — its members sit too close to the canvas edge; add margin or enlarge meta.viewBox.`);
    }
  }

  for (const conn of asArray(arch.connections)) {
    if (!components.has(conn.from)) problems.push(`Connection "${conn.label || conn.from}" references unknown source "${conn.from}".`);
    if (!components.has(conn.to)) problems.push(`Connection "${conn.label || conn.to}" references unknown target "${conn.to}".`);
    if (components.has(conn.from) && components.has(conn.to)) {
      const routed = pathFor(conn);
      const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]];
      const distance = Math.hypot(end[0] - start[0], end[1] - start[1]);
      if (distance < 24) problems.push(`Connection "${conn.label || `${conn.from}->${conn.to}`}" is too short (${Math.round(distance)}px; minimum 24px) — place its components farther apart.`);
    }
  }

  problems.push(...cleanEndpointSideProblems({
    relations: arch.connections,
    endpointIds: new Set(components.keys()),
    pathFor,
    diagramType: 'architecture',
    relationCollection: 'connections',
    fromSideFor: (conn) => connectionEndpointSide(conn, 'source'),
    toSideFor: (conn) => connectionEndpointSide(conn, 'target'),
    routeHint: 'keep automatic routing so the renderer can use a side-aware bridge, or set truthful fromSide/toSide with perpendicular via segments',
  }));
  problems.push(...cleanFlowProblems({
    relations: arch.connections,
    obstacles: components.values(),
    pathFor,
    diagramType: 'architecture',
    relationCollection: 'connections',
    obstacleKind: 'component',
    routeHint: 'adjust fromSide/toSide, set route/via, or move the component'
  }));
  problems.push(...cleanCrossingProblems({
    relations: arch.connections,
    endpointIds: new Set(components.keys()),
    pathFor,
    diagramType: 'architecture',
    relationCollection: 'connections',
    profile: arch.meta?.quality_profile,
    routeHint: 'adjust route/via or fromSide/toSide so the connections use separate corridors'
  }));
  problems.push(...cleanAmbiguousCorridorProblems({
    relations: arch.connections,
    endpointIds: new Set(components.keys()),
    pathFor,
    diagramType: 'architecture',
    relationCollection: 'connections',
    profile: arch.meta?.quality_profile,
    routeHint: 'adjust route/via or fromSide/toSide so unrelated connections do not visually merge'
  }));
  problems.push(...cleanBorderRunProblems({
    relations: arch.connections,
    endpointIds: new Set(components.keys()),
    frames: compositionFrames,
    pathFor,
    diagramType: 'architecture',
    relationCollection: 'connections',
    profile: arch.meta?.quality_profile,
    routeHint: 'adjust route/via or fromSide/toSide so the connection crosses the boundary perpendicularly instead of following its border'
  }));
  problems.push(...cleanRouteRhythmProblems({
    relations: arch.connections,
    endpointIds: new Set(components.keys()),
    pathFor,
    diagramType: 'architecture',
    relationCollection: 'connections',
    profile: arch.meta?.quality_profile,
    routeHint: 'move route/via points into a wider corridor or move the component so every turn has room to read'
  }));

  // Connection labels must not land on top of components.
  const labelRects = [];
  for (const [connectionIndex, conn] of asArray(arch.connections).entries()) {
    if (!conn.label || !components.has(conn.from) || !components.has(conn.to)) continue;
    const [lx, ly] = labelPoint(conn, pathFor(conn).points);
    const w = Math.max(30, textUnits(conn.label) * 4.8 + 10);
    labelRects.push({ relation: conn, relationIndex: connectionIndex, label: conn.label, x: lx - w / 2, y: ly - 10, width: w, height: 14, lx, ly });
  }
  for (const rect of labelRects) {
    for (const c of components.values()) {
      if (rectsOverlap(rect, c, -2)) {
        problems.push(`Label "${rect.label}" overlaps component "${c.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, c)}`);
      }
    }
    if (enforcesBoundaryTitleComposition) {
      for (const boundary of boundaries) {
        if (!rectsOverlap(boundary.title, rect)) continue;
        problems.push(
          `Boundary label "${boundary.label}" overlaps connection label "${rect.label}" — move the boundary title rail by adjusting wrapped component positions, or move the connection label with labelAt/labelDx/labelDy/labelSegment.`,
        );
      }
    }
  }
  problems.push(...cleanLabelRouteClearanceProblems({
    relations: arch.connections,
    labels: labelRects,
    endpointIds: new Set(components.keys()),
    pathFor,
    diagramType: 'architecture',
    relationCollection: 'connections',
    profile: arch.meta?.quality_profile,
  }));

  if (problems.length) {
    throwDiagnosticProblems('Architecture layout validation failed', problems, {
      subject: { diagramType: 'architecture' },
    });
  }
}

function buildLayoutReport() {
  const labels = [];
  for (const conn of asArray(arch.connections)) {
    if (!conn.label || !components.has(conn.from) || !components.has(conn.to)) continue;
    const [lx, ly] = labelPoint(conn, pathFor(conn).points);
    const w = Math.max(30, textUnits(conn.label) * 4.8 + 10);
    labels.push({
      text: conn.label,
      x: Math.round(lx - w / 2),
      y: Math.round(ly - 10),
      width: Math.round(w),
      height: 14,
      labelAt: [Math.round(lx), Math.round(ly)],
    });
  }
  return {
    ok: true,
    diagram_type: 'architecture',
    layout: grid ? { mode: 'grid', ...grid } : { mode: 'free' },
    viewBox,
    components: [...components.values()].map(componentBox),
    boundaries: boundaries.map(boundaryBox),
    connections: asArray(arch.connections)
      .filter((conn) => components.has(conn.from) && components.has(conn.to))
      .map((conn) => {
        const routed = pathFor(conn);
        const labelAt = conn.label ? labelPoint(conn, routed.points) : null;
        return connectionPath(conn, routed, labelAt);
      }),
    labels,
  };
}

// ---- Connection routing ------------------------------------------------------
function routeClearsComponents(conn, points, clearance = 2) {
  const endpointIds = new Set([conn.from, conn.to]);
  for (const component of components.values()) {
    if (endpointIds.has(component.id)) continue;
    for (let index = 0; index < points.length - 1; index += 1) {
      if (segmentIntersectsRect({ start: points[index], end: points[index + 1] }, component, clearance)) {
        return false;
      }
    }
  }
  return true;
}

function routeClearsEndpointComponents(points, from, to) {
  const lastSegment = points.length - 2;
  for (let index = 0; index <= lastSegment; index += 1) {
    const segment = { start: points[index], end: points[index + 1] };
    if (index > 0 && segmentIntersectsRect(segment, from)) return false;
    if (index < lastSegment && segmentIntersectsRect(segment, to)) return false;
  }
  return true;
}

const OUTWARD_SIDE_VECTOR = {
  left: [-1, 0],
  right: [1, 0],
  top: [0, -1],
  bottom: [0, 1],
};

function outwardStub(point, side, distance = 24) {
  const [dx, dy] = OUTWARD_SIDE_VECTOR[side] || [0, 0];
  return [point[0] + dx * distance, point[1] + dy * distance];
}

function collinearBacktrack(a, b, c) {
  const first = [b[0] - a[0], b[1] - a[1]];
  const second = [c[0] - b[0], c[1] - b[1]];
  const cross = first[0] * second[1] - first[1] * second[0];
  const dot = first[0] * second[0] + first[1] * second[1];
  return Math.abs(cross) <= 0.0001 && dot < -0.0001;
}

function sideAwareBridgeCandidates(start, end, fromSide, toSide) {
  const startStub = outwardStub(start, fromSide);
  const endStub = outwardStub(end, toSide);
  const rawCandidates = [];
  const minimumBridge = 16;
  const verticalSides = new Set(['top', 'bottom']);
  const horizontalSides = new Set(['left', 'right']);

  // Port spreading can leave parallel-side anchors only a few pixels apart.
  // Route through a bounded outside channel so we keep both endpoint normals
  // without introducing a tiny, noisy connector between the two stubs.
  if (verticalSides.has(fromSide) && verticalSides.has(toSide)
      && Math.abs(start[0] - end[0]) < minimumBridge) {
    for (const channelX of [
      Math.max(start[0], end[0]) + minimumBridge,
      Math.min(start[0], end[0]) - minimumBridge,
    ]) {
      rawCandidates.push([
        startStub,
        [channelX, startStub[1]],
        [channelX, endStub[1]],
        endStub,
      ]);
    }
  }
  if (horizontalSides.has(fromSide) && horizontalSides.has(toSide)
      && Math.abs(start[1] - end[1]) < minimumBridge) {
    for (const channelY of [
      Math.max(start[1], end[1]) + minimumBridge,
      Math.min(start[1], end[1]) - minimumBridge,
    ]) {
      rawCandidates.push([
        startStub,
        [startStub[0], channelY],
        [endStub[0], channelY],
        endStub,
      ]);
    }
  }

  rawCandidates.push(
    [startStub, [endStub[0], startStub[1]], endStub],
    [startStub, [startStub[0], endStub[1]], endStub],
  );
  return rawCandidates.map((candidate) => normalizeRoutePoints([start, ...candidate, end]))
    .filter((points) => points.length >= 2)
    .filter((points) => !collinearBacktrack(points[0], points[1], points[2] || points[1]))
    .filter((points) => !collinearBacktrack(points.at(-3) || points.at(-2), points.at(-2), points.at(-1)))
    .filter((points) => routeHonorsEndpointSides(points, fromSide, toSide))
    .map((points) => points.slice(1, -1));
}

const AUTOMATIC_PORT_CORNER_GUTTER = 16;
const AUTOMATIC_PORT_ALIGNMENT_DELTA = 16;

function portHasCornerClearance(rect, side, point) {
  if (side === 'left' || side === 'right') {
    const inset = Math.min(AUTOMATIC_PORT_CORNER_GUTTER, rect.height / 2);
    return point[1] >= rect.y + inset && point[1] <= rect.y + rect.height - inset;
  }
  if (side === 'top' || side === 'bottom') {
    const inset = Math.min(AUTOMATIC_PORT_CORNER_GUTTER, rect.width / 2);
    return point[0] >= rect.x + inset && point[0] <= rect.x + rect.width - inset;
  }
  return false;
}

function alignFacingPorts(conn, from, to, start, end, fromSide, toSide, ports) {
  const hasExplicitGeometry = (
    conn.via
    || (conn.route && conn.route !== 'auto')
    || conn.channelX !== undefined
    || conn.channelY !== undefined
    || conn.labelAt
  );
  const horizontallyFacing = (
    (fromSide === 'right' && toSide === 'left')
    || (fromSide === 'left' && toSide === 'right')
  );
  const verticallyFacing = (
    (fromSide === 'bottom' && toSide === 'top')
    || (fromSide === 'top' && toSide === 'bottom')
  );
  if (hasExplicitGeometry || (!horizontallyFacing && !verticallyFacing)) return { start, end };

  const fromSpread = Boolean(ports?.from);
  const toSpread = Boolean(ports?.to);
  if (fromSpread && toSpread) return { start, end };
  const hasExplicitSides = (
    (conn.fromSide && conn.fromSide !== 'auto')
    || (conn.toSide && conn.toSide !== 'auto')
  );
  if (!fromSpread && !toSpread && hasExplicitSides) return { start, end };

  const alignmentDelta = horizontallyFacing
    ? Math.abs(start[1] - end[1])
    : Math.abs(start[0] - end[0]);
  if (alignmentDelta >= AUTOMATIC_PORT_ALIGNMENT_DELTA) return { start, end };

  // Keep the shared endpoint's distinct spread slot and move only the
  // relationship's unshared endpoint onto that axis. With no spread endpoint,
  // retain the existing least-movement choice between the two facing sides.
  // If both endpoints are shared, preserve the outside bridge so no competing
  // port is silently collapsed.
  const alignEndToStart = horizontallyFacing
    ? { start, end: [end[0], start[1]] }
    : { start, end: [start[0], end[1]] };
  const alignStartToEnd = horizontallyFacing
    ? { start: [start[0], end[1]], end }
    : { start: [end[0], start[1]], end };
  const candidates = fromSpread
    ? [alignEndToStart]
    : toSpread
      ? [alignStartToEnd]
      : [alignEndToStart, alignStartToEnd];
  for (const candidate of candidates) {
    const points = [candidate.start, candidate.end];
    if (portHasCornerClearance(from, fromSide, candidate.start)
        && portHasCornerClearance(to, toSide, candidate.end)
        && routeHonorsEndpointSides(points, fromSide, toSide)
        && routeClearsEndpointComponents(points, from, to)
        && routeClearsComponents(conn, points)) {
      return candidate;
    }
  }
  return { start, end };
}

function routeVia(conn, from, to, start, end, fromSide, toSide) {
  if (conn.via) return conn.via;
  switch (conn.route || 'auto') {
    case 'straight':
      return [];
    case 'orthogonal-h': {
      const midX = (start[0] + end[0]) / 2;
      return [[midX, start[1]], [midX, end[1]]];
    }
    case 'orthogonal-v': {
      const midY = (start[1] + end[1]) / 2;
      return [[start[0], midY], [end[0], midY]];
    }
    case 'auto':
    default: {
      // Direct line unless the anchors are clearly orthogonal-friendly.
      const deltaX = Math.abs(start[0] - end[0]);
      const deltaY = Math.abs(start[1] - end[1]);
      if ((deltaX < 4 || deltaY < 4) && routeHonorsEndpointSides([start, end], fromSide, toSide)) return [];

      const rhythmBridge = automaticPortRhythmBridge(start, end, fromSide, toSide, {
        accept: (points) => (
          routeClearsEndpointComponents(points, from, to)
          && routeClearsComponents(conn, points)
        ),
      });
      if (rhythmBridge) return rhythmBridge.slice(1, -1);

      // Automatic port spreading can leave otherwise aligned endpoints only a
      // few pixels apart. A midpoint route would split that tiny difference
      // into two unreadable endpoint stubs, so take a bounded outside channel
      // when both anchors sit on parallel component sides.
      const minimumStub = 8;
      const fromVerticalSide = start[1] === from.y || start[1] === from.y + from.height;
      const toVerticalSide = end[1] === to.y || end[1] === to.y + to.height;
      if (fromVerticalSide && toVerticalSide && deltaX < minimumStub * 2) {
        const outsideChannels = [
          Math.max(start[0], end[0]) + minimumStub * 2,
          Math.min(start[0], end[0]) - minimumStub * 2,
        ];
        for (const channelX of outsideChannels) {
          const candidate = [[channelX, start[1]], [channelX, end[1]]];
          const points = [start, ...candidate, end];
          if (routeHonorsEndpointSides(points, fromSide, toSide) && routeClearsComponents(conn, points)) return candidate;
        }
      }

      const fromHorizontalSide = start[0] === from.x || start[0] === from.x + from.width;
      const toHorizontalSide = end[0] === to.x || end[0] === to.x + to.width;
      if (fromHorizontalSide && toHorizontalSide && deltaY < minimumStub * 2) {
        const outsideChannels = [
          Math.max(start[1], end[1]) + minimumStub * 2,
          Math.min(start[1], end[1]) - minimumStub * 2,
        ];
        for (const channelY of outsideChannels) {
          const candidate = [[start[0], channelY], [end[0], channelY]];
          const points = [start, ...candidate, end];
          if (routeHonorsEndpointSides(points, fromSide, toSide) && routeClearsComponents(conn, points)) return candidate;
        }
      }

      const midX = (start[0] + end[0]) / 2;
      const horizontalFirst = [[midX, start[1]], [midX, end[1]]];
      const midY = (start[1] + end[1]) / 2;
      const verticalFirst = [[start[0], midY], [end[0], midY]];
      const candidates = [horizontalFirst, verticalFirst];
      const sideSafe = candidates.filter((candidate) => (
        routeHonorsEndpointSides([start, ...candidate, end], fromSide, toSide)
      ));
      const sideAware = sideAwareBridgeCandidates(start, end, fromSide, toSide);
      const nearParallelPorts = (
        ((fromSide === 'top' || fromSide === 'bottom')
          && (toSide === 'top' || toSide === 'bottom')
          && deltaX < minimumStub * 2)
        || ((fromSide === 'left' || fromSide === 'right')
          && (toSide === 'left' || toSide === 'right')
          && deltaY < minimumStub * 2)
      );
      const ordered = [
        ...(nearParallelPorts ? sideAware : sideSafe),
        ...(nearParallelPorts ? sideSafe : sideAware),
        ...candidates.filter((candidate) => !sideSafe.includes(candidate)),
      ];
      for (const candidate of ordered) {
        const points = [start, ...candidate, end];
        if (routeClearsEndpointComponents(points, from, to) && routeClearsComponents(conn, points)) return candidate;
      }

      // Both bounded doglegs are blocked. Keep the best endpoint-safe route
      // when one exists so the universal Clean Flow gate reports the actual
      // obstacle; otherwise preserve the historical deterministic fallback
      // and let the endpoint-direction gate explain the side mismatch.
      return sideSafe[0] || sideAware[0] || horizontalFirst;
    }
  }
}

const pathCache = new Map();
const automaticPorts = automaticPortSpread(arch.connections, components);
function connectionSides(conn) {
  const from = components.get(conn.from);
  const to = components.get(conn.to);
  return {
    fromSide: chosenSide(conn.fromSide, defaultFromSide(from, to)),
    toSide: chosenSide(conn.toSide, defaultToSide(from, to)),
  };
}

function connectionEndpointSide(conn, endpoint) {
  const field = endpoint === 'source' ? 'fromSide' : 'toSide';
  if (conn[field] && conn[field] !== 'auto') return conn[field];
  return connectionSides(conn)[field];
}

function pathFor(conn) {
  if (pathCache.has(conn)) return pathCache.get(conn);
  const from = components.get(conn.from);
  const to = components.get(conn.to);
  const ports = automaticPorts.get(conn);
  const { fromSide, toSide } = connectionSides(conn);
  const baseStart = ports?.from || anchor(from, fromSide);
  const baseEnd = ports?.to || anchor(to, toSide);
  const { start, end } = alignFacingPorts(
    conn,
    from,
    to,
    baseStart,
    baseEnd,
    fromSide,
    toSide,
    ports,
  );
  const points = [start, ...routeVia(conn, from, to, start, end, fromSide, toSide), end];
  const routed = { d: roundedPath(points, 8), points };
  pathCache.set(conn, routed);
  return routed;
}

// ---- Rendering ---------------------------------------------------------------
function renderBoundaryFrame(b, index) {
  const cls = b.kind === 'security-group' ? 'c-security-group' : 'c-region';
  const rx = b.kind === 'security-group' ? 8 : 12;
  return `        <rect data-graph-role="structural-frame" data-composition-frame-kind="${esc(b.kind || 'boundary')}" data-composition-frame-id="${index}" data-composition-frame-label="${esc(b.label)}" x="${b.x}" y="${b.y}" width="${b.width}" height="${b.height}" rx="${rx}" class="${cls}" stroke-width="1"/>`;
}

function renderBoundaryLabel(b, index) {
  const labelCls = b.kind === 'security-group' ? 't-security' : 't-cloud';
  return `        <g data-graph-role="structural-frame-label" data-composition-frame-id="${index}" data-composition-frame-kind="${esc(b.kind || 'boundary')}" data-composition-frame-label="${esc(b.label)}">
          <rect data-graph-role="structural-frame-label-mask" x="${b.title.x}" y="${b.title.y}" width="${b.title.width}" height="${b.title.height}" rx="3" class="c-mask"/>
          <text data-boundary-label="" x="${b.title.x + 4}" y="${b.title.y + b.title.baselineOffset}" class="${labelCls}" font-size="${b.title.fontSize}" font-weight="600">${esc(b.label)}</text>
        </g>`;
}

function renderConnectionPath(conn, index) {
  const [cls, marker] = arrowClassMap[conn.variant || 'default'] || arrowClassMap.default;
  const routed = pathFor(conn);
  const strokeWidth = conn.width || (conn.variant === 'emphasis' ? 1.8 : 1.5);
  return `        <path ${focusEdgeAttrs(conn.from, conn.to, conn.label, index, conn.id)} data-composition-points="${routePointsValue(routed.points)}" d="${routed.d}" class="${cls}"${animateAttr(arch.meta, 'edge', index)} stroke-width="${strokeWidth}" marker-end="url(#${marker})"/>`;
}

function renderConnectionLabel(conn, index) {
  if (!conn.label) return '';
  const [lx, ly] = labelPoint(conn, pathFor(conn).points);
  const w = Math.max(30, textUnits(conn.label) * 4.8 + 10);
  return `        <g data-detail="context" ${focusEdgeAttrs(conn.from, conn.to, conn.label, index, conn.id)}>
          <rect x="${lx - w / 2}" y="${ly - 10}" width="${w}" height="14" rx="3" class="c-mask"/>
          <text x="${lx}" y="${ly}" class="${variantAccent(conn.variant)}" font-size="8" text-anchor="middle">${esc(conn.label)}</text>
        </g>`;
}

function renderComponent(c) {
  const fill = componentFill[c.type] || 'c-external';
  const accent = componentText[c.type] || 't-muted';
  const cx = c.cx;
  const hasSub = c.sublabel != null && c.sublabel !== '';
  const labelY = hasSub ? c.y + c.height / 2 - 2 : c.y + c.height / 2 + 4;
  const sub = hasSub
    ? `\n        <text data-detail="context" x="${cx}" y="${c.y + c.height / 2 + 14}" class="t-muted" font-size="${fittedNodeFontSize(c.sublabel, c.width, componentTextFit.sublabelPreferred, componentTextFit.sublabelMinimum)}" text-anchor="middle">${esc(c.sublabel)}</text>`
    : '';
  const tag = c.tag
    ? `\n        <text data-detail="fine" x="${cx}" y="${c.y + c.height - 8}" class="${accent}" font-size="${fittedNodeFontSize(c.tag, c.width, componentTextFit.tagPreferred, componentTextFit.tagMinimum)}" text-anchor="middle">${esc(c.tag)}</text>`
    : '';
  const brand = renderBrandMark(c, { x: c.x + c.width - 22, y: c.y + 6 });
  const labelFontSize = fittedNodeFontSize(c.label, brandLabelFitWidth(c, c.width), 11, 8);
  const passport = { kind: c.type, sublabel: c.sublabel, tag: c.tag, context: componentContext(c), ...brandMetadataFor(c) };
  return `        <g ${focusNodeAttrs(c.id, c.label, passport, arch.meta.locale)}>
          ${focusNodeTitle(c.label, passport)}
          <rect x="${c.x}" y="${c.y}" width="${c.width}" height="${c.height}" rx="6" class="c-mask"/>
          <rect x="${c.x}" y="${c.y}" width="${c.width}" height="${c.height}" rx="6" class="${fill}"${animateAttr(arch.meta, 'node', componentSteps.get(c.id))} stroke-width="1.5"/>
          ${renderSemanticSigil(c.type, { x: c.x + 6, y: c.y + 6 })}${brand ? `\n          ${brand}` : ''}
          <text data-node-label=""${hasSub ? ' data-detail-anchor=""' : ''} x="${cx}" y="${labelY}" class="t-primary" font-size="${labelFontSize}" font-weight="600" text-anchor="middle">${esc(c.label)}</text>${sub}${tag}
        </g>`;
}

function renderLegend() {
  const entries = architectureLegendEntries;
  const relationshipObstacles = relationshipLegendObstacles(arch.connections, {
    pointsFor: (connection) => pathFor(connection).points,
    labelRectFor: (connection) => {
      if (!connection.label) return null;
      const [x, y] = labelPoint(connection, pathFor(connection).points);
      const width = Math.max(30, textUnits(connection.label) * 4.8 + 10);
      return { x: x - width / 2, y: y - 10, width, height: 14 };
    },
  });
  const contentBottom = Math.max(
    0,
    ...[...components.values()].map((component) => component.y + component.height),
    ...boundaries.map((boundary) => boundary.y + boundary.height),
  );
  return renderResolvedLegend({
    entries,
    locale: arch.meta.locale,
    layout: {
      x: layout.margin,
      baselineY: legendY(),
      width: viewBox[0] - layout.margin * 2,
      minTitleY: contentBottom + 8,
      obstacles: relationshipObstacles,
      unfit: arch.meta?.legend === undefined ? 'hide' : 'error',
      diagramType: 'architecture',
    },
    renderSwatch: (entry) => `<rect x="${entry.x}" y="${entry.baseline - 9}" width="16" height="10" rx="2.5" class="${componentFill[entry.kind] || 'c-external'}" stroke-width="1"/>`,
  });
}

function renderSvg() {
  return `      <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(arch.meta)}>
${svgAccessibleText(arch.meta, 'architecture')}
${renderDefinitions()}

        <!-- Background Grid -->
        <rect width="100%" height="100%" fill="url(#grid)" />

        <!-- Boundaries (behind everything) -->
${boundaries.map(renderBoundaryFrame).join('\n\n')}

        <!-- Connection paths (before components for correct z-order) -->
${asArray(arch.connections).map(renderConnectionPath).join('\n')}

        <!-- Components -->
${[...components.values()].map(renderComponent).join('\n\n')}

        <!-- Connection labels -->
${asArray(arch.connections).map(renderConnectionLabel).join('\n')}

        <!-- Boundary labels (foreground masks keep routes out of titles) -->
${boundaries.map(renderBoundaryLabel).join('\n\n')}

        <!-- Legend -->
${renderLegend()}
      </svg>`;
}

validateArchitecture();
if (layoutJsonMode) {
  console.log(JSON.stringify(buildLayoutReport(), null, 2));
  process.exit(0);
}
writeDiagram({
  outPath,
  template,
  diagramType: 'architecture',
  meta: arch.meta,
  svg: renderSvg(),
  cards: arch.cards,
  sourceEvidence,
});
```

## renderers/dataflow

```

```

## renderers/dataflow/README.md

# Data Flow Renderer

Render `diagram_type: "dataflow"` JSON files into the standard Archify HTML
template.

```bash
node archify/renderers/dataflow/render-dataflow.mjs input.dataflow.json output.html
```

The renderer validates input against `archify/schemas/dataflow.schema.json`
with the bundled standalone validator. No dependency installation is required.

If `output.html` is omitted, the renderer uses `meta.output` from the JSON file
or falls back to `dataflow.html` in the current working directory.

## Input

Data-flow JSON files must set:

```json
{
  "schema_version": 1,
  "diagram_type": "dataflow",
  "meta": {
    "title": "Product Analytics Data Flow",
    "viewBox": [940, 720]
  },
  "stages": [],
  "nodes": [],
  "flows": [],
  "cards": []
}
```

A complete worked example lives at
`archify/examples/product-analytics.dataflow.json`.

The schema lives at:

```text
archify/schemas/dataflow.schema.json
```

## Legend

The default visual legend derives kinds from `flows[].variant` (omitting
`variant` means `default`) and adds `database` only when a database node exists.
Supported `meta.legend.entries` keys, in stable order, are `emphasis`,
`security`, `dashed`, `database`, and `default`. Flow variants remain
visual-only because Archify has no compiled edge-kind facts in this slice. A
present `database` entry is different: it comes from exact
`nodes[].type: "database"` facts, so it publishes the normal Semantic Legend
count, accessible name, and keyboard interaction. Forcing `database` visible
without a database node keeps it visual-only.

## Layout budget

| Constant | Value |
|----------|-------|
| viewBox | default `[940, 720]`; schema minimum `[360, 360]` |
| Stages (2–5) | centers at x = 100 + stage×215; stage band 168 wide, header at y 46 |
| Row tops (`row` 0–4) | y = 128, 242, 356, 470, 584 (plus `yOffset`) |
| Default node | 112×58 |
| Node area | x within `[24, width − 24]`; y within `[104, height − 74]` |
| Node spacing | ≥10px between any two nodes (checked across stages and rows) |
| Flow length | ≥34px between endpoints |
| Legend row | y = height − 36 |

Route presets for flows: `straight`, `vertical-channel`, `bottom-channel`,
`top-channel`, explicit `via` points, or the default `auto` (midpoint elbow).

## Design Rules

- Use stages for data lifecycle boundaries: source, ingest, process, store,
  consume.
- Place nodes by stage index and row index; do not hand-place raw SVG for the
  common case.
- Use flow labels to name the data asset, not the transport primitive:
  `clickstream`, `identity map`, `normalized facts`, `feature vectors`.
- Use `classification` for short sensitivity or governance context:
  `PII touch`, `non-PII`, `approved only`, `batch`, `read-only`.
- Use `security` for PII, policy, consent, access-control, or restricted joins.
- Use `emphasis` for the primary data path and `dashed` for async or batch
  derivations.
- Keep labels short enough to fit in narrow previews.

Schema violations exit non-zero with path-prefixed messages annotated with the
element's id or label. The renderer additionally fails when it can detect
layout problems, including missing stages, duplicate node IDs, nodes outside
the readable diagram area, node overlap, labels colliding with nodes or other
labels, labels wider than their node, unknown flow endpoints, missing flow
labels, unreadably short flows, flows crossing unrelated nodes (2px Clean Flow
clearance), or stages that exceed the viewBox. Stage frames remain intentional
pass-through containers. Text width
is estimated CJK-aware: fullwidth glyphs count as two units.

Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper
X crossings then fail with `composition/proper-crossing`; default `standard`
keeps them as artifact-receipt warnings. Collinear stage corridors are outside
the proper-X rule, but a separate gate warns in `standard` and fails in
`showcase` when unrelated flows overlap for at least 8px. Shared semantic
endpoints, point touches, and shorter overlaps remain valid. Showcase also
rejects any route segment below 8px and any interior turn segment below 16px;
ordinary 8–15px endpoint stubs remain valid.

## renderers/dataflow/render-dataflow.mjs

```js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs';
import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs';
import { throwDiagnosticProblems } from '../shared/diagnostics.mjs';
import { resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs';
import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs';
import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs';
import { translateMessage as i18nText } from '../shared/i18n.mjs';
import {
  asArray,
  isFinitePoint,
  rectsOverlap,
  cleanEndpointSideProblems,
  cleanFlowProblems,
  cleanCrossingProblems,
  cleanAmbiguousCorridorProblems,
  cleanBorderRunProblems,
  cleanRouteRhythmProblems,
  cleanLabelRouteClearanceProblems,
  suggestLabelObstacleFix,
  suggestLabelPairFix,
  anchor,
  automaticPortSpread,
  defaultFromSide,
  defaultToSide,
  chosenSide,
  polylinePath,
  routePointsValue,
  labelPoint,
  componentFill,
  componentText,
  arrowClassMap,
  variantAccent
} from '../shared/geometry.mjs';

const nodeTextFit = {
  sublabelPreferred: 7,
  sublabelMinimum: 6,
  tagPreferred: 7,
  tagMinimum: 6,
};

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const { diagram: dataflow, template, outPath } = await loadDiagramWithBrandMarks({
  rendererDir: __dirname,
  diagramType: 'dataflow',
  defaultExample: 'product-analytics.dataflow.json'
});

const viewBox = dataflow.meta?.viewBox || [940, 720];
const layout = {
  stageY: 46,
  stageH: 36,
  stageBottomPad: 74,
  leftX: 100,
  colGap: 215,
  stageW: 168,
  nodeW: 112,
  nodeH: 58,
  rowYs: [128, 242, 356, 470, 584],
  labelH: 16
};

function flowLabelSize(flow) {
  const longestLine = Math.max(textUnits(flow.label), textUnits(flow.classification || ''));
  return {
    width: Math.round(Math.max(34, longestLine * 4.9 + 12) * 10) / 10,
    height: flow.classification ? 27 : layout.labelH,
  };
}

function stageX(index) {
  return layout.leftX + index * layout.colGap;
}

function stageFrame(stage, index) {
  return {
    id: index,
    label: stage.label,
    kind: 'stage',
    x: stageX(index) - layout.stageW / 2,
    y: layout.stageY,
    width: layout.stageW,
    height: viewBox[1] - layout.stageY - layout.stageBottomPad,
    radius: 10,
  };
}

const compositionFrames = asArray(dataflow.stages).map(stageFrame);

function measureNode(node) {
  const width = node.width || layout.nodeW;
  const height = node.height || layout.nodeH;
  const cx = stageX(node.stage);
  const y = layout.rowYs[node.row] + (node.yOffset || 0);
  return {
    ...node,
    width,
    height,
    cx,
    cy: y + height / 2,
    x: cx - width / 2,
    y
  };
}

const nodes = new Map(asArray(dataflow.nodes).map((node) => [node.id, measureNode(node)]));
const nodeSteps = new Map();
for (const [index, flow] of asArray(dataflow.flows).entries()) {
  if (!nodeSteps.has(flow.from)) nodeSteps.set(flow.from, index);
  if (!nodeSteps.has(flow.to)) nodeSteps.set(flow.to, index + 1);
}
for (const [index, node] of asArray(dataflow.nodes).entries()) {
  if (!nodeSteps.has(node.id)) nodeSteps.set(node.id, index);
}

function validateDataflow() {
  const problems = [];
  if (nodes.size !== asArray(dataflow.nodes).length) problems.push('Node ids must be unique.');

  const stageCount = asArray(dataflow.stages).length;
  for (const node of nodes.values()) {
    if (typeof node.stage !== 'number' || node.stage < 0 || node.stage >= stageCount) {
      problems.push(`Node "${node.id}" uses invalid stage ${node.stage} — valid stages are 0..${stageCount - 1}.`);
    }
    if (typeof node.row !== 'number' || node.row < 0 || node.row >= layout.rowYs.length) {
      problems.push(`Node "${node.id}" uses invalid row ${node.row} — valid rows are 0..${layout.rowYs.length - 1}.`);
    }
    if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) {
      problems.push(`Node "${node.id}" produced non-finite coordinates — check stage, row, width, height, and yOffset are numbers.`);
      continue;
    }
    if (node.x < 24 || node.x + node.width > viewBox[0] - 24) {
      problems.push(`Node "${node.id}" exceeds the horizontal bounds of the viewBox — reduce node.width or increase meta.viewBox[0].`);
    }
    if (node.y < layout.stageY + layout.stageH + 22 || node.y + node.height > viewBox[1] - layout.stageBottomPad) {
      problems.push(`Node "${node.id}" exceeds the readable diagram area — keep y between ${layout.stageY + layout.stageH + 22} and ${viewBox[1] - layout.stageBottomPad} (adjust row/yOffset or increase meta.viewBox[1]).`);
    }
    const estLabelW = textUnits(node.label) * 6.2;
    if (estLabelW > node.width + 6) {
      problems.push(`Label "${node.label}" (~${Math.round(estLabelW)}px) is wider than node "${node.id}" (${node.width}px) — shorten the label or increase node.width.`);
    }
    const brandRailProblem = brandTopRailProblem(node, node.width, 8);
    if (brandRailProblem) problems.push(brandRailProblem);
    // sublabel and tag render as single unwrapped <text> elements; shrink-to-fit
    // handles the ordinary case, this rejects what it cannot rescue.
    const availableTextW = availableNodeTextWidth(node.width);
    for (const [field, value, minimum] of [
      ['Sublabel', node.sublabel, nodeTextFit.sublabelMinimum],
      ['Tag', node.tag, nodeTextFit.tagMinimum],
    ]) {
      if (!value) continue;
      const minimumW = minimumNodeTextWidth(value, minimum);
      if (minimumW > availableTextW) {
        problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but node "${node.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or increase node.width.`);
      }
    }
  }

  const nodeList = asArray(dataflow.nodes);
  for (let i = 0; i < nodeList.length; i += 1) {
    for (let j = i + 1; j < nodeList.length; j += 1) {
      const a = nodes.get(nodeList[i].id);
      const b = nodes.get(nodeList[j].id);
      if (rectsOverlap(a, b, 10)) {
        problems.push(`Nodes "${a.id}" and "${b.id}" are less than 10px apart — move one to another stage/row or adjust yOffset.`);
      }
    }
  }

  for (const flow of asArray(dataflow.flows)) {
    if (!nodes.has(flow.from)) problems.push(`Flow "${flow.label || flow.from}" references unknown source "${flow.from}".`);
    if (!nodes.has(flow.to)) problems.push(`Flow "${flow.label || flow.to}" references unknown target "${flow.to}".`);
    if (!flow.label) problems.push(`Flow "${flow.from}" -> "${flow.to}" must include a short data label.`);
    if (nodes.has(flow.from) && nodes.has(flow.to)) {
      const routed = pathFor(flow);
      const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]];
      const distance = Math.hypot(end[0] - start[0], end[1] - start[1]);
      if (distance < 34) problems.push(`Flow "${flow.label}" is too short (${Math.round(distance)}px; minimum 34px) — route it through a channel or spread its nodes.`);
      if (Array.isArray(flow.via)) {
        for (let segmentIndex = 0; segmentIndex < routed.points.length - 1; segmentIndex += 1) {
          const segmentStart = routed.points[segmentIndex];
          const segmentEnd = routed.points[segmentIndex + 1];
          const isDiagonal = Math.abs(segmentStart[0] - segmentEnd[0]) > 0.01
            && Math.abs(segmentStart[1] - segmentEnd[1]) > 0.01;
          if (!isDiagonal) continue;
          const viaIndex = Math.min(segmentIndex, flow.via.length - 1);
          problems.push(`Flow "${flow.label}" has a diagonal segment from (${segmentStart.join(', ')}) to (${segmentEnd.join(', ')}) — align via[${viaIndex}] with its adjacent point by sharing the same x or y coordinate.`);
        }
      }
    }
  }

  problems.push(...cleanEndpointSideProblems({
    relations: dataflow.flows,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'dataflow',
    relationCollection: 'flows',
    fromSideFor: (flow) => flowSides(flow).fromSide,
    toSideFor: (flow) => flowSides(flow).toSide,
    routeHint: 'keep automatic routing, or choose fromSide/toSide and via points whose first and final segments cross node borders perpendicularly',
  }));
  problems.push(...cleanFlowProblems({
    relations: dataflow.flows,
    obstacles: nodes.values(),
    pathFor,
    diagramType: 'dataflow',
    relationCollection: 'flows',
    obstacleKind: 'node',
    routeHint: 'adjust fromSide/toSide, set route/via or channelX/channelY, or move the node to another stage/row'
  }));
  problems.push(...cleanCrossingProblems({
    relations: dataflow.flows,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'dataflow',
    relationCollection: 'flows',
    profile: dataflow.meta?.quality_profile,
    routeHint: 'adjust route/via or channelX/channelY so the flows use separate stage corridors'
  }));
  problems.push(...cleanAmbiguousCorridorProblems({
    relations: dataflow.flows,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'dataflow',
    relationCollection: 'flows',
    profile: dataflow.meta?.quality_profile,
    routeHint: 'adjust route/via or channelX/channelY so unrelated flows do not visually merge'
  }));
  problems.push(...cleanBorderRunProblems({
    relations: dataflow.flows,
    endpointIds: new Set(nodes.keys()),
    frames: compositionFrames,
    pathFor,
    diagramType: 'dataflow',
    relationCollection: 'flows',
    profile: dataflow.meta?.quality_profile,
    routeHint: 'adjust route/via or channelX/channelY so the flow crosses the stage perpendicularly instead of following its border'
  }));
  problems.push(...cleanRouteRhythmProblems({
    relations: dataflow.flows,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'dataflow',
    relationCollection: 'flows',
    profile: dataflow.meta?.quality_profile,
    routeHint: 'adjust route/via or channelX/channelY so each turn uses a clear inter-stage corridor'
  }));

  const labelRects = [];
  for (const [flowIndex, flow] of asArray(dataflow.flows).entries()) {
    if (!flow.label || !nodes.has(flow.from) || !nodes.has(flow.to)) continue;
    const [lx, ly] = labelPoint(flow, pathFor(flow).points);
    const { width, height } = flowLabelSize(flow);
    labelRects.push({ relation: flow, relationIndex: flowIndex, label: flow.label, x: lx - width / 2, y: ly - 11, width, height, lx, ly });
  }
  for (const rect of labelRects) {
    for (const node of nodes.values()) {
      if (rectsOverlap(rect, node, -2)) {
        problems.push(`Label "${rect.label}" overlaps node "${node.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, node, 'node')}`);
      }
    }
  }
  for (let i = 0; i < labelRects.length; i += 1) {
    for (let j = i + 1; j < labelRects.length; j += 1) {
      if (rectsOverlap(labelRects[i], labelRects[j], -2)) {
        problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}`);
      }
    }
  }
  problems.push(...cleanLabelRouteClearanceProblems({
    relations: dataflow.flows,
    labels: labelRects,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'dataflow',
    relationCollection: 'flows',
    profile: dataflow.meta?.quality_profile,
    routeHint: 'adjust labelAt, labelDx, labelDy, or labelSegment; otherwise adjust the other flow route/via/channelX/channelY'
  }));

  const lastStageX = stageX(asArray(dataflow.stages).length - 1);
  if (lastStageX + layout.stageW / 2 > viewBox[0] - 24) {
    problems.push(`Stages exceed viewBox width — set meta.viewBox[0] to at least ${Math.ceil(lastStageX + layout.stageW / 2 + 24)}.`);
  }

  if (problems.length) {
    throwDiagnosticProblems('Data-flow layout validation failed', problems, {
      subject: { diagramType: 'dataflow' },
    });
  }
}

function routeVia(flow, from, to, start, end) {
  if (flow.via) return flow.via;
  switch (flow.route || 'auto') {
    case 'straight':
      return [];
    case 'vertical-channel': {
      const x = flow.channelX ?? start[0] + (end[0] > start[0] ? 44 : -44);
      return [[x, start[1]], [x, end[1]]];
    }
    case 'bottom-channel': {
      const y = flow.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 26;
      return [[start[0], y], [end[0], y]];
    }
    case 'top-channel': {
      const y = flow.channelY ?? Math.min(from.y, to.y) - 24;
      return [[start[0], y], [end[0], y]];
    }
    case 'auto':
    default: {
      if (Math.abs(start[1] - end[1]) < 4) return [];
      const midX = start[0] + (end[0] - start[0]) / 2;
      return [[midX, start[1]], [midX, end[1]]];
    }
  }
}

const pathCache = new Map();

function flowSides(flow) {
  const from = nodes.get(flow.from);
  const to = nodes.get(flow.to);
  return {
    fromSide: chosenSide(flow.fromSide, defaultFromSide(from, to)),
    toSide: chosenSide(flow.toSide, defaultToSide(from, to)),
  };
}

const automaticPorts = automaticPortSpread(dataflow.flows, nodes, {
  sideFor: (flow, endpoint) => flowSides(flow)[endpoint === 'source' ? 'fromSide' : 'toSide'],
});

function pathFor(flow) {
  if (pathCache.has(flow)) return pathCache.get(flow);
  const from = nodes.get(flow.from);
  const to = nodes.get(flow.to);
  const ports = automaticPorts.get(flow);
  const { fromSide, toSide } = flowSides(flow);
  const start = ports?.from || anchor(from, fromSide);
  const end = ports?.to || anchor(to, toSide);
  // Drop consecutive duplicate points so a purely vertical (or horizontal)
  // auto-route never emits a zero-length final segment — SVG derives
  // marker-end orientation from the last segment, and a degenerate segment
  // leaves the arrowhead angle undefined (see #169).
  const rawPoints = [start, ...routeVia(flow, from, to, start, end), end];
  const points = [];
  for (const p of rawPoints) {
    const prev = points.at(-1);
    if (!prev || Math.abs(p[0] - prev[0]) > 0.0001 || Math.abs(p[1] - prev[1]) > 0.0001) {
      points.push(p);
    }
  }
  // Guard against an all-degenerate route (e.g. start === end): keep both
  // endpoints so the path is still well-formed even if the marker is hidden.
  if (points.length < 2) points.push(end);
  const routed = { d: polylinePath(points), points };
  pathCache.set(flow, routed);
  return routed;
}

function renderStage(stage, index) {
  const frame = compositionFrames[index];
  const cx = stageX(index);
  return `        <rect data-graph-role="structural-frame" data-composition-frame-kind="stage" data-composition-frame-id="${index}" x="${frame.x}" y="${frame.y}" width="${frame.width}" height="${frame.height}" rx="${frame.radius}" class="c-lane" stroke-width="1"/>
        <text x="${cx}" y="${layout.stageY + 22}" class="t-dim" font-size="9" font-weight="600" text-anchor="middle">${String(index + 1).padStart(2, '0')} / ${esc(stage.label)}</text>`;
}

function renderNode(node) {
  const fill = componentFill[node.type] || 'c-external';
  const accent = componentText[node.type] || 't-muted';
  const hasSub = node.sublabel != null && node.sublabel !== '';
  const sub = hasSub
    ? `\n          <text data-detail="context" x="${node.cx}" y="${node.y + 37}" class="t-muted" font-size="${fittedNodeFontSize(node.sublabel, node.width, nodeTextFit.sublabelPreferred, nodeTextFit.sublabelMinimum)}" text-anchor="middle">${esc(node.sublabel)}</text>`
    : '';
  const tag = node.tag
    ? `\n        <text data-detail="fine" x="${node.cx}" y="${node.y + node.height - 11}" class="${accent}" font-size="${fittedNodeFontSize(node.tag, node.width, nodeTextFit.tagPreferred, nodeTextFit.tagMinimum)}" text-anchor="middle">${esc(node.tag)}</text>`
    : '';
  const stage = asArray(dataflow.stages)[node.stage];
  const context = stage
    ? `${String(node.stage + 1).padStart(2, '0')} / ${stage.label}`
    : i18nText(dataflow.meta.locale, 'node.context.dataflow');
  const brand = renderBrandMark(node, { x: node.x + node.width - 22, y: node.y + 6 });
  const labelFontSize = fittedNodeFontSize(node.label, brandLabelFitWidth(node, node.width), 10, 8);
  const passport = { kind: node.type, sublabel: node.sublabel, tag: node.tag, context, ...brandMetadataFor(node) };
  return `        <g ${focusNodeAttrs(node.id, node.label, passport, dataflow.meta.locale)}>
          ${focusNodeTitle(node.label, passport)}
          <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="6" class="c-mask"/>
          <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="6" class="${fill}"${animateAttr(dataflow.meta, 'node', nodeSteps.get(node.id))} stroke-width="1.5"/>
          ${renderSemanticSigil(node.type, { x: node.x + 6, y: node.y + 6 })}${brand ? `\n          ${brand}` : ''}
          <text data-node-label=""${hasSub ? ' data-detail-anchor=""' : ''} x="${node.cx}" y="${node.y + 21}" class="t-primary" font-size="${labelFontSize}" font-weight="600" text-anchor="middle">${esc(node.label)}</text>${sub}${tag}
        </g>`;
}

function renderFlowPath(flow, index) {
  const [cls, marker] = arrowClassMap[flow.variant || 'default'] || arrowClassMap.default;
  const routed = pathFor(flow);
  const strokeWidth = flow.width || (flow.variant === 'emphasis' ? 1.8 : 1.4);
  return `        <path ${focusEdgeAttrs(flow.from, flow.to, flow.label, index, flow.id)} data-composition-points="${routePointsValue(routed.points)}" d="${routed.d}" class="${cls}"${animateAttr(dataflow.meta, 'edge', index)} stroke-width="${strokeWidth}" marker-end="url(#${marker})"/>`;
}

function renderFlowLabel(flow, index) {
  const routed = pathFor(flow);
  const [lx, ly] = labelPoint(flow, routed.points);
  const { width: labelW, height: labelH } = flowLabelSize(flow);
  const classification = flow.classification
    ? `\n        <text data-detail="fine" x="${lx}" y="${ly + 11}" class="t-dim" font-size="7" text-anchor="middle">${esc(flow.classification)}</text>`
    : '';
  return `        <g data-detail="context" ${focusEdgeAttrs(flow.from, flow.to, flow.label, index, flow.id)}>
          <rect x="${lx - labelW / 2}" y="${ly - 11}" width="${labelW}" height="${labelH}" rx="4" class="c-mask"/>
          <text x="${lx}" y="${ly}" class="${variantAccent(flow.variant)}" font-size="8" text-anchor="middle">${esc(flow.label)}</text>${classification}
        </g>`;
}

const LEGEND_CATALOG = [
  { kind: 'emphasis', className: 'a-emphasis', marker: 'arrowhead-emphasis', strokeWidth: 1.8, swatchWidth: 34, swatchGap: 9, interactive: false },
  { kind: 'security', className: 'a-security', marker: 'arrowhead-security', swatchWidth: 34, swatchGap: 9, interactive: false },
  { kind: 'dashed', className: 'a-dashed', marker: 'arrowhead-dashed', swatchWidth: 34, swatchGap: 9, interactive: false },
  { kind: 'database' },
  { kind: 'default', className: 'a-default', marker: 'arrowhead', swatchWidth: 34, swatchGap: 9, interactive: false },
].map((entry) => ({
  ...entry,
  label: i18nText(dataflow.meta.locale, `legend.dataflow.${entry.kind}`),
}));

function renderLegend() {
  const presentKinds = new Set(asArray(dataflow.flows).map((flow) => flow.variant || 'default'));
  if ([...nodes.values()].some((node) => node.type === 'database')) presentKinds.add('database');
  const entries = resolveLegend(dataflow.meta?.legend, LEGEND_CATALOG, presentKinds);
  return renderResolvedLegend({
    entries,
    locale: dataflow.meta.locale,
    layout: {
      x: 40,
      baselineY: viewBox[1] - 36,
      width: viewBox[0] - 80,
      minTitleY: viewBox[1] - 66,
      unfit: dataflow.meta?.legend === undefined ? 'hide' : 'error',
      diagramType: 'dataflow',
    },
    renderSwatch: (entry) => entry.kind === 'database'
      ? `<rect x="${entry.x}" y="${entry.baseline - 8}" width="14" height="9" rx="2" class="c-database" stroke-width="1"/>`
      : `<path d="M ${entry.x} ${entry.baseline - 3} L ${entry.x + 34} ${entry.baseline - 3}" class="${entry.className}" stroke-width="${entry.strokeWidth || 1.4}" marker-end="url(#${entry.marker})"/>`,
  });
}

function renderSvg() {
  return `      <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(dataflow.meta)}>
${svgAccessibleText(dataflow.meta, 'dataflow')}
${renderDefinitions()}

        <!-- Background Grid -->
        <rect width="100%" height="100%" fill="url(#grid)" />

        <!-- Data Stages -->
${dataflow.stages.map(renderStage).join('\n\n')}

        <!-- Flow paths -->
${asArray(dataflow.flows).map(renderFlowPath).join('\n')}

        <!-- Nodes -->
${[...nodes.values()].map(renderNode).join('\n\n')}

        <!-- Flow labels -->
${asArray(dataflow.flows).map(renderFlowLabel).join('\n')}

        <!-- Legend -->
${renderLegend()}
      </svg>`;
}

validateDataflow();
writeDiagram({
  outPath,
  template,
  diagramType: 'dataflow',
  meta: dataflow.meta,
  svg: renderSvg(),
  cards: dataflow.cards,
});
```

## renderers/lifecycle

```

```

## renderers/lifecycle/README.md

# Lifecycle Renderer

Render `diagram_type: "lifecycle"` JSON files into the standard Archify HTML
template.

```bash
node archify/renderers/lifecycle/render-lifecycle.mjs input.lifecycle.json output.html
```

The renderer validates input against `archify/schemas/lifecycle.schema.json`
with the bundled standalone validator. No dependency installation is required.

If `output.html` is omitted, the renderer uses `meta.output` from the JSON file
or falls back to `lifecycle.html` in the current working directory.

## Input

Lifecycle JSON files must set:

```json
{
  "schema_version": 1,
  "diagram_type": "lifecycle",
  "meta": {
    "title": "Agent Run Lifecycle",
    "viewBox": [980, 660]
  },
  "lanes": [],
  "states": [],
  "transitions": [],
  "cards": []
}
```

Lane ids are semantic and reserved: a lane with id `main` is required and maps
to the top phase band; `terminal` maps to the bottom outcome band; every other
lane id (up to 4 lanes total) shares the single middle event band. The three
band headers render from your lane labels — the middle band joins the labels of
all event lanes with ` + `. A complete worked example lives at
`archify/examples/agent-run.lifecycle.json`.

The schema lives at:

```text
archify/schemas/lifecycle.schema.json
```

## Legend

The default legend derives kinds from `states[].type`. Supported
`meta.legend.entries` keys, in stable order, are `start`, `active`, `waiting`,
`decision`, `success`, `failure`, `neutral`, and `external`. Labels and
visibility may be overridden through the shared legend contract; only kinds
backed by rendered states receive Semantic Legend controls.

## Layout budget

| Band | Lane id | Top y | Column centers | Default state |
|------|---------|-------|----------------|---------------|
| Phase | `main` (required) | 126 | `col` 0–4 → x = 94, 248, 402, 556, 710 | 118×62 |
| Event | any other id | 278 | `col` 0–2 → x = 402, 556, 710 | 126×58 |
| Outcome | `terminal` | 450 | `col` 0–2 → x = 402, 556, 710 | 118×58 |

Event and terminal columns are intentionally offset from the main rail:
event/terminal `col: N` uses the same x coordinate as main `col: N + 2`.
For example, lower-band columns 0, 1, and 2 align beneath main columns 2, 3,
and 4 respectively.

| Constant | Value |
|----------|-------|
| viewBox | default `[980, 660]`; schema minimum `[420, 566]` |
| State area | x within `[32, width − 32]`; state bottom at or above `height − 122` |
| State spacing | ≥10px between any two states — checked across lanes, because all event lanes share one band; separate same-band states with `col` or `yOffset` |
| Transition length | ≥32px between endpoints |
| Legend row | final baseline y = height − 36; extra measured rows wrap upward |

The primary lifecycle rail runs along the phase band and extends to the
furthest occupied phase column. Route presets for transitions: `straight`,
`drop` (bend at `channelY`, defaulting to the vertical midpoint),
`bottom-channel`, `top-channel`, `right-channel`, `left-channel`, explicit
`via` points, or the default `auto`. Multi-segment transitions get rounded
corners; tune them with `cornerRadius` (default 10, `0` for sharp bends).

## Design Rules

- Treat lifecycle diagrams as a phase map, not a dense state-transition graph.
- Put the primary lifecycle on one horizontal rail using the `main` lane.
- Use `step` labels for ordered phases, such as `01`, `02`, and `03`.
- Use lower lanes only for interruptions, recovery, and terminal exits.
- Keep transition labels out of the main SVG unless the label is essential;
  prefer node labels, tags, legend entries, and summary cards.
- Avoid diagonal and crossing lines. Terminal exits should drop vertically from
  their source event whenever possible.
- Use `success` for completion, `failure` for failure/terminal exits,
  `waiting` for pauses, and `decision` for quality gates.

Schema violations exit non-zero with path-prefixed messages annotated with the
element's id or label. The renderer additionally fails when it can detect
layout problems, including a missing `main` lane, duplicate state IDs, unknown
lanes, unknown transition endpoints, states outside the lifecycle area,
overlapping states (including across lanes), labels colliding with states or
other labels, labels wider than their state, unreadably short transitions, or
transitions crossing unrelated states (2px Clean Flow clearance). Lifecycle
bands remain intentional pass-through containers.
Text width is estimated CJK-aware: fullwidth glyphs count as two units.

Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper
X crossings then fail with `composition/proper-crossing`; default `standard`
keeps them as artifact-receipt warnings. The final artifact check samples
rounded `Q` corners. Collinear corridors remain outside the proper-X rule, but
a separate gate warns in `standard` and fails in `showcase` when unrelated
transitions overlap for at least 8px. Shared semantic endpoints, point touches,
and shorter overlaps remain valid. Showcase also rejects any route segment
below 8px and any interior turn segment below 16px; ordinary 8–15px endpoint
stubs remain valid.

## renderers/lifecycle/render-lifecycle.mjs

```js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs';
import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs';
import { throwDiagnosticProblems } from '../shared/diagnostics.mjs';
import { resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs';
import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs';
import { brandLabelFitWidth, brandMarkFor, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs';
import { translateMessage as i18nText } from '../shared/i18n.mjs';
import {
  asArray,
  isFinitePoint,
  rectsOverlap,
  cleanEndpointSideProblems,
  cleanFlowProblems,
  cleanCrossingProblems,
  cleanAmbiguousCorridorProblems,
  cleanBorderRunProblems,
  cleanRouteRhythmProblems,
  cleanLabelRouteClearanceProblems,
  suggestLabelObstacleFix,
  suggestLabelPairFix,
  anchor,
  automaticPortSpread,
  defaultFromSide,
  defaultToSide,
  chosenSide,
  roundedPath,
  routePointsValue,
  labelPoint,
  arrowClassMap,
  variantAccent
} from '../shared/geometry.mjs';

const stateTextFit = {
  sublabelPreferred: 7,
  sublabelMinimum: 6,
  tagPreferred: 7,
  tagMinimum: 6,
};

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const { diagram: lifecycle, template, outPath } = await loadDiagramWithBrandMarks({
  rendererDir: __dirname,
  diagramType: 'lifecycle',
  defaultExample: 'agent-run.lifecycle.json'
});

const viewBox = lifecycle.meta?.viewBox || [980, 660];
const layout = {
  phaseY: 126,
  eventY: 278,
  outcomeY: 450,
  phaseW: 118,
  phaseH: 62,
  eventW: 126,
  eventH: 58,
  outcomeW: 118,
  outcomeH: 58,
  phaseXs: [94, 248, 402, 556, 710],
  eventXs: [402, 556, 710],
  outcomeXs: [402, 556, 710]
};

const typeClass = {
  start: 'c-frontend',
  active: 'c-backend',
  waiting: 'c-cloud',
  decision: 'c-security',
  success: 'c-database',
  failure: 'c-security',
  neutral: 'c-external',
  external: 'c-external'
};

const textClass = {
  start: 't-frontend',
  active: 't-backend',
  waiting: 't-cloud',
  decision: 't-security',
  success: 't-database',
  failure: 't-security',
  neutral: 't-muted',
  external: 't-muted'
};

function legendY() {
  return viewBox[1] - 36;
}

// Keep the authored state-placement contract independent from the measured
// legend's lower baseline. Moving legend chrome must not admit new state
// geometry into the reserved outcome/legend band.
function lifecycleAreaBottom() {
  return viewBox[1] - 122;
}

// Lane semantics are fixed: lane id "main" maps to the top phase band, lane id
// "terminal" maps to the bottom outcome band, and every other lane shares the
// middle event band (separated visually via yOffset).
function bandFor(lane) {
  if (lane === 'main') return 'phase';
  if (lane === 'terminal') return 'outcome';
  return 'event';
}

function measureState(state) {
  const isPhase = bandFor(state.lane) === 'phase';
  const isOutcome = bandFor(state.lane) === 'outcome';
  const width = state.width || (isPhase ? layout.phaseW : isOutcome ? layout.outcomeW : layout.eventW);
  const height = state.height || (isPhase ? layout.phaseH : isOutcome ? layout.outcomeH : layout.eventH);
  const xs = isPhase ? layout.phaseXs : isOutcome ? layout.outcomeXs : layout.eventXs;
  const cx = xs[state.col] ?? xs[xs.length - 1];
  const y = (
    isPhase ? layout.phaseY :
      isOutcome ? layout.outcomeY :
        layout.eventY
  ) + (state.yOffset || 0);
  return {
    ...state,
    width,
    height,
    x: cx - width / 2,
    y,
    cx,
    cy: y + height / 2
  };
}

const states = new Map(asArray(lifecycle.states).map((state) => [state.id, measureState(state)]));
const laneLabels = new Map(asArray(lifecycle.lanes).map((lane) => [lane.id, lane.label]));
const stateSteps = new Map();
for (const [index, transition] of asArray(lifecycle.transitions).entries()) {
  if (!stateSteps.has(transition.from)) stateSteps.set(transition.from, index);
  if (!stateSteps.has(transition.to)) stateSteps.set(transition.to, index + 1);
}
for (const [index, state] of asArray(lifecycle.states).entries()) {
  if (!stateSteps.has(state.id)) stateSteps.set(state.id, index);
}

function validateLifecycle() {
  const problems = [];
  if (states.size !== asArray(lifecycle.states).length) problems.push('State ids must be unique.');

  // The three bands are fixed at y=112/264/436. Preserve the original
  // outcome/legend reserve even though measured legend rows now sit lower.
  if (lifecycleAreaBottom() + 4 < 448) {
    problems.push(`viewBox height ${viewBox[1]} is too short for the fixed band layout — set meta.viewBox[1] to at least 566.`);
  }

  const laneIds = new Set(asArray(lifecycle.lanes).map((lane) => lane.id));
  if (laneIds.size !== asArray(lifecycle.lanes).length) problems.push('Lane ids must be unique.');
  if (!laneIds.has('main')) {
    problems.push('Lifecycle diagrams need a lane with id "main" (the phase rail). Lane ids "main" and "terminal" are reserved: "main" maps to the top phase band, "terminal" to the bottom outcome band, and all other lanes share the middle event band.');
  }

  for (const state of states.values()) {
    if (!laneIds.has(state.lane)) {
      problems.push(`State "${state.id}" uses unknown lane "${state.lane}".`);
      continue;
    }
    const band = bandFor(state.lane);
    const maxCol = band === 'phase'
      ? layout.phaseXs.length
      : band === 'outcome'
        ? layout.outcomeXs.length
        : layout.eventXs.length;
    if (!Number.isInteger(state.col) || state.col < 0 || state.col >= maxCol) {
      problems.push(`State "${state.id}" uses invalid column ${state.col} — the ${band} band has integer columns 0..${maxCol - 1}.`);
      continue;
    }
    if (!isFinitePoint(state.x, state.y, state.cx, state.cy)) {
      problems.push(`State "${state.id}" produced non-finite coordinates — check col, width, height, and yOffset are numbers.`);
      continue;
    }
    if (state.x < 32 || state.x + state.width > viewBox[0] - 32) {
      problems.push(`State "${state.id}" exceeds the horizontal bounds of the diagram — reduce state.width or increase meta.viewBox[0].`);
    }
    if (state.y < 64 || state.y + state.height > lifecycleAreaBottom()) {
      problems.push(`State "${state.id}" exceeds the vertical lifecycle area — keep y between 64 and ${lifecycleAreaBottom()} (adjust yOffset or increase meta.viewBox[1]).`);
    }
    const estLabelW = textUnits(state.label) * 6.2;
    if (estLabelW > state.width + 6) {
      problems.push(`Label "${state.label}" (~${Math.round(estLabelW)}px) is wider than state "${state.id}" (${state.width}px) — shorten the label or increase state.width.`);
    }
    const brandRailProblem = brandTopRailProblem(state, state.width, 8, 'State');
    if (brandRailProblem) problems.push(brandRailProblem);
    // sublabel and tag render as single unwrapped <text> elements; shrink-to-fit
    // handles the ordinary case, this rejects what it cannot rescue.
    const availableTextW = availableNodeTextWidth(state.width);
    for (const [field, value, minimum] of [
      ['Sublabel', state.sublabel, stateTextFit.sublabelMinimum],
      ['Tag', state.tag, stateTextFit.tagMinimum],
    ]) {
      if (!value) continue;
      const minimumW = minimumNodeTextWidth(value, minimum);
      if (minimumW > availableTextW) {
        problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but state "${state.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or increase state.width.`);
      }
    }
  }

  // All non-main/non-terminal lanes share the same y band, so the overlap
  // check must run across lanes — not per-lane.
  const allStates = [...states.values()];
  for (let i = 0; i < allStates.length; i += 1) {
    for (let j = i + 1; j < allStates.length; j += 1) {
      if (rectsOverlap(allStates[i], allStates[j], 10)) {
        problems.push(`States "${allStates[i].id}" and "${allStates[j].id}" are less than 10px apart — move one to another col or separate them with yOffset (lanes other than "main"/"terminal" share one band).`);
      }
    }
  }

  for (const transition of asArray(lifecycle.transitions)) {
    if (!states.has(transition.from)) problems.push(`Transition "${transition.label || transition.from}" references unknown source "${transition.from}".`);
    if (!states.has(transition.to)) problems.push(`Transition "${transition.label || transition.to}" references unknown target "${transition.to}".`);
    if (states.has(transition.from) && states.has(transition.to)) {
      const routed = pathFor(transition);
      const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]];
      const distance = Math.hypot(end[0] - start[0], end[1] - start[1]);
      if (distance < 32) problems.push(`Transition "${transition.label || `${transition.from}->${transition.to}`}" is too short (${Math.round(distance)}px; minimum 32px) — route it through a channel or drop its label.`);
    }
  }

  // Authored via points are authoritative in schema v1, including under a
  // quality profile. Preserve and render them exactly: applying the endpoint
  // gate would either reject an existing typed input or require silently
  // falsifying its geometry. Automatic routes still receive the side gate.
  problems.push(...cleanEndpointSideProblems({
    relations: lifecycle.transitions,
    endpointIds: new Set(states.keys()),
    pathFor,
    diagramType: 'lifecycle',
    relationCollection: 'transitions',
    fromSideFor: (transition) => transitionSides(transition).fromSide,
    toSideFor: (transition) => transitionSides(transition).toSide,
    shouldCheckRelation: (transition) => !Array.isArray(transition.via),
    routeHint: 'keep automatic routing, or choose fromSide/toSide and via points whose first and final segments cross state borders perpendicularly',
  }));
  problems.push(...cleanFlowProblems({
    relations: lifecycle.transitions,
    obstacles: states.values(),
    pathFor,
    diagramType: 'lifecycle',
    relationCollection: 'transitions',
    obstacleKind: 'state',
    routeHint: 'adjust fromSide/toSide, set route/via or channelX/channelY, or move the state with col/yOffset'
  }));
  problems.push(...cleanCrossingProblems({
    relations: lifecycle.transitions,
    endpointIds: new Set(states.keys()),
    pathFor,
    diagramType: 'lifecycle',
    relationCollection: 'transitions',
    profile: lifecycle.meta?.quality_profile,
    routeHint: 'adjust route/via or channelX/channelY so the transitions use separate lifecycle corridors'
  }));
  problems.push(...cleanAmbiguousCorridorProblems({
    relations: lifecycle.transitions,
    endpointIds: new Set(states.keys()),
    pathFor,
    diagramType: 'lifecycle',
    relationCollection: 'transitions',
    profile: lifecycle.meta?.quality_profile,
    routeHint: 'adjust route/via or channelX/channelY so unrelated transitions do not visually merge'
  }));
  // Lifecycle bands are dashed reading guides, not closed containers. Keep the
  // shared contract wired with an explicit empty frame set so future typed
  // lifecycle containers cannot accidentally inherit presentation geometry.
  problems.push(...cleanBorderRunProblems({
    relations: lifecycle.transitions,
    endpointIds: new Set(states.keys()),
    frames: [],
    pathFor,
    diagramType: 'lifecycle',
    relationCollection: 'transitions',
    profile: lifecycle.meta?.quality_profile
  }));
  problems.push(...cleanRouteRhythmProblems({
    relations: lifecycle.transitions,
    endpointIds: new Set(states.keys()),
    pathFor,
    diagramType: 'lifecycle',
    relationCollection: 'transitions',
    profile: lifecycle.meta?.quality_profile,
    routeHint: 'move route/via or channel coordinates so each lifecycle turn has a readable run-up'
  }));

  const labelRects = [];
  for (const [transitionIndex, transition] of asArray(lifecycle.transitions).entries()) {
    if (!transition.label || !states.has(transition.from) || !states.has(transition.to)) continue;
    const [lx, ly] = labelPoint(transition, pathFor(transition).points);
    const longestLine = Math.max(textUnits(transition.label), textUnits(transition.note || ''));
    const width = Math.max(32, longestLine * 4.9 + 12);
    const height = transition.note ? 27 : 16;
    labelRects.push({ relation: transition, relationIndex: transitionIndex, label: transition.label, x: lx - width / 2, y: ly - 11, width, height, lx, ly });
  }
  for (const rect of labelRects) {
    for (const state of states.values()) {
      if (rectsOverlap(rect, state, -2)) {
        problems.push(`Label "${rect.label}" overlaps state "${state.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, state, 'state')}`);
      }
    }
  }
  for (let i = 0; i < labelRects.length; i += 1) {
    for (let j = i + 1; j < labelRects.length; j += 1) {
      if (rectsOverlap(labelRects[i], labelRects[j], -2)) {
        problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}`);
      }
    }
  }
  problems.push(...cleanLabelRouteClearanceProblems({
    relations: lifecycle.transitions,
    labels: labelRects,
    endpointIds: new Set(states.keys()),
    pathFor,
    diagramType: 'lifecycle',
    relationCollection: 'transitions',
    profile: lifecycle.meta?.quality_profile,
  }));

  if (problems.length) {
    throwDiagnosticProblems('Lifecycle layout validation failed', problems, {
      subject: { diagramType: 'lifecycle' },
    });
  }
}

function routeVia(transition, from, to, start, end, fromSide, toSide) {
  if (transition.via) return transition.via;
  switch (transition.route || 'auto') {
    case 'straight':
      return [];
    case 'drop': {
      const y = transition.channelY ?? (start[1] + end[1]) / 2;
      return [[start[0], y], [end[0], y]];
    }
    case 'bottom-channel': {
      const y = transition.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 34;
      return [[start[0], y], [end[0], y]];
    }
    case 'top-channel': {
      const y = transition.channelY ?? Math.min(from.y, to.y) - 28;
      return [[start[0], y], [end[0], y]];
    }
    case 'right-channel': {
      const x = transition.channelX ?? Math.max(from.x + from.width, to.x + to.width) + 36;
      return [[x, start[1]], [x, end[1]]];
    }
    case 'left-channel': {
      const x = transition.channelX ?? Math.min(from.x, to.x) - 36;
      return [[x, start[1]], [x, end[1]]];
    }
    case 'auto':
    default: {
      if (start[0] === end[0] || start[1] === end[1]) return [];
      const fromVertical = fromSide === 'top' || fromSide === 'bottom';
      const toVertical = toSide === 'top' || toSide === 'bottom';
      if (fromVertical !== toVertical) {
        return [fromVertical ? [start[0], end[1]] : [end[0], start[1]]];
      }
      if (fromVertical) {
        const y = transition.channelY ?? (start[1] + end[1]) / 2;
        return [[start[0], y], [end[0], y]];
      }
      const x = transition.channelX ?? (start[0] + end[0]) / 2;
      return [[x, start[1]], [x, end[1]]];
    }
  }
}

const pathCache = new Map();

function transitionSides(transition) {
  const from = states.get(transition.from);
  const to = states.get(transition.to);
  return {
    fromSide: chosenSide(transition.fromSide, defaultFromSide(from, to)),
    toSide: chosenSide(transition.toSide, defaultToSide(from, to)),
  };
}

const automaticPorts = automaticPortSpread(lifecycle.transitions, states, {
  sideFor: (transition, endpoint) => transitionSides(transition)[endpoint === 'source' ? 'fromSide' : 'toSide'],
});

function pathFor(transition) {
  if (pathCache.has(transition)) return pathCache.get(transition);
  const from = states.get(transition.from);
  const to = states.get(transition.to);
  const ports = automaticPorts.get(transition);
  const { fromSide, toSide } = transitionSides(transition);
  const start = ports?.from || anchor(from, fromSide);
  const end = ports?.to || anchor(to, toSide);
  let via = routeVia(transition, from, to, start, end, fromSide, toSide);
  if (ports && !via.length && Math.abs(start[0] - end[0]) >= 4 && Math.abs(start[1] - end[1]) >= 4) {
    const midX = (start[0] + end[0]) / 2;
    via = [[midX, start[1]], [midX, end[1]]];
  }
  const points = [start, ...via, end];
  const routed = {
    d: roundedPath(points, transition.cornerRadius ?? 10),
    points
  };
  pathCache.set(transition, routed);
  return routed;
}

function bandTitles() {
  const lanes = asArray(lifecycle.lanes);
  const mainLane = lanes.find((lane) => lane.id === 'main');
  const terminalLane = lanes.find((lane) => lane.id === 'terminal');
  const eventLanes = lanes.filter((lane) => lane.id !== 'main' && lane.id !== 'terminal');
  return [
    mainLane?.label || 'Lifecycle phases',
    eventLanes.length ? eventLanes.map((lane) => lane.label).join(' + ') : 'Interruptions + recovery',
    terminalLane?.label || 'Outcomes'
  ];
}

function renderBands() {
  const right = viewBox[0] - 72;
  const titles = bandTitles();
  return `        <path d="M 72 112 L ${right} 112" class="a-default" stroke-width="0.8" stroke-dasharray="3,8"/>
        <text x="72" y="100" class="t-dim" font-size="10" font-weight="600">01 / ${esc(titles[0])}</text>
        <path d="M 72 264 L ${right} 264" class="a-default" stroke-width="0.8" stroke-dasharray="3,8"/>
        <text x="72" y="252" class="t-dim" font-size="10" font-weight="600">02 / ${esc(titles[1])}</text>
        <path d="M 72 436 L ${right} 436" class="a-default" stroke-width="0.8" stroke-dasharray="3,8"/>
        <text x="72" y="424" class="t-dim" font-size="10" font-weight="600">03 / ${esc(titles[2])}</text>`;
}

function renderState(state) {
  const fill = typeClass[state.type] || typeClass.neutral;
  const accent = textClass[state.type] || 't-muted';
  const hasSub = state.sublabel != null && state.sublabel !== '';
  const sub = hasSub
    ? `\n          <text data-detail="context" x="${state.cx}" y="${state.y + 37}" class="t-muted" font-size="${fittedNodeFontSize(state.sublabel, state.width, stateTextFit.sublabelPreferred, stateTextFit.sublabelMinimum)}" text-anchor="middle">${esc(state.sublabel)}</text>`
    : '';
  const tag = state.tag
    ? `\n        <text data-detail="fine" x="${state.cx}" y="${state.y + state.height - 11}" class="${accent}" font-size="${fittedNodeFontSize(state.tag, state.width, stateTextFit.tagPreferred, stateTextFit.tagMinimum)}" text-anchor="middle">${esc(state.tag)}</text>`
    : '';
  const hasBrand = Boolean(brandMarkFor(state));
  const step = state.step
    ? `\n        <text data-detail="fine" x="${state.x + (hasBrand ? 23 : 10)}" y="${state.y + 14}" class="${accent}" font-size="7" font-weight="700">${esc(state.step)}</text>`
    : '';
  const brand = renderBrandMark(state, { x: state.x + state.width - 22, y: state.y + 6 });
  const labelFontSize = fittedNodeFontSize(state.label, brandLabelFitWidth(state, state.width), 10, 8);
  const passport = {
    kind: state.type,
    sublabel: state.sublabel,
    tag: state.tag,
    context: laneLabels.get(state.lane) || i18nText(lifecycle.meta.locale, 'node.context.lifecycle'),
    ...brandMetadataFor(state),
  };
  return `        <g ${focusNodeAttrs(state.id, state.label, passport, lifecycle.meta.locale)}>
          ${focusNodeTitle(state.label, passport)}
          <rect x="${state.x}" y="${state.y}" width="${state.width}" height="${state.height}" rx="7" class="c-mask"/>
          <rect x="${state.x}" y="${state.y}" width="${state.width}" height="${state.height}" rx="7" class="${fill}"${animateAttr(lifecycle.meta, 'node', stateSteps.get(state.id))} stroke-width="1.5"/>
          ${renderSemanticSigil(state.type, { x: hasBrand ? state.x + 6 : state.x + state.width - 17, y: state.y + 6 })}${brand ? `\n          ${brand}` : ''}${step}
          <text data-node-label=""${hasSub ? ' data-detail-anchor=""' : ''} x="${state.cx}" y="${state.y + 21}" class="t-primary" font-size="${labelFontSize}" font-weight="600" text-anchor="middle">${esc(state.label)}</text>${sub}${tag}
        </g>`;
}

function renderTransitionPath(transition, index) {
  const [cls, marker] = arrowClassMap[transition.variant || 'default'] || arrowClassMap.default;
  const routed = pathFor(transition);
  const strokeWidth = transition.width || (transition.variant === 'emphasis' ? 2 : 1.1);
  return `        <path ${focusEdgeAttrs(transition.from, transition.to, transition.label, index, transition.id)} data-composition-points="${routePointsValue(routed.points)}" d="${routed.d}" class="${cls}"${animateAttr(lifecycle.meta, 'edge', index)} stroke-width="${strokeWidth}" marker-end="url(#${marker})"/>`;
}

function renderTransitionLabel(transition, index) {
  if (!transition.label) return '';
  const routed = pathFor(transition);
  const [lx, ly] = labelPoint(transition, routed.points);
  const longestLine = Math.max(textUnits(transition.label), textUnits(transition.note || ''));
  const labelW = Math.max(32, longestLine * 4.9 + 12);
  const labelH = transition.note ? 27 : 16;
  const note = transition.note
    ? `\n        <text data-detail="fine" x="${lx}" y="${ly + 11}" class="t-dim" font-size="7" text-anchor="middle">${esc(transition.note)}</text>`
    : '';
  return `        <g data-detail="context" ${focusEdgeAttrs(transition.from, transition.to, transition.label, index, transition.id)}>
          <rect x="${lx - labelW / 2}" y="${ly - 11}" width="${labelW}" height="${labelH}" rx="4" class="c-mask"/>
          <text x="${lx}" y="${ly}" class="${variantAccent(transition.variant)}" font-size="8" text-anchor="middle">${esc(transition.label)}</text>${note}
        </g>`;
}

const LEGEND_CATALOG = [
  'start',
  'active',
  'waiting',
  'decision',
  'success',
  'failure',
  'neutral',
  'external',
].map((kind) => ({ kind, label: i18nText(lifecycle.meta.locale, `legend.lifecycle.${kind}`) }));

function renderLegend() {
  const presentKinds = new Set([...states.values()].map((state) => state.type));
  const entries = resolveLegend(lifecycle.meta?.legend, LEGEND_CATALOG, presentKinds);
  return renderResolvedLegend({
    entries,
    locale: lifecycle.meta.locale,
    layout: {
      x: 40,
      baselineY: legendY(),
      width: viewBox[0] - 80,
      minTitleY: lifecycleAreaBottom() + 8,
      unfit: lifecycle.meta?.legend === undefined ? 'hide' : 'error',
      diagramType: 'lifecycle',
    },
    renderSwatch: (entry) => `<rect x="${entry.x}" y="${entry.baseline - 8}" width="14" height="9" rx="2" class="${typeClass[entry.kind] || 'c-external'}" stroke-width="1"/>`,
  });
}

function renderLifecycleRail() {
  const mainCols = [...states.values()]
    .filter((state) => bandFor(state.lane) === 'phase')
    .map((state) => state.col);
  if (!mainCols.length) return '';
  const railEnd = layout.phaseXs[Math.max(...mainCols)] + 38;
  return `        <path d="M 154 ${layout.phaseY + 31} L ${railEnd} ${layout.phaseY + 31}" class="a-emphasis" stroke-width="2.2" marker-end="url(#arrowhead-emphasis)"/>`;
}

function renderSvg() {
  return `      <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(lifecycle.meta)}>
${svgAccessibleText(lifecycle.meta, 'lifecycle')}
${renderDefinitions()}

        <!-- Background Grid -->
        <rect width="100%" height="100%" fill="url(#grid)" />

        <!-- Lifecycle bands -->
${renderBands()}

        <!-- Primary lifecycle rail -->
${renderLifecycleRail()}

        <!-- Transition paths -->
${asArray(lifecycle.transitions).map(renderTransitionPath).join('\n')}

        <!-- States -->
${[...states.values()].map(renderState).join('\n\n')}

        <!-- Transition labels -->
${asArray(lifecycle.transitions).map(renderTransitionLabel).join('\n')}

        <!-- Legend -->
${renderLegend()}
      </svg>`;
}

validateLifecycle();
writeDiagram({
  outPath,
  template,
  diagramType: 'lifecycle',
  meta: lifecycle.meta,
  svg: renderSvg(),
  cards: lifecycle.cards,
});
```

## renderers/sequence

```

```

## renderers/sequence/README.md

# Sequence Renderer

Render `diagram_type: "sequence"` JSON files into the standard Archify HTML
template.

```bash
node archify/renderers/sequence/render-sequence.mjs input.sequence.json output.html
```

The renderer validates input against `archify/schemas/sequence.schema.json`
with the bundled standalone validator. No dependency installation is required.

If `output.html` is omitted, the renderer uses `meta.output` from the JSON file
or falls back to `sequence.html` in the current working directory.

## Input

Sequence JSON files must set:

```json
{
  "schema_version": 1,
  "diagram_type": "sequence",
  "meta": {
    "title": "Cache Miss Request Sequence",
    "viewBox": [920, 760]
  },
  "participants": [],
  "segments": [],
  "messages": [],
  "activations": [],
  "cards": []
}
```

The timeline scales with the viewBox height: a taller `meta.viewBox` buys more
message room, a shorter one shrinks the readable band instead of clipping. A
complete worked example lives at
`archify/examples/cache-miss-request.sequence.json`.

The schema lives at:

```text
archify/schemas/sequence.schema.json
```

## Legend

The default visual legend derives kinds from `messages[].variant` (omitting
`variant` means `default`). Supported `meta.legend.entries` keys, in stable
order, are `emphasis`, `return`, `security`, `dashed`, and `default`. These are
visual message keys, not Semantic Lens controls; label/visibility overrides do
not create edge facts.

## Layout budget

| Constant | Value |
|----------|-------|
| viewBox | default `[920, 760]`; schema minimum `[480, 480]` |
| Participant boxes | `fixed` (default): 86×54 at y 72; `spread`: viewBox-relative width from 86px up to 190px |
| Participant columns | `fixed`: centers at x = 62 + index×108; `spread`: columns distribute across the available viewBox width |
| Participant count | the last box must end at or before width − 40; layouts that cannot fit fail closed |
| Lifelines | from y 142 down to height − 65; band must be ≥120px tall |
| Message `y` range | `[160, height − 83]` |
| Message spacing | ≥28px vertical between messages that share horizontal space |
| Arrow span | ≥60px horizontal between the two participants |
| Segments | y pixel ranges with `to > from`, inside `[72, lifeline bottom + 20]` |
| Legend row | y = height − 54 |

`segments[].from/to` and `activations[].from/to` are y pixel coordinates, not
participant ids; activations also require `to > from`.

### Column fit

Sequence diagrams use `meta.column_fit: "fixed"` by default so existing
documents keep their historical coordinates. Use `"spread"` when a wide
viewBox would otherwise leave empty space on the right or when meaningful
participant labels do not fit the fixed 86px boxes. Spread derives box width
and column distance from the viewBox while preserving participant order,
lifelines, and message semantics.

## Design Rules

- Put participants across the top, ordered by the story the reader should
  follow.
- Time moves downward.
- Use `emphasis` for the main request path.
- Use `security` for auth, consent, permission, and policy calls.
- Use `return` for quiet response messages.
- Use `dashed` for async trace, event, logging, and non-blocking work.
- Use segments as light background guides; keep segment labels short.
- Keep labels concise, but try `meta.column_fit: "spread"` before shortening a
  meaningful participant label just to fit the fixed boxes.

Schema violations exit non-zero with path-prefixed messages annotated with the
element's id or label. The renderer additionally fails when it can detect
layout problems, including missing participants, duplicate participant IDs,
participant labels wider than their box, unknown message endpoints, messages
outside the readable timeline, overly tight vertical spacing between messages
that overlap horizontally, invalid segment or activation ranges, or
participants that exceed the viewBox. The shared Clean Flow contract treats
participant headers as semantic boxes while explicitly allowing messages to
cross intermediate lifelines, activation bars, and segment frames. Text width is estimated CJK-aware:
fullwidth glyphs count as two units.

Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper
message X crossings then fail with `composition/proper-crossing`; default
`standard` keeps them as artifact-receipt warnings. Messages may still cross
intermediate lifelines. Collinear corridors remain outside the proper-X rule,
but a separate gate warns in `standard` and fails in `showcase` when unrelated
messages overlap for at least 8px. Shared semantic endpoints, point touches,
and shorter overlaps remain valid. Showcase also rejects any route segment
below 8px and any interior turn segment below 16px; ordinary 8–15px endpoint
stubs remain valid.

## renderers/sequence/render-sequence.mjs

```js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs';
import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs';
import { throwDiagnosticProblems } from '../shared/diagnostics.mjs';
import { resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs';
import { componentFill, arrowClassMap, rectsOverlap, cleanFlowProblems, cleanCrossingProblems, cleanAmbiguousCorridorProblems, cleanBorderRunProblems, cleanRouteRhythmProblems, cleanLabelRouteClearanceProblems, routePointsValue, asArray, isFinitePoint } from '../shared/geometry.mjs';
import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs';
import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs';
import { translateMessage as i18nText } from '../shared/i18n.mjs';

const participantTextFit = {
  sublabelPreferred: 7,
  sublabelMinimum: 6,
};

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const { diagram: sequence, template, outPath } = await loadDiagramWithBrandMarks({
  rendererDir: __dirname,
  diagramType: 'sequence',
  defaultExample: 'cache-miss-request.sequence.json'
});

const viewBox = sequence.meta?.viewBox || [920, 760];
// The timeline scales with viewBox height: a taller viewBox gains message room,
// a shorter one shrinks the readable band (validated below) instead of clipping.
// `column_fit: "spread"` widens the lanes with the viewBox instead of keeping
// the fixed 108px gap, so a wide canvas gains column distance and label room
// rather than dead space on the right. The default stays "fixed" so existing
// diagrams keep their coordinates.
const columnFit = sequence.meta?.column_fit === 'spread' ? 'spread' : 'fixed';
const participantCount = Math.max(1, asArray(sequence.participants).length);
const sideMargin = 62;
const participantW = columnFit === 'spread'
  ? Math.max(86, Math.min(190, Math.round((viewBox[0] - sideMargin * 2) / participantCount) - 24))
  : 86;
const colGap = columnFit === 'spread' && participantCount > 1
  ? Math.max(108, (viewBox[0] - 40 - sideMargin - participantW) / (participantCount - 1))
  : 108;

const layout = {
  topY: 72,
  participantW,
  participantH: 54,
  lifelineTop: 142,
  lifelineBottom: viewBox[1] - 65,
  legendY: viewBox[1] - 54,
  leftX: columnFit === 'spread' ? sideMargin + participantW / 2 : sideMargin,
  colGap,
  labelH: 16
};

const participantBoxWidthNote = columnFit === 'spread'
  ? `participant boxes are ${participantW}px for this viewBox width and ${participantCount} participants`
  : `participant boxes are a fixed ${participantW}px unless meta.column_fit is "spread"`;

const arrowClass = {
  ...arrowClassMap,
  return: ['a-default', 'arrowhead']
};

function participantX(index) {
  return layout.leftX + index * layout.colGap;
}

const participants = new Map(asArray(sequence.participants).map((participant, index) => [
  participant.id,
  {
    ...participant,
    index,
    cx: participantX(index),
    x: participantX(index) - layout.participantW / 2,
    y: layout.topY,
    width: layout.participantW,
    height: layout.participantH,
    cy: layout.topY + layout.participantH / 2
  }
]));

function messageGeometry(message) {
  const from = participants.get(message.from);
  const to = participants.get(message.to);
  if (!from || !to || typeof message.y !== 'number') return null;
  const direction = to.cx > from.cx ? 1 : -1;
  const start = from.cx + direction * 7;
  const end = to.cx - direction * 7;
  return { start, end, center: (start + end) / 2 };
}

function messageLabelBox(message, relationIndex = null) {
  const geometry = messageGeometry(message);
  if (!geometry) return null;
  const width = Math.max(34, textUnits(message.label) * 5.2 + 12);
  return {
    relation: message,
    relationIndex,
    label: message.label,
    x: geometry.center - width / 2,
    y: message.y - 20,
    width,
    height: layout.labelH,
  };
}

function messageRouteBox(message) {
  const geometry = messageGeometry(message);
  if (!geometry) return null;
  return {
    x: Math.min(geometry.start, geometry.end),
    y: message.y - 2,
    width: Math.abs(geometry.end - geometry.start),
    height: 4,
  };
}

function segmentLabelBox(segment) {
  const labelW = Math.max(42, textUnits(segment.label) * 5.2 + 14);
  const occupied = asArray(sequence.messages)
    .flatMap((message) => [messageLabelBox(message), messageRouteBox(message)])
    .filter(Boolean);
  const label = { x: 56, y: segment.from - 22, width: labelW, height: 18 };
  for (let attempt = 0; attempt < 4; attempt += 1) {
    if (!occupied.some((rect) => rectsOverlap(label, rect, 2))) break;
    label.y -= 22;
  }
  return label;
}

const compositionFrames = asArray(sequence.segments).map((segment, index) => ({
  id: index,
  label: segment.label,
  kind: 'segment',
  x: 48,
  y: segment.from,
  width: viewBox[0] - 96,
  height: segment.to - segment.from,
  radius: 10,
}));

function messagePath(message) {
  return {
    points: participants.has(message.from) && participants.has(message.to)
      ? [[participants.get(message.from).cx, message.y], [participants.get(message.to).cx, message.y]]
      : []
  };
}

function validateSequence() {
  const problems = [];
  if (participants.size !== asArray(sequence.participants).length) problems.push('Participant ids must be unique.');

  if (layout.lifelineBottom - layout.lifelineTop < 120) {
    problems.push(`viewBox height ${viewBox[1]} leaves under 120px of timeline — set meta.viewBox[1] to at least ${layout.lifelineTop + 120 + 65}.`);
  }

  for (const participant of participants.values()) {
    const estLabelW = textUnits(participant.label) * 6.8;
    if (estLabelW > layout.participantW + 6) {
      problems.push(`Label "${participant.label}" (~${Math.round(estLabelW)}px) is wider than the ${layout.participantW}px participant box — shorten it.`);
    }
    const brandRailProblem = brandTopRailProblem(participant, layout.participantW, 8, 'Participant');
    if (brandRailProblem) problems.push(brandRailProblem);
    // sublabel renders as a single unwrapped <text>; shrink-to-fit handles the
    // ordinary case, this rejects what it cannot rescue.
    if (participant.sublabel) {
      const availableTextW = availableNodeTextWidth(layout.participantW);
      const minimumW = minimumNodeTextWidth(participant.sublabel, participantTextFit.sublabelMinimum);
      if (minimumW > availableTextW) {
        problems.push(`Sublabel "${participant.sublabel}" needs ~${Math.ceil(minimumW)}px at the ${participantTextFit.sublabelMinimum}px legible minimum, but participant "${participant.id}" provides ${availableTextW}px — shorten the sublabel (${participantBoxWidthNote}).`);
      }
    }
  }

  for (const message of asArray(sequence.messages)) {
    if (!participants.has(message.from)) problems.push(`Message "${message.label}" references unknown source "${message.from}".`);
    if (!participants.has(message.to)) problems.push(`Message "${message.label}" references unknown target "${message.to}".`);
    if (typeof message.y !== 'number') problems.push(`Message "${message.label}" must provide a numeric y.`);
    if (message.y < layout.lifelineTop + 18 || message.y > layout.lifelineBottom - 18) {
      problems.push(`Message "${message.label}" sits outside the readable timeline — keep y between ${layout.lifelineTop + 18} and ${layout.lifelineBottom - 18}.`);
    }
    if (participants.has(message.from) && participants.has(message.to)) {
      const distance = Math.abs(participants.get(message.to).cx - participants.get(message.from).cx);
      if (distance < 60) problems.push(`Message "${message.label}" spans ${Math.round(distance)}px (minimum 60px) — give its participants more column distance.`);
    }
  }

  // Participant headers are opaque nodes. Lifelines, activation bars, and
  // segment bands remain intentional pass-through geometry and are excluded.
  problems.push(...cleanFlowProblems({
    relations: sequence.messages,
    obstacles: participants.values(),
    pathFor: messagePath,
    diagramType: 'sequence',
    relationCollection: 'messages',
    obstacleKind: 'participant header',
    clearance: 0,
    routeHint: 'move the message y below the participant headers or reorder participants'
  }));
  problems.push(...cleanCrossingProblems({
    relations: sequence.messages,
    endpointIds: new Set(participants.keys()),
    pathFor: messagePath,
    diagramType: 'sequence',
    relationCollection: 'messages',
    profile: sequence.meta?.quality_profile,
    routeHint: 'separate the message y values; lifeline crossings remain allowed'
  }));
  problems.push(...cleanAmbiguousCorridorProblems({
    relations: sequence.messages,
    endpointIds: new Set(participants.keys()),
    pathFor: messagePath,
    diagramType: 'sequence',
    relationCollection: 'messages',
    profile: sequence.meta?.quality_profile,
    routeHint: 'separate the message y values so unrelated messages do not visually merge'
  }));
  problems.push(...cleanBorderRunProblems({
    relations: sequence.messages,
    endpointIds: new Set(participants.keys()),
    frames: compositionFrames,
    pathFor: messagePath,
    diagramType: 'sequence',
    relationCollection: 'messages',
    profile: sequence.meta?.quality_profile,
    routeHint: 'move the message y so it crosses a segment boundary perpendicularly or stays clearly inside the segment'
  }));
  problems.push(...cleanRouteRhythmProblems({
    relations: sequence.messages,
    endpointIds: new Set(participants.keys()),
    pathFor: messagePath,
    diagramType: 'sequence',
    relationCollection: 'messages',
    profile: sequence.meta?.quality_profile,
    routeHint: 'increase participant spacing or simplify message routing so every turn has room to read'
  }));

  // Vertical crowding only matters when the arrows share horizontal space;
  // disjoint arrows may legitimately run in parallel rows.
  const placed = asArray(sequence.messages)
    .filter((m) => participants.has(m.from) && participants.has(m.to))
    .map((m) => ({
      label: m.label,
      y: m.y,
      x1: Math.min(participants.get(m.from).cx, participants.get(m.to).cx),
      x2: Math.max(participants.get(m.from).cx, participants.get(m.to).cx)
    }))
    .sort((a, b) => a.y - b.y);
  for (let i = 0; i < placed.length; i += 1) {
    for (let j = i + 1; j < placed.length && placed[j].y - placed[i].y < 28; j += 1) {
      if (placed[i].x1 < placed[j].x2 && placed[j].x1 < placed[i].x2) {
        problems.push(`Messages "${placed[i].label}" and "${placed[j].label}" are less than 28px apart and share horizontal space — spread their y values.`);
      }
    }
  }

  // Label masks can extend well past the arrow span, so check the actual
  // label rectangles too — tangent arrows with long labels still collide.
  const labelRects = asArray(sequence.messages)
    .map((m, messageIndex) => messageLabelBox(m, messageIndex))
    .filter(Boolean);
  for (let i = 0; i < labelRects.length; i += 1) {
    for (let j = i + 1; j < labelRects.length; j += 1) {
      if (rectsOverlap(labelRects[i], labelRects[j], -2)) {
        problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — spread their message y values or shorten the labels.`);
      }
    }
  }
  problems.push(...cleanLabelRouteClearanceProblems({
    relations: sequence.messages,
    labels: labelRects,
    endpointIds: new Set(participants.keys()),
    pathFor: messagePath,
    diagramType: 'sequence',
    relationCollection: 'messages',
    profile: sequence.meta?.quality_profile,
    routeHint: 'spread the message y values, shorten the label, or reorder participants so the adjacent route stays visible'
  }));

  for (const segment of asArray(sequence.segments)) {
    if (segment.to <= segment.from) {
      problems.push(`Segment "${segment.label}" has invalid y range (from ${segment.from} to ${segment.to}) — "to" must be greater than "from".`);
    }
    if (segment.from < layout.topY || segment.to > layout.lifelineBottom + 20) {
      problems.push(`Segment "${segment.label}" extends outside the canvas — keep its y range between ${layout.topY} and ${layout.lifelineBottom + 20}.`);
    }
    const labelBox = segmentLabelBox(segment);
    const availableWidth = Math.max(0, viewBox[0] - 48 - labelBox.x);
    if (labelBox.x + labelBox.width > viewBox[0] - 48) {
      const requiredWidth = Math.ceil(labelBox.x + labelBox.width + 48);
      problems.push(`Segment "${segment.label}" label (~${Math.round(labelBox.width)}px) exceeds the segment frame's available width (${availableWidth}px) — shorten the label or increase meta.viewBox[0] to at least ${requiredWidth}.`);
    }
  }

  for (const activation of asArray(sequence.activations)) {
    if (!participants.has(activation.participant)) problems.push(`Activation references unknown participant "${activation.participant}".`);
    if (activation.to <= activation.from) problems.push(`Activation for "${activation.participant}" has invalid time range — "to" must be greater than "from".`);
  }

  const lastParticipant = asArray(sequence.participants)[asArray(sequence.participants).length - 1];
  if (lastParticipant && participants.get(lastParticipant.id).cx + layout.participantW / 2 > viewBox[0] - 40) {
    const requiredWidth = Math.ceil(participants.get(lastParticipant.id).cx + layout.participantW / 2 + 40);
    problems.push(`Participants exceed viewBox width — set meta.viewBox[0] to at least ${requiredWidth} or remove a participant.`);
  }

  if (problems.length) {
    throwDiagnosticProblems('Sequence layout validation failed', problems, {
      subject: { diagramType: 'sequence' },
    });
  }
}

function renderParticipant(participant) {
  const fill = componentFill[participant.type] || 'c-external';
  const hasSub = participant.sublabel != null && participant.sublabel !== '';
  const sub = hasSub
    ? `\n          <text data-detail="context" x="${participant.cx}" y="${layout.topY + 39}" class="t-muted" font-size="${fittedNodeFontSize(participant.sublabel, layout.participantW, participantTextFit.sublabelPreferred, participantTextFit.sublabelMinimum)}" text-anchor="middle">${esc(participant.sublabel)}</text>`
    : '';
  const brand = renderBrandMark(participant, { x: participant.x + layout.participantW - 22, y: layout.topY + 6 });
  const labelFontSize = fittedNodeFontSize(participant.label, brandLabelFitWidth(participant, layout.participantW), 11, 8);
  const passport = {
    kind: participant.type,
    sublabel: participant.sublabel,
    context: i18nText(sequence.meta.locale, 'node.context.sequence'),
    ...brandMetadataFor(participant),
  };
  return `        <g ${focusNodeAttrs(participant.id, participant.label, passport, sequence.meta.locale)}>
          ${focusNodeTitle(participant.label, passport)}
          <rect x="${participant.x}" y="${layout.topY}" width="${layout.participantW}" height="${layout.participantH}" rx="6" class="c-mask"/>
          <rect x="${participant.x}" y="${layout.topY}" width="${layout.participantW}" height="${layout.participantH}" rx="6" class="${fill}"${animateAttr(sequence.meta, 'node', participant.index)} stroke-width="1.5"/>
          ${renderSemanticSigil(participant.type, { x: participant.x + 6, y: layout.topY + 6 })}${brand ? `\n          ${brand}` : ''}
          <text data-node-label=""${hasSub ? ' data-detail-anchor=""' : ''} x="${participant.cx}" y="${layout.topY + 22}" class="t-primary" font-size="${labelFontSize}" font-weight="600" text-anchor="middle">${esc(participant.label)}</text>${sub}
        </g>`;
}

function renderLifeline(participant) {
  return `        <path d="M ${participant.cx} ${layout.lifelineTop} L ${participant.cx} ${layout.lifelineBottom}" class="a-default" stroke-width="0.8" stroke-dasharray="3,7"/>`;
}

function renderSegment(segment, index) {
  return `        <rect data-graph-role="structural-frame" data-composition-frame-kind="segment" data-composition-frame-id="${index}" x="48" y="${segment.from}" width="${viewBox[0] - 96}" height="${segment.to - segment.from}" rx="10" class="c-lane" stroke-width="1"/>`;
}

function renderSegmentLabel(segment, index) {
  const label = segmentLabelBox(segment);
  return `        <g data-graph-role="segment-label" data-segment-id="${index}">
          <rect x="${label.x}" y="${label.y}" width="${label.width}" height="${label.height}" rx="3" class="c-mask"/>
          <text x="${label.x + 6}" y="${label.y + 13}" class="t-dim" font-size="9" font-weight="600">${esc(segment.label)}</text>
        </g>`;
}

function renderActivation(activation) {
  const participant = participants.get(activation.participant);
  const fill = componentFill[activation.type] || componentFill[participant.type] || 'c-external';
  const x = participant.cx - 5;
  const height = activation.to - activation.from;
  return `        <rect x="${x}" y="${activation.from}" width="10" height="${height}" rx="3" class="c-mask"/>
        <rect x="${x}" y="${activation.from}" width="10" height="${height}" rx="3" class="${fill}" stroke-width="1"/>`;
}

function messageLabel(message, x1, x2) {
  const box = messageLabelBox(message);
  const center = box ? box.x + box.width / 2 : (x1 + x2) / 2;
  const y = message.y - 10;
  const labelW = box?.width || Math.max(34, textUnits(message.label) * 5.2 + 12);
  const accent = message.variant === 'security'
    ? 't-security'
    : message.variant === 'dashed'
      ? 't-messagebus'
      : message.variant === 'return'
        ? 't-muted'
        : 't-backend';
  return `        <g data-detail="context">
          <rect x="${center - labelW / 2}" y="${y - 10}" width="${labelW}" height="${layout.labelH}" rx="3" class="c-mask"/>
          <text x="${center}" y="${y}" class="${accent}" font-size="9" text-anchor="middle">${esc(message.label)}</text>
        </g>`;
}

function renderMessage(message, index) {
  const { start, end } = messageGeometry(message);
  const [cls, marker] = arrowClass[message.variant || 'default'] || arrowClass.default;
  const strokeWidth = message.variant === 'emphasis' ? 1.8 : 1.4;
  const dash = message.variant === 'return' ? ' stroke-dasharray="3,5"' : '';
  const note = message.note
    ? `\n        <text data-detail="fine" x="${Math.min(start, end) + 12}" y="${message.y + 18}" class="t-dim" font-size="7">${esc(message.note)}</text>`
    : '';
  return `        <g ${focusEdgeAttrs(message.from, message.to, message.label, index, message.id)}>
          <path data-composition-edge-from="${esc(message.from)}" data-composition-edge-to="${esc(message.to)}"${message.id ? ` data-composition-edge-id="${esc(message.id)}"` : ''} data-composition-points="${routePointsValue([[start, message.y], [end, message.y]])}" d="M ${start} ${message.y} L ${end} ${message.y}" class="${cls}"${animateAttr(sequence.meta, 'edge', index)} stroke-width="${strokeWidth}"${dash} marker-end="url(#${marker})"/>
${messageLabel(message, start, end)}${note}
        </g>`;
}

const LEGEND_CATALOG = [
  { kind: 'emphasis', className: 'a-emphasis', marker: 'arrowhead-emphasis', strokeWidth: 1.8 },
  { kind: 'return', className: 'a-default', marker: 'arrowhead', dash: '3,5' },
  { kind: 'security', className: 'a-security', marker: 'arrowhead-security' },
  { kind: 'dashed', className: 'a-dashed', marker: 'arrowhead-dashed' },
  { kind: 'default', className: 'a-default', marker: 'arrowhead' },
].map((entry) => ({
  ...entry,
  interactive: false,
  swatchWidth: 34,
  swatchGap: 9,
  label: i18nText(sequence.meta.locale, `legend.sequence.${entry.kind}`),
}));

function renderLegend() {
  const presentKinds = new Set(asArray(sequence.messages).map((message) => message.variant || 'default'));
  const entries = resolveLegend(sequence.meta?.legend, LEGEND_CATALOG, presentKinds);
  return renderResolvedLegend({
    entries,
    locale: sequence.meta.locale,
    layout: {
      x: 40,
      baselineY: layout.legendY,
      width: viewBox[0] - 80,
      minTitleY: layout.legendY - 30,
      unfit: sequence.meta?.legend === undefined ? 'hide' : 'error',
      diagramType: 'sequence',
    },
    renderSwatch: (entry) => `<path d="M ${entry.x} ${entry.baseline - 3} L ${entry.x + 34} ${entry.baseline - 3}" class="${entry.className}" stroke-width="${entry.strokeWidth || 1.4}"${entry.dash ? ` stroke-dasharray="${entry.dash}"` : ''} marker-end="url(#${entry.marker})"/>`,
  });
}

function renderSvg() {
  const participantList = [...participants.values()];
  return `      <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(sequence.meta)}>
${svgAccessibleText(sequence.meta, 'sequence')}
${renderDefinitions()}

        <!-- Background Grid -->
        <rect width="100%" height="100%" fill="url(#grid)" />

        <!-- Time Segments -->
${asArray(sequence.segments).map(renderSegment).join('\n\n')}

        <!-- Lifelines -->
${participantList.map(renderLifeline).join('\n')}

        <!-- Activations -->
${asArray(sequence.activations).map(renderActivation).join('\n')}

        <!-- Messages -->
${asArray(sequence.messages).map(renderMessage).join('\n\n')}

        <!-- Segment Labels -->
${asArray(sequence.segments).map(renderSegmentLabel).join('\n')}

        <!-- Participants -->
${participantList.map(renderParticipant).join('\n\n')}

        <!-- Legend -->
${renderLegend()}
      </svg>`;
}

validateSequence();
writeDiagram({
  outPath,
  template,
  diagramType: 'sequence',
  meta: sequence.meta,
  svg: renderSvg(),
  cards: sequence.cards,
});
```

## renderers/shared

```

```

## renderers/shared/brand-marks.mjs

```js
import { createHash } from 'node:crypto';
import { lookup } from 'node:dns/promises';
import http from 'node:http';
import https from 'node:https';
import net from 'node:net';
import { BRAND_MARKS } from './generated-brand-marks.mjs';
import { throwDiagnosticError } from './diagnostics.mjs';
import { esc, textUnits } from './utils.mjs';

const COLLECTIONS = Object.freeze({
  architecture: 'components',
  workflow: 'nodes',
  sequence: 'participants',
  dataflow: 'nodes',
  lifecycle: 'states',
});
const MARK_BY_LOOKUP = new Map();
const MARK_BY_DOMAIN = new Map();
const RESOLVED_BY_NODE = new WeakMap();
const RESOLVED_MARK = Symbol('archify.brandMark');
const MAX_HTML_BYTES = 256 * 1024;
const MAX_IMAGE_BYTES = 1024 * 1024;
const MAX_CAPTURE_CONCURRENCY = 3;
const DEFAULT_CAPTURE_TIMEOUT_MS = 8000;
const USER_AGENT = 'Archify/2.15 brand-preview';

function lookupForms(value) {
  const raw = String(value ?? '').trim().toLocaleLowerCase('en-US');
  if (!raw) return [];
  const dashed = raw.replace(/[\s_]+/g, '-');
  const compact = raw.replace(/[\s_.-]+/g, '');
  return [...new Set([raw, dashed, compact])];
}

for (const mark of BRAND_MARKS) {
  for (const value of [mark.id, mark.title, ...mark.aliases]) {
    for (const form of lookupForms(value)) {
      if (!MARK_BY_LOOKUP.has(form)) MARK_BY_LOOKUP.set(form, mark);
    }
  }
  for (const domain of mark.domains) MARK_BY_DOMAIN.set(domain, mark);
}

function asUrl(value) {
  try {
    const url = new URL(String(value));
    return ['https:', 'http:'].includes(url.protocol) ? url : null;
  } catch {
    return null;
  }
}

function domainMark(hostname) {
  const host = hostname.toLocaleLowerCase('en-US').replace(/\.$/, '');
  const candidates = [...MARK_BY_DOMAIN.entries()]
    .filter(([domain]) => host === domain || host.endsWith(`.${domain}`))
    .sort(([left], [right]) => right.length - left.length);
  return candidates[0]?.[1] || null;
}

export function findBrandMark(value) {
  const url = asUrl(value);
  if (url) return domainMark(url.hostname);
  for (const form of lookupForms(value)) {
    const mark = MARK_BY_LOOKUP.get(form);
    if (mark) return mark;
  }
  return null;
}

export function listBrandMarks(query = '') {
  const needle = String(query).trim().toLocaleLowerCase('en-US');
  return BRAND_MARKS.filter((mark) => {
    if (!needle) return true;
    return [mark.id, mark.title, mark.category, ...mark.aliases, ...mark.domains]
      .some((value) => String(value).toLocaleLowerCase('en-US').includes(needle));
  }).map(({ path, ...mark }) => mark);
}

function ipv4Private(address) {
  const parts = address.split('.').map(Number);
  if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;
  const [a, b, c] = parts;
  return a === 0 || a === 10 || a === 127 || a >= 224
    || (a === 100 && b >= 64 && b <= 127)
    || (a === 169 && b === 254)
    || (a === 172 && b >= 16 && b <= 31)
    || (a === 192 && b === 0 && (c === 0 || c === 2))
    || (a === 192 && b === 88 && c === 99)
    || (a === 192 && b === 168)
    || (a === 198 && (b === 18 || b === 19))
    || (a === 198 && b === 51 && c === 100)
    || (a === 203 && b === 0 && c === 113);
}

function ipv6Private(address) {
  const normalized = address.toLocaleLowerCase('en-US').split('%')[0];
  if (normalized === '::' || normalized === '::1') return true;
  if (normalized.startsWith('fc') || normalized.startsWith('fd') || normalized.startsWith('ff') || /^fe[89ab]/.test(normalized)) return true;
  if (normalized.startsWith('64:ff9b:') || normalized.startsWith('100:')
    || normalized.startsWith('2001:db8:') || normalized.startsWith('2002:')) return true;
  const mappedDotted = normalized.match(/::ffff:(\d+\.\d+\.\d+\.\d+)$/);
  if (mappedDotted) return ipv4Private(mappedDotted[1]);
  const mappedHex = normalized.match(/::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
  if (mappedHex) {
    const high = Number.parseInt(mappedHex[1], 16);
    const low = Number.parseInt(mappedHex[2], 16);
    return ipv4Private(`${high >>> 8}.${high & 255}.${low >>> 8}.${low & 255}`);
  }
  const compatibleHex = normalized.match(/^::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
  if (compatibleHex) {
    const high = Number.parseInt(compatibleHex[1], 16);
    const low = Number.parseInt(compatibleHex[2], 16);
    return ipv4Private(`${high >>> 8}.${high & 255}.${low >>> 8}.${low & 255}`);
  }
  return false;
}

export function isPrivateBrandAddress(address) {
  const family = net.isIP(address);
  return family === 4 ? ipv4Private(address) : (family === 6 ? ipv6Private(address) : true);
}

function validateUrlShape(url, allowPrivate = process.env.ARCHIFY_BRAND_ALLOW_PRIVATE === '1') {
  if (!['https:', 'http:'].includes(url.protocol)) throw new Error('only HTTP(S) brand links are supported');
  if (url.username || url.password) throw new Error('brand links cannot contain credentials');
  const expectedPort = url.protocol === 'https:' ? '443' : '80';
  if (!allowPrivate && url.port && url.port !== expectedPort) {
    throw new Error('brand links must use a standard web port');
  }
  const host = url.hostname.toLocaleLowerCase('en-US').replace(/\.$/, '').replace(/^\[|\]$/g, '');
  if (!allowPrivate && (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.local'))) {
    throw new Error('private brand links are not fetched');
  }
  return host;
}

function beforeDeadline(promise, deadline) {
  const remaining = deadline - Date.now();
  if (remaining <= 0) return Promise.reject(new Error('brand capture timed out'));
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error('brand capture timed out')), remaining);
    timer.unref?.();
    promise.then(
      (value) => { clearTimeout(timer); resolve(value); },
      (error) => { clearTimeout(timer); reject(error); },
    );
  });
}

async function resolveRequestTarget(url, deadline) {
  const allowPrivate = process.env.ARCHIFY_BRAND_ALLOW_PRIVATE === '1';
  const host = validateUrlShape(url, allowPrivate);
  const directFamily = net.isIP(host);
  const addresses = directFamily
    ? [{ address: host, family: directFamily }]
    : await beforeDeadline(lookup(host, { all: true, verbatim: true }), deadline);
  if (!addresses.length || (!allowPrivate && addresses.some(({ address }) => isPrivateBrandAddress(address)))) {
    throw new Error('private brand links are not fetched');
  }
  return addresses[0];
}

function timeoutSignal(milliseconds) {
  if (typeof AbortSignal.timeout === 'function') return AbortSignal.timeout(milliseconds);
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), milliseconds);
  timer.unref?.();
  return controller.signal;
}

function captureTimeoutMilliseconds() {
  const configured = Number(process.env.ARCHIFY_BRAND_CAPTURE_TIMEOUT_MS);
  if (!Number.isFinite(configured)) return DEFAULT_CAPTURE_TIMEOUT_MS;
  return Math.max(100, Math.min(30000, Math.round(configured)));
}

function requestPinned(url, accept, target, deadline) {
  return new Promise((resolve, reject) => {
    const transport = url.protocol === 'https:' ? https : http;
    const request = transport.request(url, {
      method: 'GET',
      signal: timeoutSignal(Math.max(1, Math.min(4500, deadline - Date.now()))),
      headers: { accept, 'user-agent': USER_AGENT },
      // Reuse the exact public address that passed validation. This closes the
      // DNS-rebinding gap between checking a hostname and opening its socket.
      lookup(_hostname, options, callback) {
        if (options?.all) callback(null, [target]);
        else callback(null, target.address, target.family);
      },
    }, (response) => {
      const status = response.statusCode || 0;
      resolve({
        status,
        ok: status >= 200 && status < 300,
        headers: {
          get(name) {
            const value = response.headers[String(name).toLocaleLowerCase('en-US')];
            return Array.isArray(value) ? value.join(', ') : (value ?? null);
          },
        },
        body: response,
      });
    });
    request.on('error', reject);
    request.end();
  });
}

async function checkedFetch(input, accept, deadline) {
  let current = new URL(input);
  for (let redirects = 0; redirects <= 3; redirects += 1) {
    if (Date.now() >= deadline) throw new Error('brand capture timed out');
    const target = await resolveRequestTarget(current, deadline);
    const response = await requestPinned(current, accept, target, deadline);
    if ([301, 302, 303, 307, 308].includes(response.status)) {
      const location = response.headers.get('location');
      response.body.resume();
      if (!location || redirects === 3) throw new Error('brand link redirected too many times');
      current = new URL(location, current);
      continue;
    }
    if (!response.ok) {
      response.body.resume();
      throw new Error(`brand link returned HTTP ${response.status}`);
    }
    return { response, finalUrl: current };
  }
  throw new Error('brand link redirected too many times');
}

async function readLimited(response, maximum) {
  const declared = Number(response.headers.get('content-length'));
  if (Number.isFinite(declared) && declared > maximum) {
    response.body?.destroy?.();
    throw new Error('brand asset is too large');
  }
  if (response.body && typeof response.body[Symbol.asyncIterator] === 'function') {
    const chunks = [];
    let total = 0;
    for await (const value of response.body) {
      total += value.byteLength;
      if (total > maximum) {
        response.body.destroy?.();
        throw new Error('brand asset is too large');
      }
      chunks.push(Buffer.from(value));
    }
    return Buffer.concat(chunks, total);
  }
  if (!response.body?.getReader) {
    const buffer = Buffer.from(await response.arrayBuffer());
    if (buffer.length > maximum) throw new Error('brand asset is too large');
    return buffer;
  }
  const reader = response.body.getReader();
  const chunks = [];
  let total = 0;
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    total += value.byteLength;
    if (total > maximum) {
      await reader.cancel();
      throw new Error('brand asset is too large');
    }
    chunks.push(Buffer.from(value));
  }
  return Buffer.concat(chunks, total);
}

function attribute(tag, name) {
  const match = tag.match(new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, 'i'));
  return match ? (match[1] ?? match[2] ?? match[3] ?? '') : '';
}

function iconCandidates(html, pageUrl) {
  const candidates = [];
  for (const match of html.matchAll(/<link\b[^>]*>/gi)) {
    const tag = match[0];
    const rel = attribute(tag, 'rel').toLocaleLowerCase('en-US').split(/\s+/);
    if (!rel.some((value) => value === 'icon' || value === 'apple-touch-icon' || value === 'mask-icon')) continue;
    const href = attribute(tag, 'href');
    if (!href) continue;
    try {
      const url = new URL(href, pageUrl);
      if (!['https:', 'http:'].includes(url.protocol)) continue;
      const type = attribute(tag, 'type').toLocaleLowerCase('en-US');
      const sizes = attribute(tag, 'sizes');
      const area = [...sizes.matchAll(/(\d+)x(\d+)/gi)]
        .reduce((best, size) => Math.max(best, Number(size[1]) * Number(size[2])), 0);
      const score = (type.includes('svg') || /\.svg(?:$|[?#])/i.test(url.href) ? 1000000 : 0)
        + (rel.includes('apple-touch-icon') ? 500000 : 0)
        + area;
      candidates.push({ url, score });
    } catch {
      // A malformed icon candidate is ignored; the deterministic fallback remains available.
    }
  }
  candidates.sort((left, right) => right.score - left.score);
  const fallback = new URL('/favicon.ico', pageUrl);
  const unique = new Map(candidates.map((candidate) => [candidate.url.href, candidate]));
  unique.delete(fallback.href);
  return [...unique.values()].slice(0, 5).concat({ url: fallback, score: -1 });
}

async function imageData(response) {
  const contentType = (response.headers.get('content-type') || '').split(';')[0].trim().toLocaleLowerCase('en-US');
  const allowed = new Set([
    'image/png',
    'image/jpeg',
    'image/webp',
    'image/x-icon',
    'image/vnd.microsoft.icon',
  ]);
  if (!allowed.has(contentType)) {
    response.body?.destroy?.();
    throw new Error(`unsupported brand image type ${contentType || 'unknown'}`);
  }
  const buffer = await readLimited(response, MAX_IMAGE_BYTES);
  const signatureMatches = contentType === 'image/png'
    ? buffer.length >= 45
      && buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
      && buffer.readUInt32BE(8) === 13
      && buffer.toString('ascii', 12, 16) === 'IHDR'
      && buffer.readUInt32BE(16) > 0
      && buffer.readUInt32BE(20) > 0
      && buffer.toString('ascii', buffer.length - 8, buffer.length - 4) === 'IEND'
    : (contentType === 'image/jpeg'
      ? buffer.length >= 20
        && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff
        && buffer.at(-2) === 0xff && buffer.at(-1) === 0xd9
      : (contentType === 'image/webp'
        ? buffer.length >= 16
          && buffer.toString('ascii', 0, 4) === 'RIFF'
          && buffer.toString('ascii', 8, 12) === 'WEBP'
          && buffer.readUInt32LE(4) + 8 <= buffer.length
        : buffer.length >= 22
          && buffer[0] === 0 && buffer[1] === 0 && buffer[2] === 1 && buffer[3] === 0
          && buffer.readUInt16LE(4) > 0
          && 6 + buffer.readUInt16LE(4) * 16 <= buffer.length));
  if (!signatureMatches) throw new Error(`brand asset bytes do not match ${contentType}`);
  return {
    dataUrl: `data:${contentType};base64,${buffer.toString('base64')}`,
    sha256: createHash('sha256').update(buffer).digest('hex'),
    contentType,
  };
}

async function captureRemoteBrand(value, deadline = Date.now() + captureTimeoutMilliseconds()) {
  const sourceUrl = new URL(value);
  const fallback = (reason) => ({
    id: sourceUrl.hostname,
    title: sourceUrl.hostname,
    category: 'link',
    kind: 'fallback',
    status: 'unavailable',
    sourceUrl: sourceUrl.href,
    reason,
  });
  try {
    const page = await checkedFetch(sourceUrl, 'text/html,application/xhtml+xml,image/*;q=0.8', deadline);
    const pageType = (page.response.headers.get('content-type') || '').toLocaleLowerCase('en-US');
    if (pageType.startsWith('image/')) {
      const image = await imageData(page.response);
      return {
        id: sourceUrl.hostname,
        title: sourceUrl.hostname,
        category: 'link',
        kind: 'remote',
        status: 'captured',
        sourceUrl: sourceUrl.href,
        resolvedUrl: page.finalUrl.href,
        ...image,
      };
    }
    if (!pageType.includes('text/html') && !pageType.includes('application/xhtml+xml')) {
      page.response.body?.destroy?.();
      return fallback('linked page is not HTML');
    }
    const html = (await readLimited(page.response, MAX_HTML_BYTES)).toString('utf8');
    const iconErrors = [];
    for (const candidate of iconCandidates(html, page.finalUrl)) {
      try {
        const fetched = await checkedFetch(candidate.url, 'image/*', deadline);
        const image = await imageData(fetched.response);
        return {
          id: sourceUrl.hostname,
          title: sourceUrl.hostname,
          category: 'link',
          kind: 'remote',
          status: 'captured',
          sourceUrl: sourceUrl.href,
          resolvedUrl: fetched.finalUrl.href,
          ...image,
        };
      } catch (error) {
        iconErrors.push(error);
        // Try the next declared favicon before using the generic link mark.
      }
    }
    const usefulError = iconErrors.find((error) => /unsupported brand image type/i.test(error?.message))
      || iconErrors.at(-1);
    return fallback(usefulError?.message || 'no usable site icon was found');
  } catch (error) {
    return fallback(error.message);
  }
}

export async function captureBrandReference(value) {
  const url = asUrl(value);
  if (!url) throw new Error('brand capture requires one HTTP(S) URL');
  validateUrlShape(url);
  const preset = findBrandMark(url.href);
  if (preset) return { brand: preset.id, resolved: { ...preset, kind: 'preset', status: 'preset' } };
  const resolved = await captureRemoteBrand(url.href);
  if (resolved.status !== 'captured' || !resolved.sha256) {
    throw new Error(`brand capture failed: ${resolved.reason || 'no usable site icon was found'}`);
  }
  return {
    brand: { url: url.href, sha256: resolved.sha256 },
    resolved,
  };
}

function remoteBrand(value, cache, deadline) {
  const key = new URL(value).href;
  if (!cache.has(key)) cache.set(key, captureRemoteBrand(key, deadline));
  return cache.get(key);
}

function suggestions(value) {
  const needle = lookupForms(value)[0] || '';
  return BRAND_MARKS.map((mark) => ({
    id: mark.id,
    score: lookupForms(mark.id).some((form) => form.includes(needle) || needle.includes(form)) ? 0 : 1,
  })).sort((left, right) => left.score - right.score || left.id.localeCompare(right.id))
    .slice(0, 5)
    .map((entry) => entry.id);
}

async function mapConcurrent(values, limit, visit) {
  let cursor = 0;
  const workers = Array.from({ length: Math.min(limit, values.length) }, async () => {
    while (cursor < values.length) {
      const index = cursor;
      cursor += 1;
      await visit(values[index], index);
    }
  });
  await Promise.all(workers);
}

export async function prepareDiagramBrandMarks(diagramType, diagram) {
  const collection = COLLECTIONS[diagramType];
  const nodes = collection && Array.isArray(diagram[collection]) ? diagram[collection] : [];
  const unknown = [];
  const remoteByUrl = new Map();
  const deadline = Date.now() + captureTimeoutMilliseconds();
  await mapConcurrent(nodes, MAX_CAPTURE_CONCURRENCY, async (node, index) => {
    if (!node.brand) return;
    if (typeof node.brand === 'object') {
      const url = asUrl(node.brand.url);
      const resolved = url ? await remoteBrand(url.href, remoteByUrl, deadline) : null;
      if (!resolved || resolved.status !== 'captured') {
        unknown.push(`/${collection}/${index}/brand could not reproduce the pinned capture: ${resolved?.reason || 'invalid URL'}`);
        return;
      }
      if (resolved.sha256 !== node.brand.sha256) {
        unknown.push(`/${collection}/${index}/brand digest changed: expected ${node.brand.sha256}, received ${resolved.sha256}`);
        return;
      }
      node[RESOLVED_MARK] = resolved;
      RESOLVED_BY_NODE.set(node, resolved);
      return;
    }
    const preset = findBrandMark(node.brand);
    if (preset) {
      const resolved = { ...preset, kind: 'preset', status: 'preset', sourceUrl: preset.provenance.source };
      node[RESOLVED_MARK] = resolved;
      RESOLVED_BY_NODE.set(node, resolved);
      return;
    }
    const url = asUrl(node.brand);
    if (url) {
      unknown.push(`/${collection}/${index}/brand ${JSON.stringify(node.brand)} is an unpinned URL; capture it first with \`archify brands capture ${url.href} --json\``);
      return;
    }
    unknown.push(`/${collection}/${index}/brand ${JSON.stringify(node.brand)} is not a built-in brand; closest IDs: ${suggestions(node.brand).join(', ')}`);
  });
  if (unknown.length) {
    throwDiagnosticError(`Brand mark validation failed:\n- ${unknown.join('\n- ')}`, unknown.map((message) => ({
      code: message.includes('is an unpinned URL') ? 'brand/unpinned-url'
        : (message.includes('digest changed') ? 'brand/digest-mismatch'
          : (message.includes('could not reproduce') ? 'brand/capture-unavailable' : 'brand/unknown')),
      severity: 'error',
      message,
      subject: { diagramType, collection },
      evidence: {},
      supportedFixes: message.includes('is an unpinned URL')
        ? ['run `archify brands capture <url> --json` and author the returned digest-pinned brand object']
        : ['choose an ID from `archify brands`', 'run `archify brands capture <url> --json` for an unknown official site'],
    })));
  }
}

export function brandMarkFor(node) {
  return node?.[RESOLVED_MARK] || RESOLVED_BY_NODE.get(node) || null;
}

export function brandMetadataFor(node) {
  const mark = brandMarkFor(node);
  return mark ? {
    brand: mark.title,
    brandId: mark.id,
    brandStatus: mark.status,
    brandSource: mark.sourceUrl,
  } : {};
}

export function brandLabelFitWidth(node, width) {
  return brandMarkFor(node) ? Math.max(1, width - 48) : width;
}

export function brandTopRailProblem(node, width, minimumFontSize, subject = 'Node') {
  if (!brandMarkFor(node)) return null;
  const available = width - 48;
  const required = textUnits(node.label) * minimumFontSize * 0.6;
  if (available >= required) return null;
  return `${subject} "${node.id}" brand top rail leaves ${Math.max(0, available)}px for its label, but `
    + `"${node.label}" needs ~${Math.ceil(required)}px at the ${minimumFontSize}px legible minimum — widen the node or shorten the label.`;
}

function markAttrs(mark) {
  return [
    `data-brand-mark="${esc(mark.id)}"`,
    `data-brand-title="${esc(mark.title)}"`,
    `data-brand-status="${esc(mark.status)}"`,
    mark.sourceUrl ? `data-brand-source="${esc(mark.sourceUrl)}"` : '',
    mark.sha256 ? `data-brand-sha256="${esc(mark.sha256)}"` : '',
  ].filter(Boolean).join(' ');
}

export function renderBrandMark(node, { x, y, size = 16 } = {}) {
  const mark = brandMarkFor(node);
  if (!mark) return '';
  const inset = 3;
  let content;
  if (mark.kind === 'preset') {
    const scale = (size - inset * 2) / mark.viewBox;
    content = `<path d="${esc(mark.path)}" transform="translate(${inset} ${inset}) scale(${scale})" fill="#${esc(mark.hex)}"/>`;
  } else if (mark.kind === 'remote') {
    content = `<image href="${esc(mark.dataUrl)}" x="${inset}" y="${inset}" width="${size - inset * 2}" height="${size - inset * 2}" preserveAspectRatio="xMidYMid meet"/>`;
  } else {
    const scale = size / 20;
    content = `<g transform="scale(${scale})" class="brand-mark-fallback"><circle cx="10" cy="10" r="5.2"/><path d="M4.8 10h10.4M10 4.8c1.6 1.6 2.4 3.3 2.4 5.2s-.8 3.6-2.4 5.2M10 4.8C8.4 6.4 7.6 8.1 7.6 10s.8 3.6 2.4 5.2"/></g>`;
  }
  return `<g aria-hidden="true" ${markAttrs(mark)} class="brand-mark" transform="translate(${x} ${y})">
            <rect width="${size}" height="${size}" rx="4" class="brand-mark-badge"/>
            ${content}
            <rect width="${size}" height="${size}" rx="4" class="brand-mark-frame"/>
          </g>`;
}
```

## renderers/shared/cli.mjs

```js
import fs from 'node:fs';
import path from 'node:path';
import { applyTemplate, renderCards, esc } from './utils.mjs';
import { validateSchema } from './validator.mjs';
import { verifyRepositoryEvidence } from './repository-evidence.mjs';
import { installRendererDiagnosticBoundary, throwDiagnosticProblems } from './diagnostics.mjs';
import { validateEngineeringProfile } from './engineering-profiles.mjs';
import { resolveOutputPath } from './output-path.mjs';
import { prepareDiagramBrandMarks } from './brand-marks.mjs';
import { resolveLocale, translateMessage } from './i18n.mjs';

installRendererDiagnosticBoundary();

const outputPathGuards = new Map();

// Common CLI head: node render-<type>.mjs [input.json] [output.html]
// Keep this synchronous because callers also use it to establish the guarded
// output path before testing a last-moment filesystem alias change.
export function loadDiagram({ rendererDir, diagramType, defaultExample, argv = process.argv }) {
  const skillRoot = path.resolve(rendererDir, '../..');
  const inputPath = path.resolve(argv[2] || path.join(skillRoot, 'examples', defaultExample));
  const diagram = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
  validateSchema(diagramType, diagram);
  validateGuidedViews(diagramType, diagram);
  validateRelationshipIds(diagramType, diagram);
  validateEngineeringProfile(diagramType, diagram);
  const sourceEvidence = verifyRepositoryEvidence(diagramType, diagram, process.env.ARCHIFY_REPO_ROOT);
  const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8');
  const outputRequest = {
    requestedOutput: argv[3],
    authoredOutput: diagram.meta?.output,
    defaultOutput: `${diagramType}.html`,
    inputPaths: [inputPath],
    cwd: process.cwd(),
  };
  const { outputPath: outPath } = resolveOutputPath(outputRequest);
  outputPathGuards.set(outPath, outputRequest);
  return { diagram, template, outPath, sourceEvidence };
}

// Brand URL capture is the only asynchronous authoring step. Typed renderers
// opt into it through this wrapper without changing loadDiagram's long-lived
// synchronous safety contract.
export async function loadDiagramWithBrandMarks(options) {
  const loaded = loadDiagram(options);
  await prepareDiagramBrandMarks(options.diagramType, loaded.diagram);
  return loaded;
}

const START_TYPES = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']);

// Common CLI tail: fill the template and write the standalone HTML file.
export function writeDiagram({ outPath, template, diagramType, meta, svg, cards, sourceEvidence = null }) {
  if (!START_TYPES.has(diagramType)) throw new Error(`writeDiagram: unknown diagram type ${JSON.stringify(diagramType)}`);
  const outputGuard = outputPathGuards.get(outPath);
  if (outputGuard) resolveOutputPath(outputGuard);
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
  fs.writeFileSync(outPath, applyTemplate(template, {
    title: meta.title,
    subtitle: meta.subtitle,
    svg,
    cards: renderCards(cards),
    locale: meta.locale,
    visualPreset: meta.visual_preset || 'classic',
    guidedViews: meta.views || [],
    sourceEvidence,
  }));
  outputPathGuards.delete(outPath);
  console.log(outPath);
}

const SEMANTIC_COLLECTIONS = {
  architecture: 'components',
  workflow: 'nodes',
  sequence: 'participants',
  dataflow: 'nodes',
  lifecycle: 'states',
};

const RELATIONSHIP_COLLECTIONS = {
  architecture: 'connections',
  workflow: 'edges',
  sequence: 'messages',
  dataflow: 'flows',
  lifecycle: 'transitions',
};

// Relationship IDs are optional for backwards compatibility, but once an
// author supplies one it becomes the durable identity used by viewer links.
// Keep uniqueness enforcement in the shared zero-install path so every typed
// renderer fails the same way even when development dependencies are absent.
export function validateRelationshipIds(diagramType, diagram) {
  const collection = RELATIONSHIP_COLLECTIONS[diagramType];
  const relationships = collection && Array.isArray(diagram[collection]) ? diagram[collection] : [];
  const seen = new Set();
  const problems = [];

  relationships.forEach((relationship, index) => {
    if (relationship.id === undefined || relationship.id === null || relationship.id === '') return;
    if (seen.has(relationship.id)) {
      problems.push(`/${collection}/${index}/id duplicates relationship id ${JSON.stringify(relationship.id)}`);
    }
    seen.add(relationship.id);
  });

  if (problems.length) {
    throwDiagnosticProblems('Relationship identity validation failed', problems, {
      code: 'relationship/duplicate-id',
      subject: { diagramType, collection },
    });
  }
}

// JSON Schema keeps the view object bounded; this pass checks facts that span
// collections. Keeping it here makes the same contract apply to all five
// renderers, including the zero-install standalone-validator path.
export function validateGuidedViews(diagramType, diagram) {
  const views = diagram.meta?.views;
  if (!Array.isArray(views) || views.length === 0) return;
  const collection = SEMANTIC_COLLECTIONS[diagramType];
  const semanticIds = new Set((diagram[collection] || []).map((item) => item.id));
  const seen = new Set();
  const problems = [];

  views.forEach((view, index) => {
    if (seen.has(view.id)) problems.push(`/meta/views/${index}/id duplicates view id ${JSON.stringify(view.id)}`);
    seen.add(view.id);
    const seenFocus = new Set();
    (view.focus || []).forEach((id, focusIndex) => {
      if (seenFocus.has(id)) {
        problems.push(`/meta/views/${index}/focus/${focusIndex} duplicates semantic id ${JSON.stringify(id)}`);
      }
      seenFocus.add(id);
      if (!semanticIds.has(id)) {
        problems.push(`/meta/views/${index}/focus/${focusIndex} references unknown semantic id ${JSON.stringify(id)}`);
      }
    });
  });

  if (problems.length) {
    throwDiagnosticProblems('Guided view validation failed', problems, {
      code: 'guided-view/invalid',
      subject: { diagramType, collection: 'meta.views' },
    });
  }
}

// Accessible name for the generated diagram SVG.
export function svgRootAttrs(meta) {
  const animation = meta.animation === 'trace' ? ' data-animation="trace"' : '';
  const preset = ` data-preset="${esc(meta.visual_preset || 'classic')}"`;
  const engineeringProfile = meta.engineering_profile
    ? ` data-engineering-profile="${esc(meta.engineering_profile)}"`
    : '';
  const requestedProfile = process.env.ARCHIFY_QUALITY_PROFILE || meta.quality_profile;
  const qualityProfile = requestedProfile === 'showcase' ? 'showcase' : 'standard';
  const advisory = requestedProfile ? '' : ' data-quality-gates="advisory"';
  return `role="img" lang="${esc(resolveLocale(meta.locale))}" aria-labelledby="archify-diagram-title archify-diagram-description"${animation}${preset}${engineeringProfile} data-quality-profile="${esc(qualityProfile)}"${advisory}`;
}

// Keep the accessible name inside the SVG so it survives standalone SVG
// export and embedding. The fixed IDs are deterministic because an Archify
// artifact intentionally contains one primary diagram SVG.
export function svgAccessibleText(meta, kind) {
  const description = meta.subtitle || translateMessage(meta.locale, `diagram.description.${kind}`);
  return `        <title id="archify-diagram-title">${esc(meta.title)}</title>\n        <desc id="archify-diagram-description">${esc(description)}</desc>`;
}

export function animateAttr(meta, kind, step) {
  if (meta.animation !== 'trace') return '';
  // Ambient trace must finish inside the fixed six-second WebM capture. The
  // cap affects visual delay only; authored order and semantic identity stay
  // untouched in the JSON, DOM, Story, and relationship contracts.
  const safeStep = Number.isFinite(step) && step >= 0 ? Math.min(12, Math.floor(step)) : 0;
  return ` data-animate="${kind}" style="--step:${safeStep}"`;
}

// Stable semantic hooks for the standalone HTML explorer. IDs already pass
// the schema's conservative identifier pattern; escape again at the markup
// boundary so these helpers remain safe if that contract expands later.
export function focusNodeAttrs(id, label, metadata = {}, locale) {
  const optional = [
    ['data-node-kind', metadata.kind],
    ['data-node-sublabel', metadata.sublabel],
    ['data-node-tag', metadata.tag],
    ['data-node-context', metadata.context],
    ['data-node-brand', metadata.brand],
    ['data-node-brand-id', metadata.brandId],
    ['data-node-brand-status', metadata.brandStatus],
    ['data-node-brand-source', metadata.brandSource],
  ].filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== '')
    .map(([name, value]) => ` ${name}="${esc(String(value))}"`)
    .join('');
  const detail = [metadata.sublabel, metadata.context, metadata.brand]
    .filter((value) => value !== undefined && value !== null && String(value).trim() !== '')
    .join(', ');
  const aria = detail
    ? translateMessage(locale, 'node.focus.detail', { label, detail })
    : translateMessage(locale, 'node.focus', { label });
  return `id="node-${esc(id)}" data-node-id="${esc(id)}" data-node-label="${esc(label)}" tabindex="0" role="button" aria-label="${esc(aria)}" aria-pressed="false"${optional}`;
}

// Native SVG titles preserve a compact details-on-demand fallback when the
// canonical SVG is embedded inline outside the full Archify viewer.
export function focusNodeTitle(label, metadata = {}) {
  const parts = [label, metadata.sublabel, metadata.context, metadata.tag, metadata.brand]
    .filter((value) => value !== undefined && value !== null && String(value).trim() !== '');
  return `<title>${esc(parts.join(' · '))}</title>`;
}

export function focusEdgeAttrs(from, to, label, key, id) {
  const named = label ? ` data-edge-label="${esc(label)}"` : '';
  const keyed = key !== undefined && key !== null ? ` data-edge-key="${esc(String(key))}"` : '';
  const identified = id !== undefined && id !== null && String(id).trim() !== ''
    ? ` data-edge-id="${esc(String(id))}"`
    : '';
  return `data-edge-from="${esc(from)}" data-edge-to="${esc(to)}"${named}${keyed}${identified}`;
}
```

## renderers/shared/desktop-readability.mjs

```js
export const DESKTOP_READABILITY_VIEWPORT = Object.freeze({ width: 1440, height: 900 });
export const DESKTOP_READER_MIN_WIDTH = 960;
export const DESKTOP_READER_HORIZONTAL_CHROME = 30;
export const DESKTOP_READER_DIAGRAM_WIDTH = DESKTOP_READER_MIN_WIDTH - DESKTOP_READER_HORIZONTAL_CHROME;
export const MIN_PROJECTED_NODE_TEXT_PX = 6;

export function projectedNodeTextPx(sourceFontPx, viewBoxWidth, diagramWidth = DESKTOP_READER_DIAGRAM_WIDTH) {
  if (![sourceFontPx, viewBoxWidth, diagramWidth].every(Number.isFinite) || viewBoxWidth <= 0 || diagramWidth <= 0) {
    return Number.NaN;
  }
  return sourceFontPx * Math.min(1, diagramWidth / viewBoxWidth);
}

export function minimumReadableSourceTextPx(
  viewBoxWidth,
  diagramWidth = DESKTOP_READER_DIAGRAM_WIDTH,
  minimumProjectedPx = MIN_PROJECTED_NODE_TEXT_PX,
) {
  if (![viewBoxWidth, diagramWidth, minimumProjectedPx].every(Number.isFinite)
    || viewBoxWidth <= 0
    || diagramWidth <= 0
    || minimumProjectedPx <= 0) {
    return Number.NaN;
  }
  return minimumProjectedPx / Math.min(1, diagramWidth / viewBoxWidth);
}
```

## renderers/shared/diagnostics.mjs

```js
import fs from 'node:fs';
import path from 'node:path';

const DIAGNOSTIC_MODE = process.env.ARCHIFY_DIAGNOSTIC_FORMAT === 'json';
const recorded = [];
const recordedMessages = new Set();
const boundaryKey = Symbol.for('archify.renderer-diagnostic-boundary');
let recordingSuppressionDepth = 0;

function plainObject(value) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
  return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
}

function normalizedDiagnostic(diagnostic) {
  const message = String(diagnostic?.message || 'Archify could not classify this failure.').trim();
  return {
    code: String(diagnostic?.code || 'internal/unclassified'),
    severity: diagnostic?.severity === 'warning' ? 'warning' : 'error',
    message,
    subject: plainObject(diagnostic?.subject),
    evidence: plainObject(diagnostic?.evidence),
    supportedFixes: Array.isArray(diagnostic?.supportedFixes)
      ? [...new Set(diagnostic.supportedFixes.map((fix) => String(fix).trim()).filter(Boolean))]
      : [],
    ...(Array.isArray(diagnostic?.suppresses) ? {
      suppresses: [...new Set(diagnostic.suppresses.map((code) => String(code).trim()).filter(Boolean))],
    } : {}),
  };
}

export function recordDiagnostic(diagnostic) {
  if (!DIAGNOSTIC_MODE || recordingSuppressionDepth > 0) return;
  const normalized = normalizedDiagnostic(diagnostic);
  if (recordedMessages.has(normalized.message)) return;
  recordedMessages.add(normalized.message);
  recorded.push(normalized);
}

export function withDiagnosticRecordingSuppressed(callback) {
  recordingSuppressionDepth += 1;
  try {
    return callback();
  } finally {
    recordingSuppressionDepth -= 1;
  }
}

export function throwDiagnosticError(message, diagnostics) {
  for (const diagnostic of diagnostics || []) recordDiagnostic(diagnostic);
  const error = new Error(message);
  error.archifyDiagnostics = (diagnostics || []).map(normalizedDiagnostic);
  throw error;
}

export function throwDiagnosticProblems(prefix, problems, { code = 'layout/constraint', subject = {} } = {}) {
  const messages = (problems || []).map((problem) => String(problem));
  const diagnostics = messages.map((message) => normalizedDiagnostic({
      code,
      severity: 'error',
      message,
      subject,
      evidence: {},
      supportedFixes: [],
    }));
  throwDiagnosticError(`${prefix}:\n- ${messages.join('\n- ')}`, diagnostics);
}

function fallbackDiagnostic(error) {
  const input = process.argv[2] ? path.resolve(process.argv[2]) : undefined;
  if (error instanceof SyntaxError) {
    return normalizedDiagnostic({
      code: 'input/json-parse',
      severity: 'error',
      message: `Input JSON could not be parsed: ${error.message}`,
      subject: { input },
      evidence: { reason: error.message },
      supportedFixes: ['repair the JSON syntax and run validation again'],
    });
  }
  if (error?.code === 'ENOENT' || error?.code === 'EACCES' || error?.code === 'EISDIR') {
    return normalizedDiagnostic({
      code: 'input/read',
      severity: 'error',
      message: `Input could not be read: ${error.message}`,
      subject: { input },
      evidence: { systemCode: error.code, reason: error.message },
      supportedFixes: ['provide one readable JSON input file'],
    });
  }
  return normalizedDiagnostic({
    code: 'internal/unclassified',
    severity: 'error',
    message: error?.message || 'Renderer failed without a diagnostic.',
    subject: { input },
    evidence: { errorName: error?.name || 'Error' },
    supportedFixes: [],
  });
}
function rendererFailure(error) {
  const attached = Array.isArray(error?.archifyDiagnostics)
    ? error.archifyDiagnostics.map(normalizedDiagnostic)
    : [];
  const diagnostics = recorded.length ? recorded : (attached.length ? attached : [fallbackDiagnostic(error)]);
  return {
    schemaVersion: 1,
    ok: false,
    source: 'renderer',
    error: error?.message || 'Renderer failed without a diagnostic.',
    diagnostics,
  };
}

export function installRendererDiagnosticBoundary() {
  if (!DIAGNOSTIC_MODE || globalThis[boundaryKey]) return;
  globalThis[boundaryKey] = true;
  process.on('uncaughtException', (error) => {
    const payload = `${JSON.stringify(rendererFailure(error))}\n`;
    try {
      fs.writeSync(process.stderr.fd, payload);
    } catch {
      // The renderer is already failing. Avoid replacing its real error with a
      // secondary stream failure; the parent CLI still has the exit status.
    }
    process.exit(1);
  });
}
```

## renderers/shared/engineering-profiles.mjs

```js
import { throwDiagnosticError } from './diagnostics.mjs';

const DEPLOYMENT_PROFILE = 'deployment-ownership';
const DEPLOYMENT_BOUNDARY_KINDS = new Set(['region', 'security-group']);
const PRIVATE_STATE_TYPES = new Set(['database']);

function subject(collection, index, item = {}) {
  return {
    diagramType: 'architecture',
    profile: DEPLOYMENT_PROFILE,
    collection,
    index,
    ...(item.id ? { id: item.id } : {}),
  };
}

function membership(boundaries, componentId, kind) {
  return boundaries
    .map((boundary, index) => ({ boundary, index }))
    .filter(({ boundary }) => boundary.kind === kind && boundary.wraps.includes(componentId));
}

export function deploymentOwnershipDiagnostics(diagram) {
  const components = Array.isArray(diagram.components) ? diagram.components : [];
  const boundaries = (Array.isArray(diagram.boundaries) ? diagram.boundaries : [])
    .map((boundary) => ({ ...boundary, wraps: Array.isArray(boundary.wraps) ? boundary.wraps : [] }));
  const connections = Array.isArray(diagram.connections) ? diagram.connections : [];
  const diagnostics = [];

  for (const kind of DEPLOYMENT_BOUNDARY_KINDS) {
    const count = boundaries.filter((boundary) => boundary.kind === kind).length;
    if (count > 0) continue;
    diagnostics.push({
      code: 'engineering/deployment-boundary-kind',
      severity: 'error',
      message: `Deployment ownership requires at least one ${kind} boundary.`,
      subject: subject('boundaries', -1),
      evidence: { requiredKind: kind, found: count },
      supportedFixes: [`add one ${kind} boundary with an explicit wraps list`],
    });
  }

  components.forEach((component, index) => {
    if (component.type === 'external') return;
    if (typeof component.tag !== 'string' || component.tag.trim() === '') {
      diagnostics.push({
        code: 'engineering/deployment-owner-missing',
        severity: 'error',
        message: `Deployment component ${JSON.stringify(component.id)} does not name its owner in tag.`,
        subject: subject('components', index, component),
        evidence: { componentType: component.type, ownerField: 'tag' },
        supportedFixes: [`set /components/${index}/tag to the responsible team or owner`],
      });
    }

    const regions = membership(boundaries, component.id, 'region');
    if (regions.length === 0) {
      diagnostics.push({
        code: 'engineering/deployment-region-scope',
        severity: 'error',
        message: `Deployment component ${JSON.stringify(component.id)} is not assigned to a region boundary.`,
        subject: subject('components', index, component),
        evidence: { componentType: component.type, regionMemberships: 0 },
        supportedFixes: ['add the component id to the real region boundary wraps list'],
      });
    } else if (regions.length > 1) {
      diagnostics.push({
        code: 'engineering/deployment-region-ambiguous',
        severity: 'error',
        message: `Deployment component ${JSON.stringify(component.id)} belongs to more than one region boundary.`,
        subject: subject('components', index, component),
        evidence: {
          componentType: component.type,
          regions: regions.map(({ boundary, index: boundaryIndex }) => ({ boundaryIndex, label: boundary.label })),
        },
        supportedFixes: ['keep the component id in exactly one real region boundary wraps list'],
      });
    }

    if (PRIVATE_STATE_TYPES.has(component.type)) {
      const privateScopes = membership(boundaries, component.id, 'security-group');
      if (privateScopes.length === 0) {
        diagnostics.push({
          code: 'engineering/deployment-private-state',
          severity: 'error',
          message: `Stateful component ${JSON.stringify(component.id)} is not assigned to a private security-group boundary.`,
          subject: subject('components', index, component),
          evidence: { componentType: component.type, privateMemberships: 0 },
          supportedFixes: ['add the component id to the real private security-group boundary wraps list'],
        });
      }
    }
  });

  boundaries.forEach((boundary, index) => {
    if (boundary.kind !== 'security-group') return;
    const members = boundary.wraps.map((id) => ({
      id,
      regions: membership(boundaries, id, 'region').map(({ boundary: region, index: boundaryIndex }) => ({
        boundaryIndex,
        label: region.label,
      })),
    }));
    const regionIndexes = new Set(members.flatMap((member) => member.regions.map((region) => region.boundaryIndex)));
    const consistent = members.length > 0
      && members.every((member) => member.regions.length === 1)
      && regionIndexes.size === 1;
    if (consistent) return;
    diagnostics.push({
      code: 'engineering/deployment-private-region-consistency',
      severity: 'error',
      message: `Private boundary ${JSON.stringify(boundary.label)} must contain components from exactly one shared region.`,
      subject: subject('boundaries', index, boundary),
      evidence: { boundaryKind: boundary.kind, members },
      supportedFixes: ['assign every private-boundary component to exactly one shared region boundary'],
    });
  });

  connections.forEach((connection, index) => {
    const crossedBoundaries = boundaries
      .map((boundary, boundaryIndex) => ({
        boundaryIndex,
        kind: boundary.kind,
        label: boundary.label,
        fromInside: boundary.wraps.includes(connection.from),
        toInside: boundary.wraps.includes(connection.to),
      }))
      .filter((boundary) => DEPLOYMENT_BOUNDARY_KINDS.has(boundary.kind) && boundary.fromInside !== boundary.toInside);
    if (crossedBoundaries.length === 0 || (typeof connection.label === 'string' && connection.label.trim() !== '')) return;
    diagnostics.push({
      code: 'engineering/deployment-crossing-mechanism',
      severity: 'error',
      message: `Cross-boundary connection ${JSON.stringify(connection.id || `${connection.from}->${connection.to}`)} does not name its mechanism.`,
      subject: subject('connections', index, connection),
      evidence: {
        from: connection.from,
        to: connection.to,
        crossedBoundaries: crossedBoundaries.map(({ boundaryIndex, kind, label }) => ({ boundaryIndex, kind, label })),
      },
      supportedFixes: [`set /connections/${index}/label to the real cross-boundary mechanism`],
    });
  });

  return diagnostics;
}

export function validateEngineeringProfile(diagramType, diagram) {
  const profile = diagram.meta?.engineering_profile;
  if (!profile) return;
  if (diagramType !== 'architecture' || profile !== DEPLOYMENT_PROFILE) return;
  const diagnostics = deploymentOwnershipDiagnostics(diagram);
  if (!diagnostics.length) return;
  throwDiagnosticError(
    `Engineering profile ${JSON.stringify(profile)} failed:\n${diagnostics.map((entry) => `- ${entry.message}`).join('\n')}`,
    diagnostics,
  );
}
```

## renderers/shared/generated-brand-marks.mjs

```js
// Generated by scripts/generate-brand-marks.mjs from brand-marks/catalog.json.
// Simple Icons 16.28.0. Do not edit by hand.
export const BRAND_MARKS = Object.freeze([
  {
    "id": "airtable",
    "title": "Airtable",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "airtable.com"
    ],
    "viewBox": 24,
    "hex": "18BFFF",
    "path": "M11.992 1.966c-.434 0-.87.086-1.28.257L1.779 5.917c-.503.208-.49.908.012 1.116l8.982 3.558a3.266 3.266 0 0 0 2.454 0l8.982-3.558c.503-.196.503-.908.012-1.116l-8.957-3.694a3.255 3.255 0 0 0-1.272-.257zM23.4 8.056a.589.589 0 0 0-.222.045l-10.012 3.877a.612.612 0 0 0-.38.564v8.896a.6.6 0 0 0 .821.552L23.62 18.1a.583.583 0 0 0 .38-.551V8.653a.6.6 0 0 0-.6-.596zM.676 8.095a.644.644 0 0 0-.48.19C.086 8.396 0 8.53 0 8.69v8.355c0 .442.515.737.908.54l6.27-3.006.307-.147 2.969-1.436c.466-.22.43-.908-.061-1.092L.883 8.138a.57.57 0 0 0-.207-.044z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://airtable.com/newsroom"
    }
  },
  {
    "id": "alibaba-cloud",
    "title": "Alibaba Cloud",
    "category": "cloud",
    "aliases": [
      "aliyun"
    ],
    "domains": [
      "alibabacloud.com",
      "aliyun.com"
    ],
    "viewBox": 24,
    "hex": "FF6A00",
    "path": "M3.996 4.517h5.291L8.01 6.324 4.153 7.506a1.668 1.668 0 0 0-1.165 1.601v5.786a1.668 1.668 0 0 0 1.165 1.6l3.857 1.183 1.277 1.807H3.996A3.996 3.996 0 0 1 0 15.487V8.513a3.996 3.996 0 0 1 3.996-3.996m16.008 0h-5.291l1.277 1.807 3.857 1.182c.715.227 1.17.889 1.165 1.601v5.786a1.668 1.668 0 0 1-1.165 1.6l-3.857 1.183-1.277 1.807h5.291A3.996 3.996 0 0 0 24 15.487V8.513a3.996 3.996 0 0 0-3.996-3.996m-4.007 8.345H8.002v-1.804h7.995Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.alibabagroup.com/en/ir/reports"
    }
  },
  {
    "id": "angular",
    "title": "Angular",
    "category": "framework",
    "aliases": [],
    "domains": [
      "angular.dev"
    ],
    "viewBox": 24,
    "hex": "0F0F11",
    "path": "M16.712 17.711H7.288l-1.204 2.916L12 24l5.916-3.373-1.204-2.916ZM14.692 0l7.832 16.855.814-12.856L14.692 0ZM9.308 0 .662 3.999l.814 12.856L9.308 0Zm-.405 13.93h6.198L12 6.396 8.903 13.93Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://angular.dev/press-kit",
      "guidelines": "https://angular.dev/press-kit",
      "license": {
        "type": "CC-BY-4.0",
        "url": "https://spdx.org/licenses/CC-BY-4.0"
      }
    }
  },
  {
    "id": "ansible",
    "title": "Ansible",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "ansible.com"
    ],
    "viewBox": 24,
    "hex": "EE0000",
    "path": "M10.617 11.473l4.686 3.695-3.102-7.662zM12 0C5.371 0 0 5.371 0 12s5.371 12 12 12 12-5.371 12-12S18.629 0 12 0zm5.797 17.305c-.011.471-.403.842-.875.83-.236 0-.416-.09-.664-.293l-6.19-5-2.079 5.203H6.191L11.438 5.44c.124-.314.427-.52.764-.506.326-.014.63.189.742.506l4.774 11.494c.045.111.08.234.08.348-.001.009-.001.009-.001.023z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.ansible.com/logos"
    }
  },
  {
    "id": "anthropic",
    "title": "Anthropic",
    "category": "ai",
    "aliases": [],
    "domains": [
      "anthropic.com"
    ],
    "viewBox": 24,
    "hex": "191919",
    "path": "M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.anthropic.com"
    }
  },
  {
    "id": "apache-airflow",
    "title": "Apache Airflow",
    "category": "data",
    "aliases": [
      "airflow"
    ],
    "domains": [
      "airflow.apache.org"
    ],
    "viewBox": 24,
    "hex": "017CEE",
    "path": "M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495zm-.409-9.476c.04.115.05.24.031.344-.17.96-1.593 2.538-4.304 3.87a.597.597 0 0 0-.08-.079c1.432-3.155 1.828-5.61 1.175-7.322l3.058 2.984.12.203zm-.131 9.44a.73.73 0 0 1-.347.031c-.96-.171-2.537-1.594-3.87-4.307a.656.656 0 0 0 .08-.078l-.001.001c3.155 1.432 5.61 1.83 7.324 1.174l-2.969 3.043M23.568.392a.05.05 0 0 1 .052-.011c.018.006.03.024.029.043-.065 4.655-.437 9.726-2.703 12.05-1.53 1.565-4.326 1.419-8.283-.377.006-.037.021-.07.02-.108 0-.044-.017-.082-.026-.123 2.83-1.39 4.315-3.037 4.506-4.115.057-.322-.009-.542-.102-.688l6.507-6.67V.392zM.393.43A.045.045 0 0 1 .382.38C.39.36.403.343.425.35c4.655.065 9.727.438 12.05 2.703l.002.002c1.56 1.527 1.415 4.323-.379 8.28-.033-.005-.062-.02-.097-.02h-.008c-.045.001-.084.019-.126.027-1.39-2.83-3.037-4.314-4.115-4.506-.323-.057-.542.01-.688.103L.393.43zm11.94 11.563a.331.331 0 0 1-.327.335H12a.332.332 0 0 1-.004-.661c.172.016.333.144.335.326h.002zm-5.12 4.661a.722.722 0 0 1-.03-.345c.17-.96 1.595-2.54 4.309-3.873.013.016.019.035.033.05.013.012.03.017.044.028-1.434 3.158-1.83 5.613-1.177 7.326l-3.041-2.967m-.006-9.659a.735.735 0 0 1 .345-.031c.961.17 2.54 1.594 3.871 4.306a.597.597 0 0 0-.079.08c-2.167-.983-4.007-1.484-5.498-1.484-.68 0-1.289.103-1.825.308L7.128 7.35M.43 23.607c-.018.018-.038.015-.052.01-.019-.007-.028-.021-.028-.043.065-4.654.437-9.725 2.703-12.049 1.527-1.565 4.325-1.419 8.286.378-.006.035-.02.067-.02.104 0 .043.018.083.026.124-2.831 1.391-4.317 3.04-4.51 4.117-.057.322.01.542.103.688L.43 23.607zm23.144.042c-4.655-.065-9.726-.437-12.05-2.703l-.005-.006c-1.56-1.526-1.412-4.322.383-8.279.033.005.064.02.098.02h.009c.043 0 .08-.018.122-.027 1.39 2.832 3.036 4.317 4.115 4.51.083.014.16.021.23.021a.776.776 0 0 0 .45-.133l6.68 6.516c.02.02.016.04.01.052a.042.042 0 0 1-.042.029z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://apache.org/logos",
      "guidelines": "https://www.apache.org/foundation/marks",
      "license": {
        "type": "Apache-2.0",
        "url": "https://spdx.org/licenses/Apache-2.0"
      }
    }
  },
  {
    "id": "apache-kafka",
    "title": "Apache Kafka",
    "category": "data",
    "aliases": [
      "kafka"
    ],
    "domains": [
      "kafka.apache.org"
    ],
    "viewBox": 24,
    "hex": "231F20",
    "path": "M9.71 2.136a1.43 1.43 0 0 0-2.047 0h-.007a1.48 1.48 0 0 0-.421 1.042c0 .41.161.777.422 1.039l.007.007c.257.264.616.426 1.019.426.404 0 .766-.162 1.027-.426l.003-.007c.261-.262.421-.629.421-1.039 0-.408-.159-.777-.421-1.042H9.71zM8.683 22.295c.404 0 .766-.167 1.027-.429l.003-.008c.261-.261.421-.631.421-1.036 0-.41-.159-.778-.421-1.044H9.71a1.42 1.42 0 0 0-1.027-.432 1.4 1.4 0 0 0-1.02.432h-.007c-.26.266-.422.634-.422 1.044 0 .406.161.775.422 1.036l.007.008c.258.262.617.429 1.02.429zm7.89-4.462c.359-.096.683-.33.882-.684l.027-.052a1.47 1.47 0 0 0 .114-1.067 1.454 1.454 0 0 0-.675-.896l-.021-.014a1.425 1.425 0 0 0-1.078-.132c-.36.091-.684.335-.881.686-.2.349-.241.75-.146 1.119.099.363.33.691.675.896h.002c.346.203.737.239 1.101.144zm-6.405-7.342a2.083 2.083 0 0 0-1.485-.627c-.58 0-1.103.242-1.482.627-.378.385-.612.916-.612 1.507s.233 1.124.612 1.514a2.08 2.08 0 0 0 2.967 0c.379-.39.612-.923.612-1.514s-.233-1.122-.612-1.507zm-.835-2.51c.843.141 1.6.552 2.178 1.144h.004c.092.093.182.196.265.299l1.446-.851a3.176 3.176 0 0 1-.047-1.808 3.149 3.149 0 0 1 1.456-1.926l.025-.016a3.062 3.062 0 0 1 2.345-.306c.77.21 1.465.721 1.898 1.482v.002c.431.757.518 1.626.313 2.408a3.145 3.145 0 0 1-1.456 1.928l-.198.118h-.02a3.095 3.095 0 0 1-2.154.201 3.127 3.127 0 0 1-1.514-.944l-1.444.848a4.162 4.162 0 0 1 0 2.879l1.444.846c.413-.47.939-.789 1.514-.944a3.041 3.041 0 0 1 2.371.319l.048.023v.002a3.17 3.17 0 0 1 1.408 1.906 3.215 3.215 0 0 1-.313 2.405l-.026.053-.003-.005a3.147 3.147 0 0 1-1.867 1.436 3.096 3.096 0 0 1-2.371-.318v-.006a3.156 3.156 0 0 1-1.456-1.927 3.175 3.175 0 0 1 .047-1.805l-1.446-.848a3.905 3.905 0 0 1-.265.294l-.004.005a3.938 3.938 0 0 1-2.178 1.138v1.699a3.09 3.09 0 0 1 1.56.862l.002.004c.565.572.914 1.368.914 2.243 0 .873-.35 1.664-.914 2.239l-.002.009a3.1 3.1 0 0 1-2.21.931 3.1 3.1 0 0 1-2.206-.93h-.002v-.009a3.186 3.186 0 0 1-.916-2.239c0-.875.35-1.672.916-2.243v-.004h.002a3.1 3.1 0 0 1 1.558-.862v-1.699a3.926 3.926 0 0 1-2.176-1.138l-.006-.005a4.098 4.098 0 0 1-1.173-2.874c0-1.122.452-2.136 1.173-2.872h.006a3.947 3.947 0 0 1 2.176-1.144V6.289a3.137 3.137 0 0 1-1.558-.864h-.002v-.004a3.192 3.192 0 0 1-.916-2.243c0-.871.35-1.669.916-2.243l.002-.002A3.084 3.084 0 0 1 8.683 0c.861 0 1.641.355 2.21.932v.002h.002c.565.574.914 1.372.914 2.243 0 .876-.35 1.667-.914 2.243l-.002.005a3.142 3.142 0 0 1-1.56.864v1.692zm8.121-1.129l-.012-.019a1.452 1.452 0 0 0-.87-.668 1.43 1.43 0 0 0-1.103.146h.002c-.347.2-.58.529-.677.896-.095.365-.054.768.146 1.119l.007.009c.2.347.519.579.874.673.357.103.755.059 1.098-.144l.019-.009a1.47 1.47 0 0 0 .657-.885 1.493 1.493 0 0 0-.141-1.118",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://apache.org/logos",
      "guidelines": "https://www.apache.org/foundation/marks",
      "license": {
        "type": "Apache-2.0",
        "url": "https://spdx.org/licenses/Apache-2.0"
      }
    }
  },
  {
    "id": "argo",
    "title": "Argo",
    "category": "engineering",
    "aliases": [
      "argocd",
      "argo-cd"
    ],
    "domains": [
      "argoproj.github.io"
    ],
    "viewBox": 24,
    "hex": "EF7B4D",
    "path": "M12.581 0c.436.037.871.1 1.299.186 1.679.383 3.121 1.213 4.382 2.365 1.161 1.06 1.917 2.372 2.335 3.881.089.321.216.56.586.624.205.035.238.245.239.43.003.646.002 1.294.002 1.94l-.002 1.21c-.001.356-.116.479-.466.474-.211-.003-.293.119-.344.291-.146.489-.33.966-.552 1.426-.818 1.682-2.084 2.938-3.688 3.87-.077.045-.155.088-.233.131-.252.137-.258.146-.155.415.114.299.358.529.664.625.269.096.553.134.827.21a.672.672 0 0 1 .236.094c-.066.082-.156.067-.231.082-.36.073-.713.184-1.086.17a1.275 1.275 0 0 1-.438-.064c-.114-.045-.152-.006-.176.109a5.354 5.354 0 0 0-.084.92c-.015.617-.071 1.23-.112 1.844-.042.598-.018.651.558.842.281.094.563.187.842.286.069.024.15.038.192.117-.04.057-.098.035-.146.035-.493.003-.985.005-1.478.001-.524-.005-.806-.282-.845-.803-.055-.762-.12-1.524-.182-2.286a.947.947 0 0 0-.026-.12c-.079.455-.065.879-.084 1.298-.023.528-.008 1.057-.007 1.584 0 .27.086.388.335.483.359.135.711.295 1.114.262.141-.012.276.062.402.129.032.017.073.033.069.073-.004.043-.049.047-.084.045-.657-.019-1.317.065-1.972-.028-.323-.046-.533-.236-.631-.552-.094-.303-.114-.617-.137-.93-.046-.626-.078-1.253-.116-1.88a.222.222 0 0 0-.061-.171.282.282 0 0 0-.031.193c-.002.956-.002 1.911-.001 2.866 0 .388.123.575.494.708.481.172.976.298 1.47.423.11.028.225.047.242.192h-1.852c-.051-.01-.103-.022-.155-.03-.701-.1-1.001-.372-1.143-1.042l-.067-.331-.226-1.103c-.069.12-.118.25-.144.386-.083.399-.151.802-.243 1.2-.113.493-.444.763-.932.857l-.33.063H8.558c.057-.171.216-.185.355-.221.476-.127.96-.223 1.417-.409a.603.603 0 0 0 .397-.521c.058-.435.002-.865-.013-1.296a1.528 1.528 0 0 0-.078-.315.405.405 0 0 0-.071.207c-.026.296-.049.591-.075.886-.038.432-.273.716-.679.81a1.702 1.702 0 0 1-.37.045c-.557.003-1.115-.001-1.673-.005-.048 0-.109.019-.148-.065.178-.103.377-.168.582-.187a5.67 5.67 0 0 0 .939-.193c.42-.114.522-.249.512-.687-.023-.931-.091-1.86-.069-2.791.004-.184.001-.368.001-.551a2.387 2.387 0 0 0-.05.385 40.299 40.299 0 0 1-.186 2.623c-.052.513-.296.748-.804.805-.446.051-.889.002-1.332-.02-.108-.006-.234.012-.339-.064.043-.066.106-.07.16-.087.362-.115.725-.224 1.086-.344.246-.081.35-.235.355-.492a2.241 2.241 0 0 0-.003-.232 45.315 45.315 0 0 1-.105-2.149 5.487 5.487 0 0 0-.035-.478c-.024-.188-.131-.287-.295-.258-.505.092-.99-.006-1.473-.139-.059-.016-.134-.007-.178-.088a.986.986 0 0 1 .285-.09c.255-.052.507-.121.753-.208.312-.112.564-.347.695-.651.089-.203.056-.317-.112-.398-1.418-.683-2.512-1.73-3.391-3.017a8.152 8.152 0 0 1-1.123-2.447c-.067-.246-.156-.3-.383-.26-.306.053-.401.006-.535-.273v-3.49c.144-.303.205-.341.534-.329.235.01.247-.004.309-.242.396-1.508 1.082-2.861 2.171-3.988C6.9 1.42 8.523.631 10.34.203c.456-.108.922-.15 1.387-.203h.854Zm7.974 8.948a7.34 7.34 0 0 0-.048-.938 8.353 8.353 0 0 0-.099-.65c-.598-2.964-2.344-5.02-5.051-6.268-1.553-.715-3.21-.835-4.878-.511-3.248.633-5.396 2.583-6.539 5.652-.436 1.173-.495 2.406-.37 3.65.087.935.339 1.846.745 2.694.585 1.213 1.444 2.207 2.477 3.058.343.286.719.528 1.121.719.235.111.247.105.245-.146.006-.16.003-.32-.009-.48-.125-1.02-.142-2.045-.169-3.069a.392.392 0 0 0-.184-.353c-.385-.268-.713-.592-.921-1.019-.474-.97-.372-2.361.813-3.215.136-.097.217-.19.198-.373a1.724 1.724 0 0 1 .031-.442c.177-1.187.748-2.138 1.722-2.84.68-.492 1.442-.772 2.286-.782.483-.007.953.11 1.414.244 1.609.467 2.846 2.07 2.845 3.697a.64.64 0 0 0 .268.565c.463.371.821.83.943 1.426.22 1.077-.083 1.982-.979 2.634-.266.194-.347.406-.333.698.002.047 0 .095-.002.142l-.062 1.439c-.025.586-.138 1.165-.117 1.754.008.223.006.226.201.128a7.46 7.46 0 0 0 2.393-1.903c1.32-1.577 2.074-3.372 2.059-5.511ZM9.117 12.102c1.489.021 2.443-1.578 1.716-2.879a1.937 1.937 0 0 0-1.699-.991c-1.094-.004-1.954.822-1.958 1.881-.005 1.148.813 1.985 1.941 1.989Zm5.794 0c1.101.002 1.935-.823 1.935-1.917 0-1.091-.846-1.949-1.92-1.947-1.064.003-1.94.866-1.943 1.915-.003 1.105.831 1.948 1.928 1.949Zm-1.472 1.937c-.208.128-.407.277-.63.384-.536.257-1.063.257-1.579-.048-.158-.094-.308-.201-.464-.298-.047-.028-.092-.103-.15-.062-.044.03-.01.1-.001.151.037.179.064.362.082.544.027.565.293.992.742 1.31a.984.984 0 0 0 .791.186c.565-.119 1.025-.614 1.124-1.218.043-.266.005-.544.109-.803a.133.133 0 0 0-.024-.146Zm-8.78-4.92c-.012-1.102.143-2.055.54-2.961.633-1.443 1.642-2.553 2.98-3.374a.378.378 0 0 1 .459.067c.06.06.036.118.01.178a1.09 1.09 0 0 1-.48.51c-1.079.639-1.829 1.571-2.357 2.688a6.325 6.325 0 0 0-.618 2.986c.055 1.309.439 2.516 1.213 3.588.088.104.148.23.173.365.01.08.059.168-.031.228a.312.312 0 0 1-.288.041.502.502 0 0 1-.234-.185c-.72-.979-1.193-2.056-1.331-3.273-.036-.326-.004-.653-.036-.858ZM8.94 2.34a.373.373 0 0 1 .378-.382c.211.001.409.226.416.473.004.138-.309.39-.476.386-.189-.005-.318-.2-.318-.477Zm-.465 7.48a.609.609 0 0 1 .586-.631c.38-.003.671.271.675.633.004.356-.27.622-.639.621-.38-.002-.621-.241-.622-.623Zm6.496.623c-.381-.002-.625-.255-.621-.646a.635.635 0 0 1 .596-.613.656.656 0 0 1 .669.643c.001.354-.275.618-.644.616Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/cncf/artwork/blob/c2e619cdf85e8bac090ceca7c0834c5cfedf9426/projects/argo/icon/black/argo-icon-black.svg"
    }
  },
  {
    "id": "asana",
    "title": "Asana",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "asana.com"
    ],
    "viewBox": 24,
    "hex": "F06A6A",
    "path": "M18.78 12.653c-2.882 0-5.22 2.336-5.22 5.22s2.338 5.22 5.22 5.22 5.22-2.34 5.22-5.22-2.336-5.22-5.22-5.22zm-13.56 0c-2.88 0-5.22 2.337-5.22 5.22s2.338 5.22 5.22 5.22 5.22-2.338 5.22-5.22-2.336-5.22-5.22-5.22zm12-6.525c0 2.883-2.337 5.22-5.22 5.22-2.882 0-5.22-2.337-5.22-5.22 0-2.88 2.338-5.22 5.22-5.22 2.883 0 5.22 2.34 5.22 5.22z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://asana.com/brand",
      "guidelines": "https://asana.com/brand"
    }
  },
  {
    "id": "bitbucket",
    "title": "Bitbucket",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "bitbucket.org"
    ],
    "viewBox": 24,
    "hex": "0052CC",
    "path": "M.778 1.213a.768.768 0 00-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 00.77-.646l3.27-20.03a.768.768 0 00-.768-.891zM14.52 15.53H9.522L8.17 8.466h7.561z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://atlassian.design/resources/logo-library",
      "guidelines": "https://atlassian.design/foundations/logos"
    }
  },
  {
    "id": "circleci",
    "title": "CircleCI",
    "category": "engineering",
    "aliases": [
      "circle-ci"
    ],
    "domains": [
      "circleci.com"
    ],
    "viewBox": 24,
    "hex": "343434",
    "path": "M8.963 12c0-1.584 1.284-2.855 2.855-2.855 1.572 0 2.856 1.284 2.856 2.855 0 1.572-1.284 2.856-2.856 2.856-1.57 0-2.855-1.284-2.855-2.856zm2.855-12C6.215 0 1.522 3.84.19 9.025c-.01.036-.01.07-.01.12 0 .313.252.576.575.576H5.59c.23 0 .433-.13.517-.333.997-2.16 3.18-3.672 5.712-3.672 3.466 0 6.286 2.82 6.286 6.287 0 3.47-2.82 6.29-6.29 6.29-2.53 0-4.714-1.5-5.71-3.673-.097-.19-.29-.336-.517-.336H.755c-.312 0-.575.253-.575.576 0 .037.014.072.014.12C1.514 20.16 6.214 24 11.818 24c6.624 0 12-5.375 12-12 0-6.623-5.376-12-12-12z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://circleci.com/press"
    }
  },
  {
    "id": "claude",
    "title": "Claude",
    "category": "ai",
    "aliases": [
      "claude-ai"
    ],
    "domains": [
      "claude.ai"
    ],
    "viewBox": 24,
    "hex": "D97757",
    "path": "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://claude.ai"
    }
  },
  {
    "id": "clickhouse",
    "title": "ClickHouse",
    "category": "data",
    "aliases": [],
    "domains": [
      "clickhouse.com"
    ],
    "viewBox": 24,
    "hex": "FFCC01",
    "path": "M21.333 10H24v4h-2.667ZM16 1.335h2.667v21.33H16Zm-5.333 0h2.666v21.33h-2.666ZM0 22.665V1.335h2.667v21.33zm5.333-21.33H8v21.33H5.333Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/ClickHouse/ClickHouse/blob/12bd453a43819176d25ecf247033f6cb1af54beb/website/images/logo-clickhouse.svg"
    }
  },
  {
    "id": "cloudflare",
    "title": "Cloudflare",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "cloudflare.com"
    ],
    "viewBox": 24,
    "hex": "F38020",
    "path": "M16.5088 16.8447c.1475-.5068.0908-.9707-.1553-1.3154-.2246-.3164-.6045-.499-1.0615-.5205l-8.6592-.1123a.1559.1559 0 0 1-.1333-.0713c-.0283-.042-.0351-.0986-.021-.1553.0278-.084.1123-.1484.2036-.1562l8.7359-.1123c1.0351-.0489 2.1601-.8868 2.5537-1.9136l.499-1.3013c.0215-.0561.0293-.1128.0147-.168-.5625-2.5463-2.835-4.4453-5.5499-4.4453-2.5039 0-4.6284 1.6177-5.3876 3.8614-.4927-.3658-1.1187-.5625-1.794-.499-1.2026.119-2.1665 1.083-2.2861 2.2856-.0283.31-.0069.6128.0635.894C1.5683 13.171 0 14.7754 0 16.752c0 .1748.0142.3515.0352.5273.0141.083.0844.1475.1689.1475h15.9814c.0909 0 .1758-.0645.2032-.1553l.12-.4268zm2.7568-5.5634c-.0771 0-.1611 0-.2383.0112-.0566 0-.1054.0415-.127.0976l-.3378 1.1744c-.1475.5068-.0918.9707.1543 1.3164.2256.3164.6055.498 1.0625.5195l1.8437.1133c.0557 0 .1055.0263.1329.0703.0283.043.0351.1074.0214.1562-.0283.084-.1132.1485-.204.1553l-1.921.1123c-1.041.0488-2.1582.8867-2.5527 1.914l-.1406.3585c-.0283.0713.0215.1416.0986.1416h6.5977c.0771 0 .1474-.0489.169-.126.1122-.4082.1757-.837.1757-1.2803 0-2.6025-2.125-4.727-4.7344-4.727",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.cloudflare.com/logo/",
      "guidelines": "https://www.cloudflare.com/trademark/"
    }
  },
  {
    "id": "cloudinary",
    "title": "Cloudinary",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "cloudinary.com"
    ],
    "viewBox": 24,
    "hex": "3448C5",
    "path": "M24 14.8598c0 2.1729-1.3757 3.974-3.5903 4.6996l-.0995.0318V17.989c1.3777-.5805 2.1869-1.7275 2.1869-3.1291-.0072-2-1.6087-3.6288-3.6082-3.6699h-.5964l-.1432-.5686c-.7025-2.8996-3.2886-4.9489-6.2721-4.97a6.3915 6.3915 0 0 0-5.811 3.664l-.1828.3757-.4175.0437a4.4311 4.4311 0 0 0-3.3052 2.088c-1.2803 2.0856-.6274 4.8143 1.4583 6.0947v1.6897h-.01l-.149-.0675a5.9402 5.9402 0 0 1-3.3658-4.3494c-.5787-3.2291 1.57-6.3161 4.7991-6.8948a7.8766 7.8766 0 0 1 6.9839-4.149c3.4724.025 6.535 2.28 7.5901 5.5883 2.5789.3366 4.5138 2.5245 4.5327 5.1251zm-15.3176-1.322h.5647a.0656.0656 0 0 0 .0457-.1113L7.084 11.2158l-.0007-.0007a.0656.0656 0 0 0-.0927.0007L4.78 13.4265a.0656.0656 0 0 0 .0477.1113h.5566a.0656.0656 0 0 1 .0657.0656v5.0574c0 .6588.534 1.1928 1.1928 1.1928H9.247a.0656.0656 0 0 0 .0457-.1113l-.33-.33a1.1928 1.1928 0 0 1-.348-.839v-4.97a.0676.0676 0 0 1 .0676-.0655zm9.769 2.5466h.5667a.0655.0655 0 0 0 .0457-.1133l-2.2107-2.2087-.0015-.0015a.0636.0636 0 0 0-.0899.0015L14.551 15.971a.0657.0657 0 0 0 .0457.1133h.5567a.0656.0656 0 0 1 .0656.0656v2.5108c0 .6588.534 1.1928 1.1928 1.1928h2.6063a.0655.0655 0 0 0 .0457-.1113l-.33-.33a1.1928 1.1928 0 0 1-.348-.839V16.15a.0656.0656 0 0 1 .0657-.0656zm-4.8844-1.2743h.5646a.0656.0656 0 0 0 .0477-.1114l-2.2107-2.2027-.0006-.0006a.0656.0656 0 0 0-.0928.0006l-2.2087 2.2068a.0656.0656 0 0 0 .0457.1113h.5626a.0676.0676 0 0 1 .0657.0676v3.7791c0 .6588.534 1.1928 1.1928 1.1928h2.5983a.0656.0656 0 0 0 .0477-.1113l-.332-.33a1.193 1.193 0 0 1-.346-.839v-3.6956c0-.0366.0291-.0665.0657-.0676z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://cloudinary.com"
    }
  },
  {
    "id": "databricks",
    "title": "Databricks",
    "category": "data",
    "aliases": [],
    "domains": [
      "databricks.com"
    ],
    "viewBox": 24,
    "hex": "FF3621",
    "path": "M.95 14.184L12 20.403l9.919-5.55v2.21L12 22.662l-10.484-5.96-.565.308v.77L12 24l11.05-6.218v-4.317l-.515-.309L12 19.118l-9.867-5.653v-2.21L12 16.805l11.05-6.218V6.32l-.515-.308L12 11.974 2.647 6.681 12 1.388l7.76 4.368.668-.411v-.566L12 0 .95 6.27v.72L12 13.207l9.919-5.55v2.26L12 15.52 1.516 9.56l-.565.308Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.databricks.com",
      "guidelines": "https://brand.databricks.com/Styleguide/Guide/"
    }
  },
  {
    "id": "datadog",
    "title": "Datadog",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "datadoghq.com"
    ],
    "viewBox": 24,
    "hex": "632CA6",
    "path": "M19.57 17.04l-1.997-1.316-1.665 2.782-1.937-.567-1.706 2.604.087.82 9.274-1.71-.538-5.794zm-8.649-2.498l1.488-.204c.241.108.409.15.697.223.45.117.97.23 1.741-.16.18-.088.553-.43.704-.625l6.096-1.106.622 7.527-10.444 1.882zm11.325-2.712l-.602.115L20.488 0 .789 2.285l2.427 19.693 2.306-.334c-.184-.263-.471-.581-.96-.989-.68-.564-.44-1.522-.039-2.127.53-1.022 3.26-2.322 3.106-3.956-.056-.594-.15-1.368-.702-1.898-.02.22.017.432.017.432s-.227-.289-.34-.683c-.112-.15-.2-.199-.319-.4-.085.233-.073.503-.073.503s-.186-.437-.216-.807c-.11.166-.137.48-.137.48s-.241-.69-.186-1.062c-.11-.323-.436-.965-.343-2.424.6.421 1.924.321 2.44-.439.171-.251.288-.939-.086-2.293-.24-.868-.835-2.16-1.066-2.651l-.028.02c.122.395.374 1.223.47 1.625.293 1.218.372 1.642.234 2.204-.116.488-.397.808-1.107 1.165-.71.358-1.653-.514-1.713-.562-.69-.55-1.224-1.447-1.284-1.883-.062-.477.275-.763.445-1.153-.243.07-.514.192-.514.192s.323-.334.722-.624c.165-.109.262-.178.436-.323a9.762 9.762 0 0 0-.456.003s.42-.227.855-.392c-.318-.014-.623-.003-.623-.003s.937-.419 1.678-.727c.509-.208 1.006-.147 1.286.257.367.53.752.817 1.569.996.501-.223.653-.337 1.284-.509.554-.61.99-.688.99-.688s-.216.198-.274.51c.314-.249.66-.455.66-.455s-.134.164-.259.426l.03.043c.366-.22.797-.394.797-.394s-.123.156-.268.358c.277-.002.838.012 1.056.037 1.285.028 1.552-1.374 2.045-1.55.618-.22.894-.353 1.947.68.903.888 1.609 2.477 1.259 2.833-.294.295-.874-.115-1.516-.916a3.466 3.466 0 0 1-.716-1.562 1.533 1.533 0 0 0-.497-.85s.23.51.23.96c0 .246.03 1.165.424 1.68-.039.076-.057.374-.1.43-.458-.554-1.443-.95-1.604-1.067.544.445 1.793 1.468 2.273 2.449.453.927.186 1.777.416 1.997.065.063.976 1.197 1.15 1.767.306.994.019 2.038-.381 2.685l-1.117.174c-.163-.045-.273-.068-.42-.153.08-.143.241-.5.243-.572l-.063-.111c-.348.492-.93.97-1.414 1.245-.633.359-1.363.304-1.838.156-1.348-.415-2.623-1.327-2.93-1.566 0 0-.01.191.048.234.34.383 1.119 1.077 1.872 1.56l-1.605.177.759 5.908c-.337.048-.39.071-.757.124-.325-1.147-.946-1.895-1.624-2.332-.599-.384-1.424-.47-2.214-.314l-.05.059a2.851 2.851 0 0 1 1.863.444c.654.413 1.181 1.481 1.375 2.124.248.822.42 1.7-.248 2.632-.476.662-1.864 1.028-2.986.237.3.481.705.876 1.25.95.809.11 1.577-.03 2.106-.574.452-.464.69-1.434.628-2.456l.714-.104.258 1.834 11.827-1.424zM15.05 6.848c-.034.075-.085.125-.007.37l.004.014.013.032.032.073c.14.287.295.558.552.696.067-.011.136-.019.207-.023.242-.01.395.028.492.08.009-.048.01-.119.005-.222-.018-.364.072-.982-.626-1.308-.264-.122-.634-.084-.757.068a.302.302 0 0 1 .058.013c.186.066.06.13.027.207m1.958 3.392c-.092-.05-.52-.03-.821.005-.574.068-1.193.267-1.328.372-.247.191-.135.523.047.66.511.382.96.638 1.432.575.29-.038.546-.497.728-.914.124-.288.124-.598-.058-.698m-5.077-2.942c.162-.154-.805-.355-1.556.156-.554.378-.571 1.187-.041 1.646.053.046.096.078.137.104a4.77 4.77 0 0 1 1.396-.412c.113-.125.243-.345.21-.745-.044-.542-.455-.456-.146-.749",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.datadoghq.com/about/resources",
      "guidelines": "https://www.datadoghq.com/about/resources/"
    }
  },
  {
    "id": "deepseek",
    "title": "DeepSeek",
    "category": "ai",
    "aliases": [],
    "domains": [
      "deepseek.com"
    ],
    "viewBox": 24,
    "hex": "5786FE",
    "path": "M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.deepseek.com"
    }
  },
  {
    "id": "digitalocean",
    "title": "DigitalOcean",
    "category": "cloud",
    "aliases": [
      "digital-ocean"
    ],
    "domains": [
      "digitalocean.com"
    ],
    "viewBox": 24,
    "hex": "0080FF",
    "path": "M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.digitalocean.com/press/",
      "guidelines": "https://www.digitalocean.com/press/"
    }
  },
  {
    "id": "discord",
    "title": "Discord",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "discord.com"
    ],
    "viewBox": 24,
    "hex": "5865F2",
    "path": "M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://discord.com/branding",
      "guidelines": "https://discord.com/branding"
    }
  },
  {
    "id": "django",
    "title": "Django",
    "category": "framework",
    "aliases": [],
    "domains": [
      "djangoproject.com"
    ],
    "viewBox": 24,
    "hex": "092E20",
    "path": "M11.146 0h3.924v18.166c-2.013.382-3.491.535-5.096.535-4.791 0-7.288-2.166-7.288-6.32 0-4.002 2.65-6.6 6.753-6.6.637 0 1.121.05 1.707.203zm0 9.143a3.894 3.894 0 00-1.325-.204c-1.988 0-3.134 1.223-3.134 3.365 0 2.09 1.096 3.236 3.109 3.236.433 0 .79-.025 1.35-.102V9.142zM21.314 6.06v9.098c0 3.134-.229 4.638-.917 5.937-.637 1.249-1.478 2.039-3.211 2.905l-3.644-1.733c1.733-.815 2.574-1.53 3.109-2.625.561-1.121.739-2.421.739-5.835V6.059h3.924zM17.39.021h3.924v4.026H17.39z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.djangoproject.com/community/logos/"
    }
  },
  {
    "id": "docker",
    "title": "Docker",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "docker.com"
    ],
    "viewBox": 24,
    "hex": "2496ED",
    "path": "M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.docker.com/company/newsroom/media-resources"
    }
  },
  {
    "id": "dotnet",
    "title": ".NET",
    "category": "framework",
    "aliases": [
      ".net"
    ],
    "domains": [
      "dotnet.microsoft.com"
    ],
    "viewBox": 24,
    "hex": "512BD4",
    "path": "M24 8.77h-2.468v7.565h-1.425V8.77h-2.462V7.53H24zm-6.852 7.565h-4.821V7.53h4.63v1.24h-3.205v2.494h2.953v1.234h-2.953v2.604h3.396zm-6.708 0H8.882L4.78 9.863a2.896 2.896 0 0 1-.258-.51h-.036c.032.189.048.592.048 1.21v5.772H3.157V7.53h1.659l3.965 6.32c.167.261.275.442.323.54h.024c-.04-.233-.06-.629-.06-1.185V7.529h1.372zm-8.703-.693a.868.829 0 0 1-.869.829.868.829 0 0 1-.868-.83.868.829 0 0 1 .868-.828.868.829 0 0 1 .869.829Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/dotnet/brand/blob/c7d0f51b8ec59531332d05fb27a5b758a7a3d689/logo/dotnet-logo.svg",
      "guidelines": "https://github.com/dotnet/brand/blob/c7d0f51b8ec59531332d05fb27a5b758a7a3d689/dotnet-styleGuide-2024.pdf",
      "license": {
        "type": "CC0-1.0",
        "url": "https://spdx.org/licenses/CC0-1.0"
      }
    }
  },
  {
    "id": "elasticsearch",
    "title": "Elasticsearch",
    "category": "data",
    "aliases": [
      "elastic"
    ],
    "domains": [
      "elastic.co"
    ],
    "viewBox": 24,
    "hex": "005571",
    "path": "M13.394 0C8.683 0 4.609 2.716 2.644 6.667h15.641a4.77 4.77 0 0 0 3.073-1.11c.446-.375.864-.785 1.247-1.243l.001-.002A11.974 11.974 0 0 0 13.394 0zM1.804 8.889a12.009 12.009 0 0 0 0 6.222h14.7a3.111 3.111 0 1 0 0-6.222zm.84 8.444C4.61 21.283 8.684 24 13.395 24c3.701 0 7.011-1.677 9.212-4.312l-.001-.002a9.958 9.958 0 0 0-1.247-1.243 4.77 4.77 0 0 0-3.073-1.11z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.elastic.co/brand"
    }
  },
  {
    "id": "facebook",
    "title": "Facebook",
    "category": "channel",
    "aliases": [],
    "domains": [
      "facebook.com"
    ],
    "viewBox": 24,
    "hex": "0866FF",
    "path": "M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://about.meta.com/brand/resources/facebook/logo",
      "guidelines": "https://about.meta.com/brand/resources/facebook/logo"
    }
  },
  {
    "id": "fastapi",
    "title": "FastAPI",
    "category": "framework",
    "aliases": [],
    "domains": [
      "fastapi.tiangolo.com"
    ],
    "viewBox": 24,
    "hex": "009688",
    "path": "M12 .0387C5.3729.0384.0003 5.3931 0 11.9988c-.001 6.6066 5.372 11.9628 12 11.9625 6.628.0003 12.001-5.3559 12-11.9625-.0003-6.6057-5.3729-11.9604-12-11.96m-.829 5.4153h7.55l-7.5805 5.3284h5.1828L5.279 18.5436q2.9466-6.5444 5.892-13.0896",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/tiangolo/fastapi/blob/ffb4f77a11f83132b521ba0aac6c95792c19e797/docs/en/docs/img/icon-white.svg"
    }
  },
  {
    "id": "figma",
    "title": "Figma",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "figma.com"
    ],
    "viewBox": 24,
    "hex": "F24E1E",
    "path": "M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.figma.com/using-the-figma-brand/",
      "guidelines": "https://www.figma.com/using-the-figma-brand/"
    }
  },
  {
    "id": "firebase",
    "title": "Firebase",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "firebase.google.com"
    ],
    "viewBox": 24,
    "hex": "DD2C00",
    "path": "M19.455 8.369c-.538-.748-1.778-2.285-3.681-4.569-.826-.991-1.535-1.832-1.884-2.245a146 146 0 0 0-.488-.576l-.207-.245-.113-.133-.022-.032-.01-.005L12.57 0l-.609.488c-1.555 1.246-2.828 2.851-3.681 4.64-.523 1.064-.864 2.105-1.043 3.176-.047.241-.088.489-.121.738-.209-.017-.421-.028-.632-.033-.018-.001-.035-.002-.059-.003a7.46 7.46 0 0 0-2.28.274l-.317.089-.163.286c-.765 1.342-1.198 2.869-1.252 4.416-.07 2.01.477 3.954 1.583 5.625 1.082 1.633 2.61 2.882 4.42 3.611l.236.095.071.025.003-.001a9.59 9.59 0 0 0 2.941.568q.171.006.342.006c1.273 0 2.513-.249 3.69-.742l.008.004.313-.145a9.63 9.63 0 0 0 3.927-3.335c1.01-1.49 1.577-3.234 1.641-5.042.075-2.161-.643-4.304-2.133-6.371m-7.083 6.695c.328 1.244.264 2.44-.191 3.558-1.135-1.12-1.967-2.352-2.475-3.665-.543-1.404-.87-2.74-.974-3.975.48.157.922.366 1.315.622 1.132.737 1.914 1.902 2.325 3.461zm.207 6.022c.482.368.99.712 1.513 1.028-.771.21-1.565.302-2.369.273a8 8 0 0 1-.373-.022c.458-.394.869-.823 1.228-1.279zm1.347-6.431c-.516-1.957-1.527-3.437-3.002-4.398-.647-.421-1.385-.741-2.194-.95.011-.134.026-.268.043-.4.014-.113.03-.216.046-.313.133-.689.332-1.37.589-2.025.099-.25.206-.499.321-.74l.004-.008c.177-.358.376-.719.61-1.105l.092-.152-.003-.001c.544-.851 1.197-1.627 1.942-2.311l.288.341c.672.796 1.304 1.548 1.878 2.237 1.291 1.549 2.966 3.583 3.612 4.48 1.277 1.771 1.893 3.579 1.83 5.375-.049 1.395-.461 2.755-1.195 3.933-.694 1.116-1.661 2.05-2.8 2.708-.636-.318-1.559-.839-2.539-1.599.79-1.575.952-3.28.479-5.072zm-2.575 5.397c-.725.939-1.587 1.55-2.09 1.856-.081-.029-.163-.06-.243-.093l-.065-.026c-1.49-.616-2.747-1.656-3.635-3.01-.907-1.384-1.356-2.993-1.298-4.653.041-1.19.338-2.327.882-3.379.316-.07.638-.114.96-.131l.084-.002c.162-.003.324-.003.478 0 .227.011.454.035.677.07.073 1.513.445 3.145 1.105 4.852.637 1.644 1.694 3.162 3.144 4.515z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://firebase.google.com/brand-guidelines",
      "guidelines": "https://firebase.google.com/brand-guidelines"
    }
  },
  {
    "id": "flask",
    "title": "Flask",
    "category": "framework",
    "aliases": [],
    "domains": [
      "palletsprojects.com"
    ],
    "viewBox": 24,
    "hex": "3BABC3",
    "path": "M10.773 2.878c-.013 1.434.322 4.624.445 5.734l-8.558 3.83c-.56-.959-.98-2.304-1.237-3.38l-.06.027c-.205.09-.406.053-.494-.088l-.011-.018-.82-1.506c-.058-.105-.05-.252.024-.392a.78.78 0 0 1 .358-.331l9.824-4.207c.146-.064.299-.063.4.004.106.062.127.128.13.327Zm.68 7c.523 1.97.675 2.412.832 2.818l-7.263 3.7a19.35 19.35 0 0 1-1.81-2.83l8.24-3.689Zm12.432 8.786h.003c.283.402-.047.657-.153.698l-.947.37c.037.125.035.319-.217.414l-.736.287c-.229.09-.398-.059-.42-.2l-.025-.125c-4.427 1.784-7.94 1.685-10.696.647-1.981-.745-3.576-1.983-4.846-3.379l6.948-3.54c.721 1.431 1.586 2.454 2.509 3.178 2.086 1.638 4.415 1.712 5.793 1.563l-.047-.233c-.015-.077.007-.135.086-.165l.734-.288a.302.302 0 0 1 .342.086l.748-.288a.306.306 0 0 1 .341.086l.583.89Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/pallets/flask/blob/85c5d93cbd049c4bd0679c36fd1ddcae8c37b642/docs/_static/flask-icon.svg"
    }
  },
  {
    "id": "fly-io",
    "title": "Fly.io",
    "category": "cloud",
    "aliases": [
      "fly.io"
    ],
    "domains": [
      "fly.io"
    ],
    "viewBox": 24,
    "hex": "24175B",
    "path": "M11.987 0c-2.45-.01-5.002.925-6.541 2.897-1.17 1.502-1.664 3.474-1.49 5.356.29 2.112 1.476 3.96 2.676 5.672a41.5 41.5 0 0 0 4.216 4.831c-1.063.832-1.943 2.286-1.357 3.644.821 2.32 4.665 2.05 5.122-.372.39-1.288-.694-2.533-1.428-3.309 2.388-2.431 4.706-5.036 6.17-8.145.595-1.32.902-2.802.614-4.24-.28-2.341-1.823-4.473-3.967-5.46C14.76.266 13.364.016 11.987 0m-.236 1.577v15.534C9.881 13.483 7.724 9.266 8.73 5.069c.35-1.539 1.253-3.309 3.02-3.492m1.996.04c1.534.357 3.031 1.096 3.906 2.48 1.3 1.93 1.318 4.55.1 6.521-1.268 2.395-3.06 4.463-4.916 6.415 1.472-2.974 3.074-6.106 3.182-9.5-.043-2.08-.438-4.612-2.272-5.916M11.97 20.103c.848.342 1.597 1.983.153 2.173-.664.15-1.367-.599-.995-1.222.213-.355.488-.73.842-.95",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://fly.io/docs/about/brand",
      "guidelines": "https://fly.io/docs/about/brand"
    }
  },
  {
    "id": "github",
    "title": "GitHub",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "github.com"
    ],
    "viewBox": 24,
    "hex": "181717",
    "path": "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/logos",
      "guidelines": "https://github.com/logos"
    }
  },
  {
    "id": "github-actions",
    "title": "GitHub Actions",
    "category": "engineering",
    "aliases": [],
    "domains": [],
    "viewBox": 24,
    "hex": "2088FF",
    "path": "M10.984 13.836a.5.5 0 0 1-.353-.146l-.745-.743a.5.5 0 1 1 .706-.708l.392.391 1.181-1.18a.5.5 0 0 1 .708.707l-1.535 1.533a.504.504 0 0 1-.354.146zm9.353-.147l1.534-1.532a.5.5 0 0 0-.707-.707l-1.181 1.18-.392-.391a.5.5 0 1 0-.706.708l.746.743a.497.497 0 0 0 .706-.001zM4.527 7.452l2.557-1.585A1 1 0 0 0 7.09 4.17L4.533 2.56A1 1 0 0 0 3 3.406v3.196a1.001 1.001 0 0 0 1.527.85zm2.03-2.436L4 6.602V3.406l2.557 1.61zM24 12.5c0 1.93-1.57 3.5-3.5 3.5a3.503 3.503 0 0 1-3.46-3h-2.08a3.503 3.503 0 0 1-3.46 3 3.502 3.502 0 0 1-3.46-3h-.558c-.972 0-1.85-.399-2.482-1.042V17c0 1.654 1.346 3 3 3h.04c.244-1.693 1.7-3 3.46-3 1.93 0 3.5 1.57 3.5 3.5S13.43 24 11.5 24a3.502 3.502 0 0 1-3.46-3H8c-2.206 0-4-1.794-4-4V9.899A5.008 5.008 0 0 1 0 5c0-2.757 2.243-5 5-5s5 2.243 5 5a5.005 5.005 0 0 1-4.952 4.998A2.482 2.482 0 0 0 7.482 12h.558c.244-1.693 1.7-3 3.46-3a3.502 3.502 0 0 1 3.46 3h2.08a3.503 3.503 0 0 1 3.46-3c1.93 0 3.5 1.57 3.5 3.5zm-15 8c0 1.378 1.122 2.5 2.5 2.5s2.5-1.122 2.5-2.5-1.122-2.5-2.5-2.5S9 19.122 9 20.5zM5 9c2.206 0 4-1.794 4-4S7.206 1 5 1 1 2.794 1 5s1.794 4 4 4zm9 3.5c0-1.378-1.122-2.5-2.5-2.5S9 11.122 9 12.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5zm9 0c0-1.378-1.122-2.5-2.5-2.5S18 11.122 18 12.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5zm-13 8a.5.5 0 1 0 1 0 .5.5 0 0 0-1 0zm2 0a.5.5 0 1 0 1 0 .5.5 0 0 0-1 0zm12 0c0 1.93-1.57 3.5-3.5 3.5a3.503 3.503 0 0 1-3.46-3.002c-.007.001-.013.005-.021.005l-.506.017h-.017a.5.5 0 0 1-.016-.999l.506-.017c.018-.002.035.006.052.007A3.503 3.503 0 0 1 20.5 17c1.93 0 3.5 1.57 3.5 3.5zm-1 0c0-1.378-1.122-2.5-2.5-2.5S18 19.122 18 20.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/features/actions"
    }
  },
  {
    "id": "gitlab",
    "title": "GitLab",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "gitlab.com"
    ],
    "viewBox": 24,
    "hex": "FC6D26",
    "path": "m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://about.gitlab.com/press/press-kit/",
      "guidelines": "https://about.gitlab.com/handbook/marketing/corporate-marketing/brand-activation/trademark-guidelines/"
    }
  },
  {
    "id": "go",
    "title": "Go",
    "category": "language",
    "aliases": [
      "golang"
    ],
    "domains": [
      "go.dev"
    ],
    "viewBox": 24,
    "hex": "00ADD8",
    "path": "M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514-.176.046-.187.058-.34-.117-.174-.199-.303-.327-.548-.444-.737-.362-1.45-.257-2.115.175-.795.514-1.204 1.274-1.192 2.22.011.935.654 1.706 1.577 1.835.795.105 1.46-.175 1.987-.77.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35.152-.362.432-.97.596-1.274a.315.315 0 01.292-.187h4.253c-.023.316-.023.631-.07.947a4.983 4.983 0 01-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986-1.145.152-2.209-.07-3.143-.77-.865-.655-1.356-1.52-1.484-2.595-.152-1.274.222-2.419.993-3.424.83-1.086 1.928-1.776 3.272-2.02 1.098-.2 2.15-.07 3.096.571.62.41 1.063.97 1.356 1.648.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.665 3.665 0 01-1.262-2.255c-.21-1.32.152-2.489.947-3.529.853-1.122 1.881-1.706 3.272-1.95 1.192-.21 2.314-.095 3.33.595.923.63 1.496 1.484 1.648 2.605.198 1.578-.257 2.863-1.344 3.962-.771.783-1.718 1.273-2.805 1.495-.315.06-.63.07-.934.106zm2.78-4.72c-.011-.153-.011-.27-.034-.387-.21-1.157-1.274-1.81-2.384-1.554-1.087.245-1.788.935-2.045 2.033-.21.912.234 1.835 1.075 2.21.643.28 1.285.244 1.905-.07.923-.48 1.425-1.228 1.484-2.233z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://blog.golang.org/go-brand",
      "guidelines": "https://blog.golang.org/go-brand"
    }
  },
  {
    "id": "google-cloud",
    "title": "Google Cloud",
    "category": "cloud",
    "aliases": [
      "gcp",
      "googlecloud"
    ],
    "domains": [
      "cloud.google.com"
    ],
    "viewBox": 24,
    "hex": "4285F4",
    "path": "M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0-3.875 2.551-3.922 8.11-.247 10.941l.006-.007-.007.03a6.717 6.717 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.365 9.365 0 0 0-2.821-4.552l-.043.043.006-.05A9.344 9.344 0 0 0 12.19 2.38zm-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.186 5.186 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.59 2.59 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.747 6.747 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.186 5.186 0 0 1 3.67-1.69z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://cloud.google.com"
    }
  },
  {
    "id": "google-gemini",
    "title": "Google Gemini",
    "category": "ai",
    "aliases": [
      "gemini"
    ],
    "domains": [
      "gemini.google.com"
    ],
    "viewBox": 24,
    "hex": "8E75B2",
    "path": "M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://gemini.google.com"
    }
  },
  {
    "id": "grafana",
    "title": "Grafana",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "grafana.com"
    ],
    "viewBox": 24,
    "hex": "F46800",
    "path": "M23.02 10.59a8.578 8.578 0 0 0-.862-3.034 8.911 8.911 0 0 0-1.789-2.445c.337-1.342-.413-2.505-.413-2.505-1.292-.08-2.113.4-2.416.62-.052-.02-.102-.044-.154-.064-.22-.089-.446-.172-.677-.247-.231-.073-.47-.14-.711-.197a9.867 9.867 0 0 0-.875-.161C14.557.753 12.94 0 12.94 0c-1.804 1.145-2.147 2.744-2.147 2.744l-.018.093c-.098.029-.2.057-.298.088-.138.042-.275.094-.413.143-.138.055-.275.107-.41.166a8.869 8.869 0 0 0-1.557.87l-.063-.029c-2.497-.955-4.716.195-4.716.195-.203 2.658.996 4.33 1.235 4.636a11.608 11.608 0 0 0-.607 2.635C1.636 12.677.953 15.014.953 15.014c1.926 2.214 4.171 2.351 4.171 2.351.003-.002.006-.002.006-.005.285.509.615.994.986 1.446.156.19.32.371.488.548-.704 2.009.099 3.68.099 3.68 2.144.08 3.553-.937 3.849-1.173a9.784 9.784 0 0 0 3.164.501h.08l.055-.003.107-.002.103-.005.003.002c1.01 1.44 2.788 1.646 2.788 1.646 1.264-1.332 1.337-2.653 1.337-2.94v-.058c0-.02-.003-.039-.003-.06.265-.187.52-.387.758-.6a7.875 7.875 0 0 0 1.415-1.7c1.43.083 2.437-.885 2.437-.885-.236-1.49-1.085-2.216-1.264-2.354l-.018-.013-.016-.013a.217.217 0 0 1-.031-.02c.008-.092.016-.18.02-.27.011-.162.016-.323.016-.48v-.253l-.005-.098-.008-.135a1.891 1.891 0 0 0-.01-.13c-.003-.042-.008-.083-.013-.125l-.016-.124-.018-.122a6.215 6.215 0 0 0-2.032-3.73 6.015 6.015 0 0 0-3.222-1.46 6.292 6.292 0 0 0-.85-.048l-.107.002h-.063l-.044.003-.104.008a4.777 4.777 0 0 0-3.335 1.695c-.332.4-.592.84-.768 1.297a4.594 4.594 0 0 0-.312 1.817l.003.091c.005.055.007.11.013.164a3.615 3.615 0 0 0 .698 1.82 3.53 3.53 0 0 0 1.827 1.282c.33.098.66.14.971.137.039 0 .078 0 .114-.002l.063-.003c.02 0 .041-.003.062-.003.034-.002.065-.007.099-.01.007 0 .018-.003.028-.003l.031-.005.06-.008a1.18 1.18 0 0 0 .112-.02c.036-.008.072-.013.109-.024a2.634 2.634 0 0 0 .914-.415c.028-.02.056-.041.085-.065a.248.248 0 0 0 .039-.35.244.244 0 0 0-.309-.06l-.078.042c-.09.044-.184.083-.283.116a2.476 2.476 0 0 1-.475.096c-.028.003-.054.006-.083.006l-.083.002c-.026 0-.054 0-.08-.002l-.102-.006h-.012l-.024.006c-.016-.003-.031-.003-.044-.006-.031-.002-.06-.007-.091-.01a2.59 2.59 0 0 1-.724-.213 2.557 2.557 0 0 1-.667-.438 2.52 2.52 0 0 1-.805-1.475 2.306 2.306 0 0 1-.029-.444l.006-.122v-.023l.002-.031c.003-.021.003-.04.005-.06a3.163 3.163 0 0 1 1.352-2.29 3.12 3.12 0 0 1 .937-.43 2.946 2.946 0 0 1 .776-.101h.06l.07.002.045.003h.026l.07.005a4.041 4.041 0 0 1 1.635.49 3.94 3.94 0 0 1 1.602 1.662 3.77 3.77 0 0 1 .397 1.414l.005.076.003.075c.002.026.002.05.002.075 0 .024.003.052 0 .07v.065l-.002.073-.008.174a6.195 6.195 0 0 1-.08.639 5.1 5.1 0 0 1-.267.927 5.31 5.31 0 0 1-.624 1.13 5.052 5.052 0 0 1-3.237 2.014 4.82 4.82 0 0 1-.649.066l-.039.003h-.287a6.607 6.607 0 0 1-1.716-.265 6.776 6.776 0 0 1-3.4-2.274 6.75 6.75 0 0 1-.746-1.15 6.616 6.616 0 0 1-.714-2.596l-.005-.083-.002-.02v-.056l-.003-.073v-.096l-.003-.104v-.07l.003-.163c.008-.22.026-.45.054-.678a8.707 8.707 0 0 1 .28-1.355c.128-.444.286-.872.473-1.277a7.04 7.04 0 0 1 1.456-2.1 5.925 5.925 0 0 1 .953-.763c.169-.111.343-.213.524-.306.089-.05.182-.091.273-.135.047-.02.093-.042.138-.062a7.177 7.177 0 0 1 .714-.267l.145-.045c.049-.015.098-.026.148-.041.098-.029.197-.052.296-.076.049-.013.1-.02.15-.033l.15-.032.151-.028.076-.013.075-.01.153-.024c.057-.01.114-.013.171-.023l.169-.021c.036-.003.073-.008.106-.01l.073-.008.036-.003.042-.002c.057-.003.114-.008.171-.01l.086-.006h.023l.037-.003.145-.007a7.999 7.999 0 0 1 1.708.125 7.917 7.917 0 0 1 2.048.68 8.253 8.253 0 0 1 1.672 1.09l.09.077.089.078c.06.052.114.107.171.159.057.052.112.106.166.16.052.055.107.107.159.164a8.671 8.671 0 0 1 1.41 1.978c.012.026.028.052.04.078l.04.078.075.156c.023.051.05.1.07.153l.065.15a8.848 8.848 0 0 1 .45 1.34.19.19 0 0 0 .201.142.186.186 0 0 0 .172-.184c.01-.246.002-.532-.024-.856z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://grafana.com"
    }
  },
  {
    "id": "helm",
    "title": "Helm",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "helm.sh"
    ],
    "viewBox": 24,
    "hex": "0F1689",
    "path": "M12.337 0c-.475 0-.861 1.016-.861 2.269 0 .527.069 1.011.183 1.396a8.514 8.514 0 0 0-3.961 1.22 5.229 5.229 0 0 0-.595-1.093c-.606-.866-1.34-1.436-1.79-1.43a.381.381 0 0 0-.217.066c-.39.273-.123 1.326.596 2.353.267.381.559.705.84.948a8.683 8.683 0 0 0-1.528 1.716h1.734a7.179 7.179 0 0 1 5.381-2.421 7.18 7.18 0 0 1 5.382 2.42h1.733a8.687 8.687 0 0 0-1.32-1.53c.35-.249.735-.643 1.078-1.133.719-1.027.986-2.08.596-2.353a.382.382 0 0 0-.217-.065c-.45-.007-1.184.563-1.79 1.43a4.897 4.897 0 0 0-.676 1.325 8.52 8.52 0 0 0-3.899-1.42c.12-.39.193-.887.193-1.429 0-1.253-.386-2.269-.862-2.269zM1.624 9.443v5.162h1.358v-1.968h1.64v1.968h1.357V9.443H4.62v1.838H2.98V9.443zm5.912 0v5.162h3.21v-1.108H8.893v-.95h1.64v-1.142h-1.64v-.84h1.853V9.443zm4.698 0v5.162h3.218v-1.362h-1.86v-3.8zm4.706 0v5.162h1.364v-2.643l1.357 1.225 1.35-1.232v2.65h1.365V9.443h-.614l-2.1 1.914-2.109-1.914zm-11.82 7.28a8.688 8.688 0 0 0 1.412 1.548 5.206 5.206 0 0 0-.841.948c-.719 1.027-.985 2.08-.596 2.353.39.273 1.289-.338 2.007-1.364a5.23 5.23 0 0 0 .595-1.092 8.514 8.514 0 0 0 3.961 1.219 5.01 5.01 0 0 0-.183 1.396c0 1.253.386 2.269.861 2.269.476 0 .862-1.016.862-2.269 0-.542-.072-1.04-.193-1.43a8.52 8.52 0 0 0 3.9-1.42c.121.4.352.865.675 1.327.719 1.026 1.617 1.637 2.007 1.364.39-.273.123-1.326-.596-2.353-.343-.49-.727-.885-1.077-1.135a8.69 8.69 0 0 0 1.202-1.36h-1.771a7.174 7.174 0 0 1-5.227 2.252 7.174 7.174 0 0 1-5.226-2.252z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://helm.sh"
    }
  },
  {
    "id": "hubspot",
    "title": "HubSpot",
    "category": "business",
    "aliases": [],
    "domains": [
      "hubspot.com"
    ],
    "viewBox": 24,
    "hex": "FF7A59",
    "path": "M18.164 7.93V5.084a2.198 2.198 0 001.267-1.978v-.067A2.2 2.2 0 0017.238.845h-.067a2.2 2.2 0 00-2.193 2.193v.067a2.196 2.196 0 001.252 1.973l.013.006v2.852a6.22 6.22 0 00-2.969 1.31l.012-.01-7.828-6.095A2.497 2.497 0 104.3 4.656l-.012.006 7.697 5.991a6.176 6.176 0 00-1.038 3.446c0 1.343.425 2.588 1.147 3.607l-.013-.02-2.342 2.343a1.968 1.968 0 00-.58-.095h-.002a2.033 2.033 0 102.033 2.033 1.978 1.978 0 00-.1-.595l.005.014 2.317-2.317a6.247 6.247 0 104.782-11.134l-.036-.005zm-.964 9.378a3.206 3.206 0 113.215-3.207v.002a3.206 3.206 0 01-3.207 3.207z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.hubspot.com/style-guide",
      "guidelines": "https://www.hubspot.com/style-guide"
    }
  },
  {
    "id": "hugging-face",
    "title": "Hugging Face",
    "category": "ai",
    "aliases": [
      "huggingface"
    ],
    "domains": [
      "huggingface.co"
    ],
    "viewBox": 24,
    "hex": "FFD21E",
    "path": "M12.025 1.13c-5.77 0-10.449 4.647-10.449 10.378 0 1.112.178 2.181.503 3.185.064-.222.203-.444.416-.577a.96.96 0 0 1 .524-.15c.293 0 .584.124.84.284.278.173.48.408.71.694.226.282.458.611.684.951v-.014c.017-.324.106-.622.264-.874s.403-.487.762-.543c.3-.047.596.06.787.203s.31.313.4.467c.15.257.212.468.233.542.01.026.653 1.552 1.657 2.54.616.605 1.01 1.223 1.082 1.912.055.537-.096 1.059-.38 1.572.637.121 1.294.187 1.967.187.657 0 1.298-.063 1.921-.178-.287-.517-.44-1.041-.384-1.581.07-.69.465-1.307 1.081-1.913 1.004-.987 1.647-2.513 1.657-2.539.021-.074.083-.285.233-.542.09-.154.208-.323.4-.467a1.08 1.08 0 0 1 .787-.203c.359.056.604.29.762.543s.247.55.265.874v.015c.225-.34.457-.67.683-.952.23-.286.432-.52.71-.694.257-.16.547-.284.84-.285a.97.97 0 0 1 .524.151c.228.143.373.388.43.625l.006.04a10.3 10.3 0 0 0 .534-3.273c0-5.731-4.678-10.378-10.449-10.378M8.327 6.583a1.5 1.5 0 0 1 .713.174 1.487 1.487 0 0 1 .617 2.013c-.183.343-.762-.214-1.102-.094-.38.134-.532.914-.917.71a1.487 1.487 0 0 1 .69-2.803m7.486 0a1.487 1.487 0 0 1 .689 2.803c-.385.204-.536-.576-.916-.71-.34-.12-.92.437-1.103.094a1.487 1.487 0 0 1 .617-2.013 1.5 1.5 0 0 1 .713-.174m-10.68 1.55a.96.96 0 1 1 0 1.921.96.96 0 0 1 0-1.92m13.838 0a.96.96 0 1 1 0 1.92.96.96 0 0 1 0-1.92M8.489 11.458c.588.01 1.965 1.157 3.572 1.164 1.607-.007 2.984-1.155 3.572-1.164.196-.003.305.12.305.454 0 .886-.424 2.328-1.563 3.202-.22-.756-1.396-1.366-1.63-1.32q-.011.001-.02.006l-.044.026-.01.008-.03.024q-.018.017-.035.036l-.032.04a1 1 0 0 0-.058.09l-.014.025q-.049.088-.11.19a1 1 0 0 1-.083.116 1.2 1.2 0 0 1-.173.18q-.035.029-.075.058a1.3 1.3 0 0 1-.251-.243 1 1 0 0 1-.076-.107c-.124-.193-.177-.363-.337-.444-.034-.016-.104-.008-.2.022q-.094.03-.216.087-.06.028-.125.063l-.13.074q-.067.04-.136.086a3 3 0 0 0-.135.096 3 3 0 0 0-.26.219 2 2 0 0 0-.12.121 2 2 0 0 0-.106.128l-.002.002a2 2 0 0 0-.09.132l-.001.001a1.2 1.2 0 0 0-.105.212q-.013.036-.024.073c-1.139-.875-1.563-2.317-1.563-3.203 0-.334.109-.457.305-.454m.836 10.354c.824-1.19.766-2.082-.365-3.194-1.13-1.112-1.789-2.738-1.789-2.738s-.246-.945-.806-.858-.97 1.499.202 2.362c1.173.864-.233 1.45-.685.64-.45-.812-1.683-2.896-2.322-3.295s-1.089-.175-.938.647 2.822 2.813 2.562 3.244-1.176-.506-1.176-.506-2.866-2.567-3.49-1.898.473 1.23 2.037 2.16c1.564.932 1.686 1.178 1.464 1.53s-3.675-2.511-4-1.297c-.323 1.214 3.524 1.567 3.287 2.405-.238.839-2.71-1.587-3.216-.642-.506.946 3.49 2.056 3.522 2.064 1.29.33 4.568 1.028 5.713-.624m5.349 0c-.824-1.19-.766-2.082.365-3.194 1.13-1.112 1.789-2.738 1.789-2.738s.246-.945.806-.858.97 1.499-.202 2.362c-1.173.864.233 1.45.685.64.451-.812 1.683-2.896 2.322-3.295s1.089-.175.938.647-2.822 2.813-2.562 3.244 1.176-.506 1.176-.506 2.866-2.567 3.49-1.898-.473 1.23-2.037 2.16c-1.564.932-1.686 1.178-1.464 1.53s3.675-2.511 4-1.297c.323 1.214-3.524 1.567-3.287 2.405.238.839 2.71-1.587 3.216-.642.506.946-3.49 2.056-3.522 2.064-1.29.33-4.568 1.028-5.713-.624",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://huggingface.co/brand",
      "guidelines": "https://huggingface.co/brand"
    }
  },
  {
    "id": "influxdb",
    "title": "InfluxDB",
    "category": "data",
    "aliases": [
      "influx-db"
    ],
    "domains": [
      "influxdata.com"
    ],
    "viewBox": 24,
    "hex": "22ADF6",
    "path": "M23.778 14.482l-2.287-9.959c-.13-.545-.624-1.09-1.169-1.248L9.87.051C9.74 0 9.584 0 9.426 0c-.443 0-.909.18-1.222.443L.716 7.412C.3 7.776.092 8.504.222 9.024l2.445 10.662c.13.545.624 1.092 1.169 1.248l9.775 3.015c.13.051.285.051.443.051.443 0 .91-.18 1.223-.443l8.007-7.435c.418-.39.624-1.092.494-1.64zM10.962 2.417l7.175 2.21c.285.08.285.21 0 .286l-3.77.858c-.285.08-.674-.05-.883-.26l-2.626-2.834c-.235-.232-.184-.336.104-.26zm4.47 12.872c.079.286-.105.444-.39.365l-7.748-2.392c-.285-.079-.338-.313-.13-.52l5.93-5.514c.209-.209.443-.13.52.156zM2.667 8.267l6.293-5.85c.21-.209.545-.18.754.025L12.86 5.85c.209.21.18.545-.026.754l-6.293 5.85c-.21.21-.545.181-.754-.025L2.64 9.024a.536.536 0 01.026-.757zm1.536 9.284L2.54 10.244c-.08-.285.05-.34.234-.13L5.4 12.949c.209.209.285.624.209.909L4.462 17.55c-.079.285-.208.285-.26 0zm9.202 4.264l-8.217-2.522a.547.547 0 01-.364-.675l1.378-4.421a.547.547 0 01.675-.365l8.216 2.522c.285.079.443.39.364.675L14.08 21.45a.553.553 0 01-.674.365zm7.279-5.98L15.2 20.93c-.209.209-.31.13-.234-.155l1.144-3.694c.079-.285.39-.573.674-.624l3.77-.858c.288-.076.339.054.13.234zm.598-1.09l-4.523 1.039a.534.534 0 01-.65-.39l-1.922-8.372a.534.534 0 01.39-.65L19.1 5.335a.534.534 0 01.649.39l1.923 8.371c.079.31-.102.596-.39.65Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://influxdata.github.io/branding/logo/downloads/",
      "guidelines": "https://influxdata.github.io/branding/logo/usage/"
    }
  },
  {
    "id": "instagram",
    "title": "Instagram",
    "category": "channel",
    "aliases": [],
    "domains": [
      "instagram.com"
    ],
    "viewBox": 24,
    "hex": "FF0069",
    "path": "M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://about.meta.com/brand/resources/instagram",
      "guidelines": "https://about.meta.com/brand/resources/instagram"
    }
  },
  {
    "id": "intercom",
    "title": "Intercom",
    "category": "business",
    "aliases": [],
    "domains": [
      "intercom.com"
    ],
    "viewBox": 24,
    "hex": "6AFDEF",
    "path": "M21 0H3C1.343 0 0 1.343 0 3v18c0 1.658 1.343 3 3 3h18c1.658 0 3-1.342 3-3V3c0-1.657-1.342-3-3-3zm-5.801 4.399c0-.44.36-.8.802-.8.44 0 .8.36.8.8v10.688c0 .442-.36.801-.8.801-.443 0-.802-.359-.802-.801V4.399zM11.2 3.994c0-.44.357-.799.8-.799s.8.359.8.799v11.602c0 .44-.357.8-.8.8s-.8-.36-.8-.8V3.994zm-4 .405c0-.44.359-.8.799-.8.443 0 .802.36.802.8v10.688c0 .442-.36.801-.802.801-.44 0-.799-.359-.799-.801V4.399zM3.199 6c0-.442.36-.8.802-.8.44 0 .799.358.799.8v7.195c0 .441-.359.8-.799.8-.443 0-.802-.36-.802-.8V6zM20.52 18.202c-.123.105-3.086 2.593-8.52 2.593-5.433 0-8.397-2.486-8.521-2.593-.335-.288-.375-.792-.086-1.128.285-.334.79-.375 1.125-.09.047.041 2.693 2.211 7.481 2.211 4.848 0 7.456-2.186 7.479-2.207.334-.289.839-.25 1.128.086.289.336.25.84-.086 1.128zm.281-5.007c0 .441-.36.8-.801.8-.441 0-.801-.36-.801-.8V6c0-.442.361-.8.801-.8.441 0 .801.357.801.8v7.195z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.intercom.com/press",
      "guidelines": "https://www.intercom.com/press"
    }
  },
  {
    "id": "javascript",
    "title": "JavaScript",
    "category": "language",
    "aliases": [
      "js"
    ],
    "domains": [],
    "viewBox": 24,
    "hex": "F7DF1E",
    "path": "M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/voodootikigod/logo.js/blob/1544bdeed6d618a6cfe4f0650d04ab8d9cfa76d9/js.svg",
      "license": {
        "type": "MIT",
        "url": "https://spdx.org/licenses/MIT"
      }
    }
  },
  {
    "id": "jenkins",
    "title": "Jenkins",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "jenkins.io"
    ],
    "viewBox": 24,
    "hex": "D24939",
    "path": "M2.872 24h-.975a3.866 3.866 0 01-.07-.197c-.215-.666-.594-1.49-.692-2.154-.146-.984.78-1.039 1.374-1.465.915-.66 1.635-1.025 2.627-1.62.295-.179 1.182-.624 1.281-.829.201-.408-.345-.982-.49-1.3-.225-.507-.345-.937-.376-1.435-.824-.13-1.455-.627-1.844-1.185-.63-.925-1.066-2.635-.525-3.936.045-.103.254-.305.285-.463.06-.308-.105-.72-.12-1.048-.06-1.692.284-3.15 1.425-3.66.463-1.84 2.113-2.453 3.673-3.367.58-.342 1.224-.562 1.89-.807 2.372-.877 6.027-.712 7.994.783.836.633 2.176 1.97 2.656 2.939 1.262 2.555 1.17 6.825.287 9.934-.12.421-.29 1.032-.533 1.533-.168.35-.689 1.05-.625 1.36.064.314 1.19 1.17 1.432 1.395.434.422 1.26.975 1.324 1.5.07.557-.248 1.336-.41 1.875-.217.721-.436 1.441-.654 2.131H2.87zm11.104-3.54c-.545-.3-1.361-.622-2.065-.757-.87-.164-.78 1.188-.75 1.994.03.643.36 1.316.51 1.744.076.197.09.41.256.449.3.068 1.29-.326 1.575-.479.6-.328 1.064-.844 1.574-1.189.016-.17.016-.34.03-.508a2.648 2.648 0 00-1.095-.277c.314-.15.75-.15 1.035-.332l.016-.193c-.496-.03-.69-.254-1.021-.436zm7.454 2.935a17.78 17.78 0 00.465-1.752c.06-.287.215-.918.178-1.176-.059-.459-.684-.799-1.004-1.086-.584-.525-.95-.975-1.56-1.469-.249.375-.78.615-.983.914 1.447-.689 1.71 2.625 1.141 3.69.09.329.391.45.514.735l-.086.166h1.29c.013 0 .03 0 .044.014zm-6.634-.012c-.05-.074-.1-.135-.15-.209l-.301.195h.45zm2.77 0c.008-.209.018-.404.03-.598-.53.029-.825-.48-1.196-.527-.324-.045-.6.361-1.02.195-.095.105-.183.227-.284.316.154.18.295.375.424.584h.815c.014-.164.135-.285.3-.285.165 0 .284.121.284.27h.66zm2.116 0c-.314-.479-.947-.898-1.68-.555l-.03.541h1.71zm-8.51 0l-.104-.344c-.225-.72-.36-1.26-.405-1.68-.914-.436-1.875-.87-2.654-1.426-.15-.105-1.109-1.35-1.23-1.305-1.739.676-3.359 1.86-4.814 2.984.256.557.48 1.141.69 1.74h8.505zm8.265-2.113c-.029-.512-.164-1.56-.48-1.74-.66-.39-1.846.78-2.34.943.045.15.135.271.15.48.285-.074.645-.029.898.092-.299.03-.629.03-.824.164-.074.195.016.48-.029.764.69.197 1.5.303 2.385.332.164-.227.225-.645.211-1.082zm-4.08-.36c-.044.375.046.51.12.943 1.26.391 1.034-1.74-.135-.959zM8.76 19.5c-.45.457 1.27 1.082 1.814 1.115 0-.29.165-.564.135-.77-.65-.118-1.502-.042-1.945-.347zm5.565.215c0 .043-.061.03-.068.064.58.451 1.014.545 1.802.51.354-.262.67-.563 1.043-.807-.855.074-1.931.607-2.774.23zm3.42-17.726c-1.606-.906-4.35-1.591-6.076-.731-1.38.692-3.27 1.84-3.899 3.292.6 1.402-.166 2.686-.226 4.109-.018.757.36 1.42.391 2.242-.2.338-.825.38-1.26.356-.146-.729-.4-1.549-1.155-1.63-1.064-.116-1.845.764-1.89 1.683-.06 1.08.833 2.864 2.085 2.745.488-.046.608-.54 1.139-.54.285.57-.445.75-.523 1.154-.016.105.06.511.104.705.233.944.744 2.16 1.245 2.88.635.9 1.884 1.051 3.229 1.141.24-.525 1.125-.48 1.706-.346-.691-.27-1.336-.945-1.875-1.529-.615-.676-1.23-1.41-1.261-2.28 1.155 1.604 2.1 3 4.2 3.704 1.59.525 3.45-.254 4.664-1.109.51-.359.811-.93 1.17-1.439 1.35-1.936 1.98-4.71 1.846-7.394-.06-1.111-.06-2.221-.436-2.955-.389-.781-1.695-1.471-2.475-.781-.15-.764.63-1.23 1.545-.96-.66-.854-1.336-1.858-2.266-2.384zM13.58 14.896c.615 1.544 2.724 1.363 4.505 1.323-.084.194-.256.435-.465.515-.57.232-2.145.408-2.937-.012-.506-.27-.824-.873-1.102-1.227-.137-.172-.795-.608-.012-.609zm.164-.87c.893.464 2.52.517 3.731.48.066.267.066.593.068.913-1.55.08-3.386-.304-3.794-1.395h-.005zm6.675-.586c-.473.9-1.145 1.897-2.539 1.928-.023-.284-.045-.735 0-.904 1.064-.103 1.727-.646 2.543-1.017zm-.649-.667c-1.02.66-2.154 1.375-3.824 1.21-.351-.31-.485-1-.14-1.458.181.313.06.885.57.97.944.165 2.038-.579 2.73-.84.42-.713-.046-.976-.42-1.433-.782-.93-1.83-2.1-1.802-3.51.314-.224.346.346.391.45.404.96 1.424 2.175 2.174 3 .18.21.48.39.51.524.092.39-.254.854-.209 1.11zm-13.439-.675c-.314-.184-.393-.99-.768-1.01-.535-.03-.438 1.05-.436 1.68-.37-.33-.435-1.365-.164-1.89-.308-.15-.445.164-.618.284.22-1.59 2.34-.734 1.99.96zM4.713 5.995c-.685.756-.54 2.174-.459 3.188 1.244-.785 2.898.06 2.883 1.394.595-.016.223-.744.115-1.215-.353-1.528.592-3.187.041-4.59-1.064.084-1.939.52-2.578 1.215zm9.12 1.113c.307.562.404 1.148.84 1.57.195.19.574.424.387.95-.045.121-.365.391-.551.45-.674.195-2.254.03-1.721-.81.563.015 1.314.36 1.732-.045-.314-.524-.885-1.53-.674-2.13zm6.198-.013h.068c.33.668.6 1.375 1.004 1.965-.27.628-2.053 1.19-2.023.057.39-.17 1.05-.035 1.395-.25-.193-.556-.48-1.006-.434-1.771zm-6.927-1.617c-1.422-.33-2.131.592-2.56 1.553-.384-.094-.231-.615-.135-.883.255-.701 1.28-1.633 2.119-1.506.359.057.848.386.576.834zM9.642 1.593c-1.56.44-3.56 1.574-4.2 2.974.495-.07.84-.321 1.33-.351.186-.016.428.074.641.015.424-.104.78-1.065 1.102-1.41.31-.345.685-.496.94-.81.167-.09.409-.074.42-.33-.073-.075-.15-.135-.232-.105v.017z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://get.jenkins.io/art/",
      "guidelines": "https://www.jenkins.io/press/",
      "license": {
        "type": "CC-BY-SA-3.0",
        "url": "https://spdx.org/licenses/CC-BY-SA-3.0"
      }
    }
  },
  {
    "id": "jira",
    "title": "Jira",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "atlassian.com"
    ],
    "viewBox": 24,
    "hex": "0052CC",
    "path": "M11.571 11.513H0a5.218 5.218 0 0 0 5.232 5.215h2.13v2.057A5.215 5.215 0 0 0 12.575 24V12.518a1.005 1.005 0 0 0-1.005-1.005zm5.723-5.756H5.736a5.215 5.215 0 0 0 5.215 5.214h2.129v2.058a5.218 5.218 0 0 0 5.215 5.214V6.758a1.001 1.001 0 0 0-1.001-1.001zM23.013 0H11.455a5.215 5.215 0 0 0 5.215 5.215h2.129v2.057A5.215 5.215 0 0 0 24 12.483V1.005A1.001 1.001 0 0 0 23.013 0Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://atlassian.design/resources/logo-library",
      "guidelines": "https://atlassian.design/foundations/logos/"
    }
  },
  {
    "id": "kubernetes",
    "title": "Kubernetes",
    "category": "engineering",
    "aliases": [
      "k8s"
    ],
    "domains": [
      "kubernetes.io"
    ],
    "viewBox": 24,
    "hex": "326CE5",
    "path": "M10.204 14.35l.007.01-.999 2.413a5.171 5.171 0 0 1-2.075-2.597l2.578-.437.004.005a.44.44 0 0 1 .484.606zm-.833-2.129a.44.44 0 0 0 .173-.756l.002-.011L7.585 9.7a5.143 5.143 0 0 0-.73 3.255l2.514-.725.002-.009zm1.145-1.98a.44.44 0 0 0 .699-.337l.01-.005.15-2.62a5.144 5.144 0 0 0-3.01 1.442l2.147 1.523.004-.002zm.76 2.75l.723.349.722-.347.18-.78-.5-.623h-.804l-.5.623.179.779zm1.5-3.095a.44.44 0 0 0 .7.336l.008.003 2.134-1.513a5.188 5.188 0 0 0-2.992-1.442l.148 2.615.002.001zm10.876 5.97l-5.773 7.181a1.6 1.6 0 0 1-1.248.594l-9.261.003a1.6 1.6 0 0 1-1.247-.596l-5.776-7.18a1.583 1.583 0 0 1-.307-1.34L2.1 5.573c.108-.47.425-.864.863-1.073L11.305.513a1.606 1.606 0 0 1 1.385 0l8.345 3.985c.438.209.755.604.863 1.073l2.062 8.955c.108.47-.005.963-.308 1.34zm-3.289-2.057c-.042-.01-.103-.026-.145-.034-.174-.033-.315-.025-.479-.038-.35-.037-.638-.067-.895-.148-.105-.04-.18-.165-.216-.216l-.201-.059a6.45 6.45 0 0 0-.105-2.332 6.465 6.465 0 0 0-.936-2.163c.052-.047.15-.133.177-.159.008-.09.001-.183.094-.282.197-.185.444-.338.743-.522.142-.084.273-.137.415-.242.032-.024.076-.062.11-.089.24-.191.295-.52.123-.736-.172-.216-.506-.236-.745-.045-.034.027-.08.062-.111.088-.134.116-.217.23-.33.35-.246.25-.45.458-.673.609-.097.056-.239.037-.303.033l-.19.135a6.545 6.545 0 0 0-4.146-2.003l-.012-.223c-.065-.062-.143-.115-.163-.25-.022-.268.015-.557.057-.905.023-.163.061-.298.068-.475.001-.04-.001-.099-.001-.142 0-.306-.224-.555-.5-.555-.275 0-.499.249-.499.555l.001.014c0 .041-.002.092 0 .128.006.177.044.312.067.475.042.348.078.637.056.906a.545.545 0 0 1-.162.258l-.012.211a6.424 6.424 0 0 0-4.166 2.003 8.373 8.373 0 0 1-.18-.128c-.09.012-.18.04-.297-.029-.223-.15-.427-.358-.673-.608-.113-.12-.195-.234-.329-.349-.03-.026-.077-.062-.111-.088a.594.594 0 0 0-.348-.132.481.481 0 0 0-.398.176c-.172.216-.117.546.123.737l.007.005.104.083c.142.105.272.159.414.242.299.185.546.338.743.522.076.082.09.226.1.288l.16.143a6.462 6.462 0 0 0-1.02 4.506l-.208.06c-.055.072-.133.184-.215.217-.257.081-.546.11-.895.147-.164.014-.305.006-.48.039-.037.007-.09.02-.133.03l-.004.002-.007.002c-.295.071-.484.342-.423.608.061.267.349.429.645.365l.007-.001.01-.003.129-.029c.17-.046.294-.113.448-.172.33-.118.604-.217.87-.256.112-.009.23.069.288.101l.217-.037a6.5 6.5 0 0 0 2.88 3.596l-.09.218c.033.084.069.199.044.282-.097.252-.263.517-.452.813-.091.136-.185.242-.268.399-.02.037-.045.095-.064.134-.128.275-.034.591.213.71.248.12.556-.007.69-.282v-.002c.02-.039.046-.09.062-.127.07-.162.094-.301.144-.458.132-.332.205-.68.387-.897.05-.06.13-.082.215-.105l.113-.205a6.453 6.453 0 0 0 4.609.012l.106.192c.086.028.18.042.256.155.136.232.229.507.342.84.05.156.074.295.145.457.016.037.043.09.062.129.133.276.442.402.69.282.247-.118.341-.435.213-.71-.02-.039-.045-.096-.065-.134-.083-.156-.177-.261-.268-.398-.19-.296-.346-.541-.443-.793-.04-.13.007-.21.038-.294-.018-.022-.059-.144-.083-.202a6.499 6.499 0 0 0 2.88-3.622c.064.01.176.03.213.038.075-.05.144-.114.28-.104.266.039.54.138.87.256.154.06.277.128.448.173.036.01.088.019.13.028l.009.003.007.001c.297.064.584-.098.645-.365.06-.266-.128-.537-.423-.608zM16.4 9.701l-1.95 1.746v.005a.44.44 0 0 0 .173.757l.003.01 2.526.728a5.199 5.199 0 0 0-.108-1.674A5.208 5.208 0 0 0 16.4 9.7zm-4.013 5.325a.437.437 0 0 0-.404-.232.44.44 0 0 0-.372.233h-.002l-1.268 2.292a5.164 5.164 0 0 0 3.326.003l-1.27-2.296h-.01zm1.888-1.293a.44.44 0 0 0-.27.036.44.44 0 0 0-.214.572l-.003.004 1.01 2.438a5.15 5.15 0 0 0 2.081-2.615l-2.6-.44-.004.005z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/kubernetes/kubernetes/tree/cac53883f4714452f3084a22e4be20d042a9df33/logo"
    }
  },
  {
    "id": "linear",
    "title": "Linear",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "linear.app"
    ],
    "viewBox": 24,
    "hex": "5E6AD2",
    "path": "M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://linear.app"
    }
  },
  {
    "id": "mariadb",
    "title": "MariaDB",
    "category": "data",
    "aliases": [
      "maria-db"
    ],
    "domains": [
      "mariadb.org"
    ],
    "viewBox": 24,
    "hex": "003545",
    "path": "M23.157 4.412c-.676.284-.79.31-1.673.372-.65.045-.757.057-1.212.209-.75.246-1.395.75-2.02 1.59-.296.398-1.249 1.913-1.249 1.988 0 .057-.65.998-.915 1.32-.574.713-1.08 1.079-2.14 1.59-.77.36-1.224.524-4.102 1.477-1.073.353-2.133.738-2.367.864-.852.449-1.515 1.036-2.203 1.938-1.003 1.32-.972 1.313-3.042.947a12.264 12.264 0 00-.675-.063c-.644-.05-1.023.044-1.332.334L0 17.193l.177.088c.094.05.353.234.561.398.215.17.461.347.55.391.088.044.17.088.183.101.012.013-.089.17-.228.353-.435.581-.593.871-.574 1.048.019.164.032.17.43.17.517-.006.826-.056 1.261-.208.65-.233 2.058-.94 2.784-1.4.776-.5 1.717-.998 1.956-1.042.082-.02.354-.07.594-.114.58-.107 1.464-.095 2.587.05.108.013.373.045.6.064.227.025.43.057.454.076.026.012.474.037.998.056.934.026 1.104.007 1.3-.189.126-.133.385-.631.498-.985.209-.643.417-.921.366-.492-.113.966-.322 1.692-.713 2.411-.259.499-.663 1.092-.934 1.395-.322.347-.315.36.088.315.619-.063 1.471-.397 2.096-.82.827-.562 1.647-1.691 2.19-3.03.107-.27.22-.22.183.083-.013.094-.038.315-.057.498l-.031.328.353-.202c.833-.48 1.414-1.262 2.127-2.884.227-.518.877-2.922 1.073-3.976a9.64 9.64 0 01.271-1.042c.127-.429.196-.555.48-.858.183-.19.625-.555.978-.808.72-.505.953-.75 1.187-1.205.208-.417.284-1.13.132-1.357-.132-.202-.284-.196-.763.006Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://mariadb.com/about-us/logos/",
      "guidelines": "https://mariadb.com/about-us/logos/"
    }
  },
  {
    "id": "meta",
    "title": "Meta",
    "category": "ai",
    "aliases": [
      "llama"
    ],
    "domains": [
      "meta.com"
    ],
    "viewBox": 24,
    "hex": "0467DF",
    "path": "M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.meta.com",
      "guidelines": "https://www.facebook.com/brand/resources/meta/company-brand"
    }
  },
  {
    "id": "miro",
    "title": "Miro",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "miro.com"
    ],
    "viewBox": 24,
    "hex": "050038",
    "path": "M17.392 0H13.9L17 4.808 10.444 0H6.949l3.102 6.3L3.494 0H0l3.05 8.131L0 24h3.494L10.05 6.985 6.949 24h3.494L17 5.494 13.899 24h3.493L24 3.672 17.392 0z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://miro.com"
    }
  },
  {
    "id": "mistral-ai",
    "title": "Mistral AI",
    "category": "ai",
    "aliases": [
      "mistral"
    ],
    "domains": [
      "mistral.ai"
    ],
    "viewBox": 24,
    "hex": "FA520F",
    "path": "M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://chat.mistral.ai"
    }
  },
  {
    "id": "mongodb",
    "title": "MongoDB",
    "category": "data",
    "aliases": [
      "mongo"
    ],
    "domains": [
      "mongodb.com"
    ],
    "viewBox": 24,
    "hex": "47A248",
    "path": "M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115-.28-.394-.53-.954-.735-1.44-.036.495-.055.685-.523 1.184-.723.566-4.438 3.682-4.74 10.02-.282 5.912 4.27 9.435 4.888 9.884l.07.05A73.49 73.49 0 0111.91 24h.481c.114-1.032.284-2.056.51-3.07.417-.296.604-.463.85-.693a11.342 11.342 0 003.639-8.464c.01-.814-.103-1.662-.197-2.218zm-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695-.381-.045-.765-1.76-.765-2.405z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.mongodb.com/pressroom"
    }
  },
  {
    "id": "mysql",
    "title": "MySQL",
    "category": "data",
    "aliases": [],
    "domains": [
      "mysql.com"
    ],
    "viewBox": 24,
    "hex": "4479A1",
    "path": "M16.405 5.501c-.115 0-.193.014-.274.033v.013h.014c.054.104.146.18.214.273.054.107.1.214.154.32l.014-.015c.094-.066.14-.172.14-.333-.04-.047-.046-.094-.08-.14-.04-.067-.126-.1-.18-.153zM5.77 18.695h-.927a50.854 50.854 0 00-.27-4.41h-.008l-1.41 4.41H2.45l-1.4-4.41h-.01a72.892 72.892 0 00-.195 4.41H0c.055-1.966.192-3.81.41-5.53h1.15l1.335 4.064h.008l1.347-4.064h1.095c.242 2.015.384 3.86.428 5.53zm4.017-4.08c-.378 2.045-.876 3.533-1.492 4.46-.482.716-1.01 1.073-1.583 1.073-.153 0-.34-.046-.566-.138v-.494c.11.017.24.026.386.026.268 0 .483-.075.647-.222.197-.18.295-.382.295-.605 0-.155-.077-.47-.23-.944L6.23 14.615h.91l.727 2.36c.164.536.233.91.205 1.123.4-1.064.678-2.227.835-3.483zm12.325 4.08h-2.63v-5.53h.885v4.85h1.745zm-3.32.135l-1.016-.5c.09-.076.177-.158.255-.25.433-.506.648-1.258.648-2.253 0-1.83-.718-2.746-2.155-2.746-.704 0-1.254.232-1.65.697-.43.508-.646 1.256-.646 2.245 0 .972.19 1.686.574 2.14.35.41.877.615 1.583.615.264 0 .506-.033.725-.098l1.325.772.36-.622zM15.5 17.588c-.225-.36-.337-.94-.337-1.736 0-1.393.424-2.09 1.27-2.09.443 0 .77.167.977.5.224.362.336.936.336 1.723 0 1.404-.424 2.108-1.27 2.108-.445 0-.77-.167-.978-.5zm-1.658-.425c0 .47-.172.856-.516 1.156-.344.3-.803.45-1.384.45-.543 0-1.064-.172-1.573-.515l.237-.476c.438.22.833.328 1.19.328.332 0 .593-.073.783-.22a.754.754 0 00.3-.615c0-.33-.23-.61-.648-.845-.388-.213-1.163-.657-1.163-.657-.422-.307-.632-.636-.632-1.177 0-.45.157-.81.47-1.085.315-.278.72-.415 1.22-.415.512 0 .98.136 1.4.41l-.213.476a2.726 2.726 0 00-1.064-.23c-.283 0-.502.068-.654.206a.685.685 0 00-.248.524c0 .328.234.61.666.85.393.215 1.187.67 1.187.67.433.305.648.63.648 1.168zm9.382-5.852c-.535-.014-.95.04-1.297.188-.1.04-.26.04-.274.167.055.053.063.14.11.214.08.134.218.313.346.407.14.11.28.216.427.31.26.16.555.255.81.416.145.094.293.213.44.313.073.05.12.14.214.172v-.02c-.046-.06-.06-.147-.105-.214-.067-.067-.134-.127-.2-.193a3.223 3.223 0 00-.695-.675c-.214-.146-.682-.35-.77-.595l-.013-.014c.146-.013.32-.066.46-.106.227-.06.435-.047.67-.106.106-.027.213-.06.32-.094v-.06c-.12-.12-.21-.283-.334-.395a8.867 8.867 0 00-1.104-.823c-.21-.134-.476-.22-.697-.334-.08-.04-.214-.06-.26-.127-.12-.146-.19-.34-.275-.514a17.69 17.69 0 01-.547-1.163c-.12-.262-.193-.523-.34-.763-.69-1.137-1.437-1.826-2.586-2.5-.247-.14-.543-.2-.856-.274-.167-.008-.334-.02-.5-.027-.11-.047-.216-.174-.31-.235-.38-.24-1.364-.76-1.644-.072-.18.434.267.862.422 1.082.115.153.26.328.34.5.047.116.06.235.107.356.106.294.207.622.347.897.073.14.153.287.247.413.054.073.146.107.167.227-.094.136-.1.334-.154.5-.24.757-.146 1.693.194 2.25.107.166.362.534.703.393.3-.12.234-.5.32-.835.02-.08.007-.133.048-.187v.015c.094.188.188.367.274.555.206.328.566.668.867.895.16.12.287.328.487.402v-.02h-.015c-.043-.058-.1-.086-.154-.133a3.445 3.445 0 01-.35-.4 8.76 8.76 0 01-.747-1.218c-.11-.21-.202-.436-.29-.643-.04-.08-.04-.2-.107-.24-.1.146-.247.273-.32.453-.127.288-.14.642-.188 1.01-.027.007-.014 0-.027.014-.214-.052-.287-.274-.367-.46-.2-.475-.233-1.238-.06-1.785.047-.14.247-.582.167-.716-.042-.127-.174-.2-.247-.303a2.478 2.478 0 01-.24-.427c-.16-.374-.24-.788-.414-1.162-.08-.173-.22-.354-.334-.513-.127-.18-.267-.307-.368-.52-.033-.073-.08-.194-.027-.274.014-.054.042-.075.094-.09.088-.072.335.022.422.062.247.1.455.194.662.334.094.066.195.193.315.226h.14c.214.047.455.014.655.073.355.114.675.28.962.46a5.953 5.953 0 012.085 2.286c.08.154.115.295.188.455.14.33.313.663.455.982.14.315.275.636.476.897.1.14.502.213.682.286.133.06.34.115.46.188.23.14.454.3.67.454.11.076.443.243.463.378z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.mysql.com/about/legal/logos.html",
      "guidelines": "https://www.mysql.com/about/legal/logos.html"
    }
  },
  {
    "id": "neon",
    "title": "Neon",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "neon.tech"
    ],
    "viewBox": 24,
    "hex": "34D59A",
    "path": "M24 0V24l-9.365-8.045V24H0V0ZM2.942 21.087h8.751V9.563l9.365 8.204V2.919L2.942 2.914Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://neon.com/brand",
      "guidelines": "https://neon.com/brand"
    }
  },
  {
    "id": "netlify",
    "title": "Netlify",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "netlify.com"
    ],
    "viewBox": 24,
    "hex": "00C7B7",
    "path": "M6.49 19.04h-.23L5.13 17.9v-.23l1.73-1.71h1.2l.15.15v1.2L6.5 19.04ZM5.13 6.31V6.1l1.13-1.13h.23L8.2 6.68v1.2l-.15.15h-1.2L5.13 6.31Zm9.96 9.09h-1.65l-.14-.13v-3.83c0-.68-.27-1.2-1.1-1.23-.42 0-.9 0-1.43.02l-.07.08v4.96l-.14.14H8.9l-.13-.14V8.73l.13-.14h3.7a2.6 2.6 0 0 1 2.61 2.6v4.08l-.13.14Zm-8.37-2.44H.14L0 12.82v-1.64l.14-.14h6.58l.14.14v1.64l-.14.14Zm17.14 0h-6.58l-.14-.14v-1.64l.14-.14h6.58l.14.14v1.64l-.14.14ZM11.05 6.55V1.64l.14-.14h1.65l.14.14v4.9l-.14.14h-1.65l-.14-.13Zm0 15.81v-4.9l.14-.14h1.65l.14.13v4.91l-.14.14h-1.65l-.14-.14Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.netlify.com/press/",
      "guidelines": "https://www.netlify.com/press/"
    }
  },
  {
    "id": "next-js",
    "title": "Next.js",
    "category": "framework",
    "aliases": [
      "nextjs",
      "next.js"
    ],
    "domains": [
      "nextjs.org"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://vercel.com/design/brands#next-js",
      "guidelines": "https://vercel.com/design/brands#next-js"
    }
  },
  {
    "id": "node-js",
    "title": "Node.js",
    "category": "framework",
    "aliases": [
      "node",
      "nodejs"
    ],
    "domains": [
      "nodejs.org"
    ],
    "viewBox": 24,
    "hex": "5FA04E",
    "path": "M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://nodejs.org/en/about/branding",
      "guidelines": "https://nodejs.org/en/about/branding"
    }
  },
  {
    "id": "notion",
    "title": "Notion",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "notion.so"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.notion.so"
    }
  },
  {
    "id": "ollama",
    "title": "Ollama",
    "category": "ai",
    "aliases": [],
    "domains": [
      "ollama.com"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M16.361 10.26a.894.894 0 0 0-.558.47l-.072.148.001.207c0 .193.004.217.059.353.076.193.152.312.291.448.24.238.51.3.872.205a.86.86 0 0 0 .517-.436.752.752 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.06 1.06 0 0 0-.466 0zm-9.203.005c-.305.096-.533.32-.65.639a1.187 1.187 0 0 0-.06.52c.057.309.31.59.598.667.362.095.632.033.872-.205.14-.136.215-.255.291-.448.055-.136.059-.16.059-.353l.001-.207-.072-.148a.894.894 0 0 0-.565-.472 1.02 1.02 0 0 0-.474.007Zm4.184 2c-.131.071-.223.25-.195.383.031.143.157.288.353.407.105.063.112.072.117.136.004.038-.01.146-.029.243-.02.094-.036.194-.036.222.002.074.07.195.143.253.064.052.076.054.255.059.164.005.198.001.264-.03.169-.082.212-.234.15-.525-.052-.243-.042-.28.087-.355.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48.394.394 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053-.053-.032c-.219-.13-.259-.145-.391-.143a.396.396 0 0 0-.193.032zm.39-2.195c-.373.036-.475.05-.654.086-.291.06-.68.195-.951.328-.94.46-1.589 1.226-1.787 2.114-.04.176-.045.234-.045.53 0 .294.005.357.043.524.264 1.16 1.332 2.017 2.714 2.173.3.033 1.596.033 1.896 0 1.11-.125 2.064-.727 2.493-1.571.114-.226.169-.372.22-.602.039-.167.044-.23.044-.523 0-.297-.005-.355-.045-.531-.288-1.29-1.539-2.304-3.072-2.497a6.873 6.873 0 0 0-.855-.031zm.645.937a3.283 3.283 0 0 1 1.44.514c.223.148.537.458.671.662.166.251.26.508.303.82.02.143.01.251-.043.482-.08.345-.332.705-.672.957a3.115 3.115 0 0 1-.689.348c-.382.122-.632.144-1.525.138-.582-.006-.686-.01-.853-.042-.57-.107-1.022-.334-1.35-.68-.264-.28-.385-.535-.45-.946-.03-.192.025-.509.137-.776.136-.326.488-.73.836-.963.403-.269.934-.46 1.422-.512.187-.02.586-.02.773-.002zm-5.503-11a1.653 1.653 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503 0 .544.064 1.24.155 1.721.02.107.031.202.023.208a8.12 8.12 0 0 1-.187.152 5.324 5.324 0 0 0-.949 1.02 5.49 5.49 0 0 0-.94 2.339 6.625 6.625 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195-.037.064c-.269.452-.498 1.105-.605 1.732-.084.496-.095.629-.095 1.294 0 .67.009.803.088 1.266.095.555.288 1.143.503 1.534.071.128.243.393.264.407.007.003-.014.067-.046.141a7.405 7.405 0 0 0-.548 1.873c-.062.417-.071.552-.071.991 0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.915.915 0 0 0-.194-.25 1.74 1.74 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451.124-.486.329-.918.544-1.154a.787.787 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.136 3.136 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834.563-.814 1.353-1.336 2.237-1.475.199-.033.57-.028.776.01.226.04.367.028.512-.041.179-.085.268-.19.374-.431.093-.215.165-.333.36-.576.234-.29.46-.489.822-.729.413-.27.884-.467 1.352-.561.17-.035.25-.04.569-.04.319 0 .398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602.034.057.095.177.132.267.105.241.195.346.374.43.14.068.286.082.503.045.343-.058.607-.053.943.016 1.144.23 2.14 1.173 2.581 2.437.385 1.108.276 2.267-.296 3.153-.097.15-.193.27-.333.419-.301.322-.301.722-.001 1.053.493.539.801 1.866.708 3.036-.062.772-.26 1.463-.533 1.854a2.096 2.096 0 0 1-.224.258.916.916 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295.253 1.008.231 2.01-.059 2.581a.845.845 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074.036-.134c.019-.076.057-.3.088-.516.029-.217.029-1.016 0-1.258-.11-.875-.295-1.57-.597-2.226-.032-.074-.053-.138-.046-.141.008-.005.057-.074.108-.152.376-.569.607-1.284.724-2.228.031-.26.031-1.378 0-1.628-.083-.645-.182-1.082-.348-1.525a6.083 6.083 0 0 0-.329-.7l-.038-.064.131-.194c.402-.604.636-1.262.727-2.04a6.625 6.625 0 0 0-.024-1.358 5.512 5.512 0 0 0-.939-2.339 5.325 5.325 0 0 0-.95-1.02 8.097 8.097 0 0 1-.186-.152.692.692 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503-.19-.924-.535-1.658-.98-2.082-.354-.338-.716-.482-1.15-.455-.996.059-1.8 1.205-2.116 3.01a6.805 6.805 0 0 0-.097.726c0 .036-.007.066-.015.066a.96.96 0 0 1-.149-.078A4.857 4.857 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a.958.958 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a6.71 6.71 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2.096 2.096 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388.03.113.06.244.069.292.007.047.026.152.041.233.067.365.098.76.102 1.24l.002.475-.12.175-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.438 8.438 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711.067-.05.079-.049.157.013zm9.825-.012c.17.126.358.46.498.888.28.854.36 2.028.212 3.145-.019.14-.024.151-.057.144l-.238-.06a3.693 3.693 0 0 0-.954-.124h-.278l-.119-.178-.119-.175.002-.474c.004-.669.066-1.19.214-1.772.157-.623.434-1.185.68-1.382.078-.062.09-.063.159-.012z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/ollama/ollama/issues/2152#issuecomment-1905286922"
    }
  },
  {
    "id": "openai",
    "title": "OpenAI",
    "category": "ai",
    "aliases": [
      "chatgpt",
      "gpt",
      "codex"
    ],
    "domains": [
      "openai.com",
      "chatgpt.com"
    ],
    "viewBox": 20,
    "hex": "000000",
    "path": "M11.248 18.25q-.825 0-1.568-.314a4.3 4.3 0 0 1-1.32-.874 4 4 0 0 1-1.304.214 4 4 0 0 1-2.046-.544 4.27 4.27 0 0 1-1.518-1.485 4 4 0 0 1-.56-2.095q0-.48.131-1.04A4.4 4.4 0 0 1 2.04 10.71a4.07 4.07 0 0 1 .017-3.4 4.2 4.2 0 0 1 1.056-1.418 3.8 3.8 0 0 1 1.6-.842 3.9 3.9 0 0 1 .76-1.683q.593-.759 1.451-1.188a4.04 4.04 0 0 1 1.832-.429q.825 0 1.567.313.742.314 1.32.875a4 4 0 0 1 1.304-.215q1.106 0 2.046.545a4.14 4.14 0 0 1 1.501 1.485q.578.941.578 2.095 0 .48-.132 1.04.66.61 1.023 1.419.363.792.363 1.666 0 .892-.38 1.717a4.3 4.3 0 0 1-1.072 1.435 3.8 3.8 0 0 1-1.584.825 3.8 3.8 0 0 1-.775 1.683 4.06 4.06 0 0 1-1.436 1.188 4.04 4.04 0 0 1-1.832.429m-4.076-2.062q.825 0 1.435-.347l3.103-1.782a.36.36 0 0 0 .164-.313v-1.42L7.881 14.62a.67.67 0 0 1-.726 0l-3.118-1.798a.5.5 0 0 1-.017.115v.198q0 .841.396 1.551.413.693 1.139 1.089a3.2 3.2 0 0 0 1.617.412m.165-2.69a.4.4 0 0 0 .181.05q.083 0 .165-.05l1.238-.71-3.977-2.31a.7.7 0 0 1-.363-.643v-3.58q-.825.362-1.32 1.122a2.9 2.9 0 0 0-.495 1.65q0 .809.413 1.55.412.743 1.072 1.123zm3.91 3.663q.875 0 1.585-.396a2.96 2.96 0 0 0 1.534-2.64v-3.564a.32.32 0 0 0-.165-.297l-1.254-.726v4.604a.7.7 0 0 1-.363.643l-3.119 1.799a3 3 0 0 0 1.783.577m.627-6.039V8.878L10.01 7.822 8.129 8.878v2.244l1.881 1.056zM7.057 5.859a.7.7 0 0 1 .363-.644l3.119-1.798a3 3 0 0 0-1.782-.578q-.874 0-1.584.396A2.96 2.96 0 0 0 6.05 4.324a3.07 3.07 0 0 0-.396 1.551v3.547q0 .199.165.314l1.237.726zm8.383 7.887q.825-.364 1.303-1.123.495-.758.495-1.65a3.15 3.15 0 0 0-.412-1.55q-.413-.743-1.073-1.123l-3.086-1.782q-.099-.065-.181-.049a.3.3 0 0 0-.165.05l-1.238.692 3.993 2.327a.6.6 0 0 1 .264.264.64.64 0 0 1 .1.363zm-3.317-8.382a.63.63 0 0 1 .726 0l3.135 1.831v-.297q0-.792-.396-1.501a2.86 2.86 0 0 0-1.105-1.155q-.71-.43-1.65-.43-.825 0-1.436.347L8.294 5.941a.36.36 0 0 0-.165.314v1.418z",
    "provenance": {
      "provider": "Official brand asset",
      "source": "https://openai.com/brand/",
      "guidelines": "https://openai.com/brand/"
    }
  },
  {
    "id": "openrouter",
    "title": "OpenRouter",
    "category": "ai",
    "aliases": [
      "open-router"
    ],
    "domains": [
      "openrouter.ai"
    ],
    "viewBox": 24,
    "hex": "94A3B8",
    "path": "M16.778 1.844v1.919q-.569-.026-1.138-.032-.708-.008-1.415.037c-1.93.126-4.023.728-6.149 2.237-2.911 2.066-2.731 1.95-4.14 2.75-.396.223-1.342.574-2.185.798-.841.225-1.753.333-1.751.333v4.229s.768.108 1.61.333c.842.224 1.789.575 2.185.799 1.41.798 1.228.683 4.14 2.75 2.126 1.509 4.22 2.11 6.148 2.236.88.058 1.716.041 2.555.005v1.918l7.222-4.168-7.222-4.17v2.176c-.86.038-1.611.065-2.278.021-1.364-.09-2.417-.357-3.979-1.465-2.244-1.593-2.866-2.027-3.68-2.508.889-.518 1.449-.906 3.822-2.59 1.56-1.109 2.614-1.377 3.978-1.466.667-.044 1.418-.017 2.278.02v2.176L24 6.014Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://openrouter.ai"
    }
  },
  {
    "id": "opensearch",
    "title": "OpenSearch",
    "category": "data",
    "aliases": [
      "open-search"
    ],
    "domains": [
      "opensearch.org"
    ],
    "viewBox": 24,
    "hex": "005EB8",
    "path": "M23.1515 8.8125a.8484.8484 0 0 0-.8484.8485c0 6.982-5.6601 12.6421-12.6421 12.6421a.8485.8485 0 0 0 0 1.6969C17.5802 24 24 17.5802 24 9.661a.8485.8485 0 0 0-.8485-.8485Zm-5.121 5.4375c.816-1.3311 1.6051-3.1058 1.4498-5.5905-.3216-5.1468-4.9832-9.0512-9.3851-8.6281C8.372.1971 6.6025 1.6017 6.7598 4.1177c.0683 1.0934.6034 1.7386 1.473 2.2348.8279.4722 1.8914.7713 3.097 1.1104 1.4563.4096 3.1455.8697 4.4438 1.8265 1.5561 1.1467 2.6198 2.4759 2.2569 4.9606Zm-16.561-9C.6535 6.581-.1355 8.3558.0197 10.8405c.3216 5.1468 4.9832 9.0512 9.385 8.6281 1.7233-.1657 3.4927-1.5703 3.3355-4.0863-.0683-1.0934-.6034-1.7386-1.4731-2.2348-.8278-.4722-1.8913-.7713-3.0969-1.1104-1.4563-.4096-3.1455-.8697-4.4438-1.8265-1.5561-1.1467-2.6198-2.476-2.257-4.9606Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://opensearch.org/trademark-brand-policy.html",
      "guidelines": "https://opensearch.org/trademark-brand-policy.html"
    }
  },
  {
    "id": "pagerduty",
    "title": "PagerDuty",
    "category": "engineering",
    "aliases": [
      "pager-duty"
    ],
    "domains": [
      "pagerduty.com"
    ],
    "viewBox": 24,
    "hex": "06AC38",
    "path": "M16.965 1.18C15.085.164 13.769 0 10.683 0H3.73v14.55h6.926c2.743 0 4.8-.164 6.61-1.37 1.975-1.303 3.004-3.484 3.004-6.007 0-2.716-1.262-4.896-3.305-5.994zm-5.5 10.326h-4.21V3.113l3.977-.027c3.62-.028 5.43 1.234 5.43 4.128 0 3.113-2.248 4.292-5.197 4.292zM3.73 17.61h3.525V24H3.73Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.pagerduty.com/brand/",
      "guidelines": "https://www.pagerduty.com/brand/"
    }
  },
  {
    "id": "paypal",
    "title": "PayPal",
    "category": "business",
    "aliases": [],
    "domains": [
      "paypal.com"
    ],
    "viewBox": 24,
    "hex": "002991",
    "path": "M15.607 4.653H8.941L6.645 19.251H1.82L4.862 0h7.995c3.754 0 6.375 2.294 6.473 5.513-.648-.478-2.105-.86-3.722-.86m6.57 5.546c0 3.41-3.01 6.853-6.958 6.853h-2.493L11.595 24H6.74l1.845-11.538h3.592c4.208 0 7.346-3.634 7.153-6.949a5.24 5.24 0 0 1 2.848 4.686M9.653 5.546h6.408c.907 0 1.942.222 2.363.541-.195 2.741-2.655 5.483-6.441 5.483H8.714Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.paypal.com/us",
      "guidelines": "https://newsroom.paypal-corp.com/media-resources"
    }
  },
  {
    "id": "perplexity",
    "title": "Perplexity",
    "category": "ai",
    "aliases": [],
    "domains": [
      "perplexity.ai"
    ],
    "viewBox": 24,
    "hex": "1FB8CD",
    "path": "M22.3977 7.0896h-2.3106V.0676l-7.5094 6.3542V.1577h-1.1554v6.1966L4.4904 0v7.0896H1.6023v10.3976h2.8882V24l6.932-6.3591v6.2005h1.1554v-6.0469l6.9318 6.1807v-6.4879h2.8882V7.0896zm-3.4657-4.531v4.531h-5.355l5.355-4.531zm-13.2862.0676 4.8691 4.4634H5.6458V2.6262zM2.7576 16.332V8.245h7.8476l-6.1149 6.1147v1.9723H2.7576zm2.8882 5.0404v-3.8852h.0001v-2.6488l5.7763-5.7764v7.0111l-5.7764 5.2993zm12.7086.0248-5.7766-5.1509V9.0618l5.7766 5.7766v6.5588zm2.8882-5.0652h-1.733v-1.9723L13.3948 8.245h7.8478v8.087z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.perplexity.ai"
    }
  },
  {
    "id": "pinterest",
    "title": "Pinterest",
    "category": "channel",
    "aliases": [],
    "domains": [
      "pinterest.com"
    ],
    "viewBox": 24,
    "hex": "BD081C",
    "path": "M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://business.pinterest.com/en/brand-guidelines",
      "guidelines": "https://business.pinterest.com/en/brand-guidelines"
    }
  },
  {
    "id": "planetscale",
    "title": "PlanetScale",
    "category": "data",
    "aliases": [
      "planet-scale"
    ],
    "domains": [
      "planetscale.com"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M0 12C0 5.373 5.373 0 12 0c4.873 0 9.067 2.904 10.947 7.077l-15.87 15.87a11.981 11.981 0 0 1-1.935-1.099L14.99 12H12l-8.485 8.485A11.962 11.962 0 0 1 0 12Zm12.004 12L24 12.004C23.998 18.628 18.628 23.998 12.004 24Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://planetscale.com"
    }
  },
  {
    "id": "postgresql",
    "title": "PostgreSQL",
    "category": "data",
    "aliases": [
      "postgres"
    ],
    "domains": [
      "postgresql.org"
    ],
    "viewBox": 24,
    "hex": "4169E1",
    "path": "M23.5594 14.7228a.5269.5269 0 0 0-.0563-.1191c-.139-.2632-.4768-.3418-1.0074-.2321-1.6533.3411-2.2935.1312-2.5256-.0191 1.342-2.0482 2.445-4.522 3.0411-6.8297.2714-1.0507.7982-3.5237.1222-4.7316a1.5641 1.5641 0 0 0-.1509-.235C21.6931.9086 19.8007.0248 17.5099.0005c-1.4947-.0158-2.7705.3461-3.1161.4794a9.449 9.449 0 0 0-.5159-.0816 8.044 8.044 0 0 0-1.3114-.1278c-1.1822-.0184-2.2038.2642-3.0498.8406-.8573-.3211-4.7888-1.645-7.2219.0788C.9359 2.1526.3086 3.8733.4302 6.3043c.0409.818.5069 3.334 1.2423 5.7436.4598 1.5065.9387 2.7019 1.4334 3.582.553.9942 1.1259 1.5933 1.7143 1.7895.4474.1491 1.1327.1441 1.8581-.7279.8012-.9635 1.5903-1.8258 1.9446-2.2069.4351.2355.9064.3625 1.39.3772a.0569.0569 0 0 0 .0004.0041 11.0312 11.0312 0 0 0-.2472.3054c-.3389.4302-.4094.5197-1.5002.7443-.3102.064-1.1344.2339-1.1464.8115-.0025.1224.0329.2309.0919.3268.2269.4231.9216.6097 1.015.6331 1.3345.3335 2.5044.092 3.3714-.6787-.017 2.231.0775 4.4174.3454 5.0874.2212.5529.7618 1.9045 2.4692 1.9043.2505 0 .5263-.0291.8296-.0941 1.7819-.3821 2.5557-1.1696 2.855-2.9059.1503-.8707.4016-2.8753.5388-4.1012.0169-.0703.0357-.1207.057-.1362.0007-.0005.0697-.0471.4272.0307a.3673.3673 0 0 0 .0443.0068l.2539.0223.0149.001c.8468.0384 1.9114-.1426 2.5312-.4308.6438-.2988 1.8057-1.0323 1.5951-1.6698zM2.371 11.8765c-.7435-2.4358-1.1779-4.8851-1.2123-5.5719-.1086-2.1714.4171-3.6829 1.5623-4.4927 1.8367-1.2986 4.8398-.5408 6.108-.13-.0032.0032-.0066.0061-.0098.0094-2.0238 2.044-1.9758 5.536-1.9708 5.7495-.0002.0823.0066.1989.0162.3593.0348.5873.0996 1.6804-.0735 2.9184-.1609 1.1504.1937 2.2764.9728 3.0892.0806.0841.1648.1631.2518.2374-.3468.3714-1.1004 1.1926-1.9025 2.1576-.5677.6825-.9597.5517-1.0886.5087-.3919-.1307-.813-.5871-1.2381-1.3223-.4796-.839-.9635-2.0317-1.4155-3.5126zm6.0072 5.0871c-.1711-.0428-.3271-.1132-.4322-.1772.0889-.0394.2374-.0902.4833-.1409 1.2833-.2641 1.4815-.4506 1.9143-1.0002.0992-.126.2116-.2687.3673-.4426a.3549.3549 0 0 0 .0737-.1298c.1708-.1513.2724-.1099.4369-.0417.156.0646.3078.26.3695.4752.0291.1016.0619.2945-.0452.4444-.9043 1.2658-2.2216 1.2494-3.1676 1.0128zm2.094-3.988-.0525.141c-.133.3566-.2567.6881-.3334 1.003-.6674-.0021-1.3168-.2872-1.8105-.8024-.6279-.6551-.9131-1.5664-.7825-2.5004.1828-1.3079.1153-2.4468.079-3.0586-.005-.0857-.0095-.1607-.0122-.2199.2957-.2621 1.6659-.9962 2.6429-.7724.4459.1022.7176.4057.8305.928.5846 2.7038.0774 3.8307-.3302 4.7363-.084.1866-.1633.3629-.2311.5454zm7.3637 4.5725c-.0169.1768-.0358.376-.0618.5959l-.146.4383a.3547.3547 0 0 0-.0182.1077c-.0059.4747-.054.6489-.115.8693-.0634.2292-.1353.4891-.1794 1.0575-.11 1.4143-.8782 2.2267-2.4172 2.5565-1.5155.3251-1.7843-.4968-2.0212-1.2217a6.5824 6.5824 0 0 0-.0769-.2266c-.2154-.5858-.1911-1.4119-.1574-2.5551.0165-.5612-.0249-1.9013-.3302-2.6462.0044-.2932.0106-.5909.019-.8918a.3529.3529 0 0 0-.0153-.1126 1.4927 1.4927 0 0 0-.0439-.208c-.1226-.4283-.4213-.7866-.7797-.9351-.1424-.059-.4038-.1672-.7178-.0869.067-.276.1831-.5875.309-.9249l.0529-.142c.0595-.16.134-.3257.213-.5012.4265-.9476 1.0106-2.2453.3766-5.1772-.2374-1.0981-1.0304-1.6343-2.2324-1.5098-.7207.0746-1.3799.3654-1.7088.5321a5.6716 5.6716 0 0 0-.1958.1041c.0918-1.1064.4386-3.1741 1.7357-4.4823a4.0306 4.0306 0 0 1 .3033-.276.3532.3532 0 0 0 .1447-.0644c.7524-.5706 1.6945-.8506 2.802-.8325.4091.0067.8017.0339 1.1742.081 1.939.3544 3.2439 1.4468 4.0359 2.3827.8143.9623 1.2552 1.9315 1.4312 2.4543-1.3232-.1346-2.2234.1268-2.6797.779-.9926 1.4189.543 4.1729 1.2811 5.4964.1353.2426.2522.4522.2889.5413.2403.5825.5515.9713.7787 1.2552.0696.087.1372.1714.1885.245-.4008.1155-1.1208.3825-1.0552 1.717-.0123.1563-.0423.4469-.0834.8148-.0461.2077-.0702.4603-.0994.7662zm.8905-1.6211c-.0405-.8316.2691-.9185.5967-1.0105a2.8566 2.8566 0 0 0 .135-.0406 1.202 1.202 0 0 0 .1342.103c.5703.3765 1.5823.4213 3.0068.1344-.2016.1769-.5189.3994-.9533.6011-.4098.1903-1.0957.333-1.7473.3636-.7197.0336-1.0859-.0807-1.1721-.151zm.5695-9.2712c-.0059.3508-.0542.6692-.1054 1.0017-.055.3576-.112.7274-.1264 1.1762-.0142.4368.0404.8909.0932 1.3301.1066.887.216 1.8003-.2075 2.7014a3.5272 3.5272 0 0 1-.1876-.3856c-.0527-.1276-.1669-.3326-.3251-.6162-.6156-1.1041-2.0574-3.6896-1.3193-4.7446.3795-.5427 1.3408-.5661 2.1781-.463zm.2284 7.0137a12.3762 12.3762 0 0 0-.0853-.1074l-.0355-.0444c.7262-1.1995.5842-2.3862.4578-3.4385-.0519-.4318-.1009-.8396-.0885-1.2226.0129-.4061.0666-.7543.1185-1.0911.0639-.415.1288-.8443.1109-1.3505.0134-.0531.0188-.1158.0118-.1902-.0457-.4855-.5999-1.938-1.7294-3.253-.6076-.7073-1.4896-1.4972-2.6889-2.0395.5251-.1066 1.2328-.2035 2.0244-.1859 2.0515.0456 3.6746.8135 4.8242 2.2824a.908.908 0 0 1 .0667.1002c.7231 1.3556-.2762 6.2751-2.9867 10.5405zm-8.8166-6.1162c-.025.1794-.3089.4225-.6211.4225a.5821.5821 0 0 1-.0809-.0056c-.1873-.026-.3765-.144-.5059-.3156-.0458-.0605-.1203-.178-.1055-.2844.0055-.0401.0261-.0985.0925-.1488.1182-.0894.3518-.1226.6096-.0867.3163.0441.6426.1938.6113.4186zm7.9305-.4114c.0111.0792-.049.201-.1531.3102-.0683.0717-.212.1961-.4079.2232a.5456.5456 0 0 1-.075.0052c-.2935 0-.5414-.2344-.5607-.3717-.024-.1765.2641-.3106.5611-.352.297-.0414.6111.0088.6356.1851z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://wiki.postgresql.org/wiki/Logo",
      "guidelines": "https://www.postgresql.org/about/policies/trademarks/"
    }
  },
  {
    "id": "prisma",
    "title": "Prisma",
    "category": "data",
    "aliases": [],
    "domains": [
      "prisma.io"
    ],
    "viewBox": 24,
    "hex": "2D3748",
    "path": "M21.8068 18.2848L13.5528.7565c-.207-.4382-.639-.7273-1.1286-.7541-.5023-.0293-.9523.213-1.2062.6253L2.266 15.1271c-.2773.4518-.2718 1.0091.0158 1.4555l4.3759 6.7786c.2608.4046.7127.6388 1.1823.6388.1332 0 .267-.0188.3987-.0577l12.7019-3.7568c.3891-.1151.7072-.3904.8737-.7553s.1633-.7828-.0075-1.1454zm-1.8481.7519L9.1814 22.2242c-.3292.0975-.6448-.1873-.5756-.5194l3.8501-18.4386c.072-.3448.5486-.3996.699-.0803l7.1288 15.138c.1344.2856-.019.6224-.325.7128z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/prisma/presskit/tree/4bcb64181f266723439d955d60afa1c55fefa715"
    }
  },
  {
    "id": "prometheus",
    "title": "Prometheus",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "prometheus.io"
    ],
    "viewBox": 24,
    "hex": "E6522C",
    "path": "M12 0C5.373 0 0 5.372 0 12c0 6.627 5.373 12 12 12s12-5.373 12-12c0-6.628-5.373-12-12-12zm0 22.46c-1.885 0-3.414-1.26-3.414-2.814h6.828c0 1.553-1.528 2.813-3.414 2.813zm5.64-3.745H6.36v-2.046h11.28v2.046zm-.04-3.098H6.391c-.037-.043-.075-.086-.111-.13-1.155-1.401-1.427-2.133-1.69-2.879-.005-.025 1.4.287 2.395.511 0 0 .513.119 1.262.255-.72-.843-1.147-1.915-1.147-3.01 0-2.406 1.845-4.508 1.18-6.207.648.053 1.34 1.367 1.387 3.422.689-.951.977-2.69.977-3.755 0-1.103.727-2.385 1.454-2.429-.648 1.069.168 1.984.894 4.256.272.854.237 2.29.447 3.201.07-1.892.395-4.652 1.595-5.605-.529 1.2.079 2.702.494 3.424.671 1.164 1.078 2.047 1.078 3.716a4.642 4.642 0 01-1.11 2.996c.792-.149 1.34-.283 1.34-.283l2.573-.502s-.374 1.538-1.81 3.019z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://prometheus.io"
    }
  },
  {
    "id": "pulumi",
    "title": "Pulumi",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "pulumi.com"
    ],
    "viewBox": 24,
    "hex": "8A3391",
    "path": "M11.997 0C10.226 0 8.79.83 8.79 1.856c0 1.025 1.436 1.856 3.207 1.856 1.772 0 3.208-.831 3.208-1.856C15.205.83 13.77 0 11.997 0zM5.95 3.488c-1.772 0-3.208.83-3.208 1.856C2.742 6.369 4.178 7.2 5.95 7.2c1.771 0 3.207-.831 3.207-1.856 0-1.025-1.436-1.856-3.207-1.856zm12.103 0c-1.772 0-3.208.83-3.208 1.856 0 1.025 1.436 1.856 3.208 1.856 1.771 0 3.207-.831 3.207-1.856 0-1.025-1.436-1.856-3.207-1.856zm-6.056 3.495c-1.771 0-3.207.831-3.207 1.856 0 1.025 1.436 1.856 3.207 1.856 1.772 0 3.208-.83 3.208-1.856 0-1.025-1.436-1.856-3.208-1.856zm-10.127.67a1.157 1.157 0 0 0-.55.151c-.888.513-.89 2.172-.004 3.706.886 1.534 2.324 2.362 3.211 1.85.888-.513.89-2.171.003-3.706-.72-1.246-1.803-2.027-2.66-2zm20.257.004c-.857-.026-1.941.754-2.661 2-.886 1.535-.884 3.194.003 3.707.888.512 2.325-.316 3.211-1.85.886-1.534.885-3.193-.003-3.706a1.157 1.157 0 0 0-.55-.15zm-6.048 3.492c-.857-.026-1.94.754-2.66 2-.886 1.535-.885 3.194.003 3.706.887.513 2.325-.316 3.21-1.85.887-1.534.885-3.193-.003-3.706a1.157 1.157 0 0 0-.55-.15zm-8.16.001a1.157 1.157 0 0 0-.55.151c-.888.513-.89 2.172-.004 3.706.886 1.535 2.324 2.363 3.211 1.85.888-.512.89-2.171.003-3.705-.72-1.247-1.803-2.028-2.66-2.002zm-6.047 3.494a1.157 1.157 0 0 0-.55.151c-.888.513-.89 2.172-.004 3.706.886 1.534 2.324 2.362 3.212 1.85.887-.513.888-2.172.003-3.706-.72-1.246-1.804-2.027-2.661-2.001zm20.258.002c-.857-.026-1.941.755-2.66 2.001-.887 1.535-.885 3.193.003 3.706.887.512 2.325-.316 3.21-1.85.886-1.534.885-3.193-.003-3.706a1.157 1.157 0 0 0-.55-.15zm-6.047 3.492c-.858-.026-1.942.754-2.661 2-.886 1.535-.885 3.194.003 3.706.888.513 2.325-.315 3.21-1.85.887-1.533.885-3.193-.002-3.705a1.157 1.157 0 0 0-.55-.151zm-8.163.003a1.157 1.157 0 0 0-.55.151c-.887.513-.889 2.172-.003 3.706.886 1.534 2.323 2.363 3.211 1.85.888-.512.89-2.171.004-3.706-.72-1.246-1.804-2.027-2.662-2z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.pulumi.com",
      "guidelines": "https://www.pulumi.com/brand/"
    }
  },
  {
    "id": "python",
    "title": "Python",
    "category": "language",
    "aliases": [],
    "domains": [
      "python.org"
    ],
    "viewBox": 24,
    "hex": "3776AB",
    "path": "M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.python.org/community/logos/",
      "guidelines": "https://www.python.org/community/logos/"
    }
  },
  {
    "id": "pytorch",
    "title": "PyTorch",
    "category": "framework",
    "aliases": [],
    "domains": [
      "pytorch.org"
    ],
    "viewBox": 24,
    "hex": "EE4C2C",
    "path": "M12.005 0L4.952 7.053a9.865 9.865 0 000 14.022 9.866 9.866 0 0014.022 0c3.984-3.9 3.986-10.205.085-14.023l-1.744 1.743c2.904 2.905 2.904 7.634 0 10.538s-7.634 2.904-10.538 0-2.904-7.634 0-10.538l4.647-4.646.582-.665zm3.568 3.899a1.327 1.327 0 00-1.327 1.327 1.327 1.327 0 001.327 1.328A1.327 1.327 0 0016.9 5.226 1.327 1.327 0 0015.573 3.9z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/pytorch/pytorch.github.io/blob/8f083bd12192ca12d5e1c1f3d236f4831d823d8f/assets/images/logo.svg",
      "guidelines": "https://github.com/pytorch/pytorch.github.io/blob/381117ec296f002b2de475402ef29cca6c55e209/assets/brand-guidelines/PyTorch-Brand-Guidelines.pdf"
    }
  },
  {
    "id": "qwen",
    "title": "QWen",
    "category": "ai",
    "aliases": [],
    "domains": [
      "qwen.ai"
    ],
    "viewBox": 24,
    "hex": "6950EF",
    "path": "M23.919 14.545 20.817 9.17l1.47-2.544a.56.56 0 0 0 0-.566l-1.633-2.83a.57.57 0 0 0-.49-.283h-6.207L12.487.402a.57.57 0 0 0-.49-.284H8.732a.56.56 0 0 0-.49.284L5.139 5.775h-2.94a.56.56 0 0 0-.49.284L.077 8.887a.56.56 0 0 0 0 .567L3.18 14.83l-1.47 2.545a.56.56 0 0 0 0 .566l1.634 2.83a.57.57 0 0 0 .49.283h6.205l1.47 2.545a.57.57 0 0 0 .49.284h3.266a.57.57 0 0 0 .49-.284l3.104-5.375h2.94a.57.57 0 0 0 .49-.283l1.634-2.828a.55.55 0 0 0-.004-.568M8.733.686l1.634 2.828-1.634 2.828H21.8L20.164 9.17H7.425L5.63 6.06Zm1.306 19.801-6.205-.002 1.634-2.83h3.265L2.201 6.344h3.267q3.182 5.517 6.367 11.032zm10.124-5.66L18.53 12l-6.532 11.315-1.634-2.83c2.129-3.673 4.25-7.351 6.373-11.028h3.592l3.102 5.374z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://qwen.ai"
    }
  },
  {
    "id": "rabbitmq",
    "title": "RabbitMQ",
    "category": "data",
    "aliases": [
      "rabbit-mq"
    ],
    "domains": [
      "rabbitmq.com"
    ],
    "viewBox": 24,
    "hex": "FF6600",
    "path": "M23.035 9.601h-7.677a.956.956 0 01-.962-.962V.962a.956.956 0 00-.962-.956H10.56a.956.956 0 00-.962.956V8.64a.956.956 0 01-.962.962H5.762a.956.956 0 01-.961-.962V.962A.956.956 0 003.839 0H.959a.956.956 0 00-.956.962v22.076A.956.956 0 00.965 24h22.07a.956.956 0 00.962-.962V10.58a.956.956 0 00-.962-.98zm-3.86 8.152a1.437 1.437 0 01-1.437 1.443h-1.924a1.437 1.437 0 01-1.436-1.443v-1.917a1.437 1.437 0 011.436-1.443h1.924a1.437 1.437 0 011.437 1.443z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.rabbitmq.com",
      "guidelines": "https://www.rabbitmq.com/trademark-guidelines.html"
    }
  },
  {
    "id": "railway",
    "title": "Railway",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "railway.com",
      "railway.app"
    ],
    "viewBox": 24,
    "hex": "0B0D0E",
    "path": "M.113 10.27A13.026 13.026 0 000 11.48h18.23c-.064-.125-.15-.237-.235-.347-3.117-4.027-4.793-3.677-7.19-3.78-.8-.034-1.34-.048-4.524-.048-1.704 0-3.555.005-5.358.01-.234.63-.459 1.24-.567 1.737h9.342v1.216H.113v.002zm18.26 2.426H.009c.02.326.05.645.094.961h16.955c.754 0 1.179-.429 1.315-.96zm-17.318 4.28s2.81 6.902 10.93 7.024c4.855 0 9.027-2.883 10.92-7.024H1.056zM11.988 0C7.5 0 3.593 2.466 1.531 6.108l4.75-.005v-.002c3.71 0 3.849.016 4.573.047l.448.016c1.563.052 3.485.22 4.996 1.364.82.621 2.007 1.99 2.712 2.965.654.902.842 1.94.396 2.934-.408.914-1.289 1.458-2.353 1.458H.391s.099.42.249.886h22.748A12.026 12.026 0 0024 12.005C24 5.377 18.621 0 11.988 0z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://railway.app"
    }
  },
  {
    "id": "react",
    "title": "React",
    "category": "framework",
    "aliases": [
      "reactjs"
    ],
    "domains": [
      "react.dev"
    ],
    "viewBox": 24,
    "hex": "61DAFB",
    "path": "M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/facebook/create-react-app/blob/282c03f9525fdf8061ffa1ec50dce89296d916bd/test/fixtures/relative-paths/src/logo.svg"
    }
  },
  {
    "id": "reddit",
    "title": "Reddit",
    "category": "channel",
    "aliases": [],
    "domains": [
      "reddit.com"
    ],
    "viewBox": 24,
    "hex": "FF4500",
    "path": "M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199c1.104 0 1.999.895 1.999 1.999 0 1.105-.895 2-1.999 2-.946 0-1.739-.657-1.947-1.539v.002c-1.147.162-2.032 1.15-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363.473-.363 1.064-.58 1.707-.58 1.547 0 2.802 1.254 2.802 2.802 0 1.117-.655 2.081-1.601 2.531-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87-.954-.447-1.614-1.415-1.614-2.538 0-1.548 1.255-2.802 2.803-2.802.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01c0-1.663 1.263-3.034 2.88-3.207.188-.911.993-1.595 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135-.147.015-.241.168-.183.305.483 1.154 1.622 1.964 2.953 1.964 1.33 0 2.47-.81 2.953-1.964.057-.137-.037-.29-.184-.305-.863-.087-1.795-.135-2.769-.135Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.redditinc.com/brand",
      "guidelines": "https://www.redditinc.com/brand"
    }
  },
  {
    "id": "redis",
    "title": "Redis",
    "category": "data",
    "aliases": [],
    "domains": [
      "redis.io"
    ],
    "viewBox": 24,
    "hex": "FF4438",
    "path": "M22.71 13.145c-1.66 2.092-3.452 4.483-7.038 4.483-3.203 0-4.397-2.825-4.48-5.12.701 1.484 2.073 2.685 4.214 2.63 4.117-.133 6.94-3.852 6.94-7.239 0-4.05-3.022-6.972-8.268-6.972-3.752 0-8.4 1.428-11.455 3.685C2.59 6.937 3.885 9.958 4.35 9.626c2.648-1.904 4.748-3.13 6.784-3.744C8.12 9.244.886 17.05 0 18.425c.1 1.261 1.66 4.648 2.424 4.648.232 0 .431-.133.664-.365a100.49 100.49 0 0 0 5.54-6.765c.222 3.104 1.748 6.898 6.014 6.898 3.819 0 7.604-2.756 9.33-8.965.2-.764-.73-1.361-1.261-.73zm-4.349-5.013c0 1.959-1.926 2.922-3.685 2.922-.941 0-1.664-.247-2.235-.568 1.051-1.592 2.092-3.225 3.21-4.973 1.972.334 2.71 1.43 2.71 2.619z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://redis.io/brand-guidelines",
      "guidelines": "https://redis.io/brand-guidelines"
    }
  },
  {
    "id": "render",
    "title": "Render",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "render.com"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M18.263.007c-3.121-.147-5.744 2.109-6.192 5.082-.018.138-.045.272-.067.405-.696 3.703-3.936 6.507-7.827 6.507-1.388 0-2.691-.356-3.825-.979a.2024.2024 0 0 0-.302.178V24H12v-8.999c0-1.656 1.338-3 2.987-3h2.988c3.382 0 6.103-2.817 5.97-6.244-.12-3.084-2.61-5.603-5.682-5.75",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://render.com"
    }
  },
  {
    "id": "replicate",
    "title": "Replicate",
    "category": "ai",
    "aliases": [],
    "domains": [
      "replicate.com"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M24 10.262v2.712h-9.518V24h-3.034V10.262zm0-5.131v2.717H8.755V24H5.722V5.131zM24 0v2.717H3.034V24H0V0z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://replicate.com"
    }
  },
  {
    "id": "rust",
    "title": "Rust",
    "category": "language",
    "aliases": [],
    "domains": [
      "rust-lang.org"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M23.8346 11.7033l-1.0073-.6236a13.7268 13.7268 0 00-.0283-.2936l.8656-.8069a.3483.3483 0 00-.1154-.578l-1.1066-.414a8.4958 8.4958 0 00-.087-.2856l.6904-.9587a.3462.3462 0 00-.2257-.5446l-1.1663-.1894a9.3574 9.3574 0 00-.1407-.2622l.49-1.0761a.3437.3437 0 00-.0274-.3361.3486.3486 0 00-.3006-.154l-1.1845.0416a6.7444 6.7444 0 00-.1873-.2268l.2723-1.153a.3472.3472 0 00-.417-.4172l-1.1532.2724a14.0183 14.0183 0 00-.2278-.1873l.0415-1.1845a.3442.3442 0 00-.49-.328l-1.076.491c-.0872-.0476-.1742-.0952-.2623-.1407l-.1903-1.1673A.3483.3483 0 0016.256.955l-.9597.6905a8.4867 8.4867 0 00-.2855-.086l-.414-1.1066a.3483.3483 0 00-.5781-.1154l-.8069.8666a9.2936 9.2936 0 00-.2936-.0284L12.2946.1683a.3462.3462 0 00-.5892 0l-.6236 1.0073a13.7383 13.7383 0 00-.2936.0284L9.9803.3374a.3462.3462 0 00-.578.1154l-.4141 1.1065c-.0962.0274-.1903.0567-.2855.086L7.744.955a.3483.3483 0 00-.5447.2258L7.009 2.348a9.3574 9.3574 0 00-.2622.1407l-1.0762-.491a.3462.3462 0 00-.49.328l.0416 1.1845a7.9826 7.9826 0 00-.2278.1873L3.8413 3.425a.3472.3472 0 00-.4171.4171l.2713 1.1531c-.0628.075-.1255.1509-.1863.2268l-1.1845-.0415a.3462.3462 0 00-.328.49l.491 1.0761a9.167 9.167 0 00-.1407.2622l-1.1662.1894a.3483.3483 0 00-.2258.5446l.6904.9587a13.303 13.303 0 00-.087.2855l-1.1065.414a.3483.3483 0 00-.1155.5781l.8656.807a9.2936 9.2936 0 00-.0283.2935l-1.0073.6236a.3442.3442 0 000 .5892l1.0073.6236c.008.0982.0182.1964.0283.2936l-.8656.8079a.3462.3462 0 00.1155.578l1.1065.4141c.0273.0962.0567.1914.087.2855l-.6904.9587a.3452.3452 0 00.2268.5447l1.1662.1893c.0456.088.0922.1751.1408.2622l-.491 1.0762a.3462.3462 0 00.328.49l1.1834-.0415c.0618.0769.1235.1528.1873.2277l-.2713 1.1541a.3462.3462 0 00.4171.4161l1.153-.2713c.075.0638.151.1255.2279.1863l-.0415 1.1845a.3442.3442 0 00.49.327l1.0761-.49c.087.0486.1741.0951.2622.1407l.1903 1.1662a.3483.3483 0 00.5447.2268l.9587-.6904a9.299 9.299 0 00.2855.087l.414 1.1066a.3452.3452 0 00.5781.1154l.8079-.8656c.0972.0111.1954.0203.2936.0294l.6236 1.0073a.3472.3472 0 00.5892 0l.6236-1.0073c.0982-.0091.1964-.0183.2936-.0294l.8069.8656a.3483.3483 0 00.578-.1154l.4141-1.1066a8.4626 8.4626 0 00.2855-.087l.9587.6904a.3452.3452 0 00.5447-.2268l.1903-1.1662c.088-.0456.1751-.0931.2622-.1407l1.0762.49a.3472.3472 0 00.49-.327l-.0415-1.1845a6.7267 6.7267 0 00.2267-.1863l1.1531.2713a.3472.3472 0 00.4171-.416l-.2713-1.1542c.0628-.0749.1255-.1508.1863-.2278l1.1845.0415a.3442.3442 0 00.328-.49l-.49-1.076c.0475-.0872.0951-.1742.1407-.2623l1.1662-.1893a.3483.3483 0 00.2258-.5447l-.6904-.9587.087-.2855 1.1066-.414a.3462.3462 0 00.1154-.5781l-.8656-.8079c.0101-.0972.0202-.1954.0283-.2936l1.0073-.6236a.3442.3442 0 000-.5892zm-6.7413 8.3551a.7138.7138 0 01.2986-1.396.714.714 0 11-.2997 1.396zm-.3422-2.3142a.649.649 0 00-.7715.5l-.3573 1.6685c-1.1035.501-2.3285.7795-3.6193.7795a8.7368 8.7368 0 01-3.6951-.814l-.3574-1.6684a.648.648 0 00-.7714-.499l-1.473.3158a8.7216 8.7216 0 01-.7613-.898h7.1676c.081 0 .1356-.0141.1356-.088v-2.536c0-.074-.0536-.0881-.1356-.0881h-2.0966v-1.6077h2.2677c.2065 0 1.1065.0587 1.394 1.2088.0901.3533.2875 1.5044.4232 1.8729.1346.413.6833 1.2381 1.2685 1.2381h3.5716a.7492.7492 0 00.1296-.0131 8.7874 8.7874 0 01-.8119.9526zM6.8369 20.024a.714.714 0 11-.2997-1.396.714.714 0 01.2997 1.396zM4.1177 8.9972a.7137.7137 0 11-1.304.5791.7137.7137 0 011.304-.579zm-.8352 1.9813l1.5347-.6824a.65.65 0 00.33-.8585l-.3158-.7147h1.2432v5.6025H3.5669a8.7753 8.7753 0 01-.2834-3.348zm6.7343-.5437V8.7836h2.9601c.153 0 1.0792.1772 1.0792.8697 0 .575-.7107.7815-1.2948.7815zm10.7574 1.4862c0 .2187-.008.4363-.0243.651h-.9c-.09 0-.1265.0586-.1265.1477v.413c0 .973-.5487 1.1846-1.0296 1.2382-.4576.0517-.9648-.1913-1.0275-.4717-.2704-1.5186-.7198-1.8436-1.4305-2.4034.8817-.5599 1.799-1.386 1.799-2.4915 0-1.1936-.819-1.9458-1.3769-2.3153-.7825-.5163-1.6491-.6195-1.883-.6195H5.4682a8.7651 8.7651 0 014.907-2.7699l1.0974 1.151a.648.648 0 00.9182.0213l1.227-1.1743a8.7753 8.7753 0 016.0044 4.2762l-.8403 1.8982a.652.652 0 00.33.8585l1.6178.7188c.0283.2875.0425.577.0425.8717zm-9.3006-9.5993a.7128.7128 0 11.984 1.0316.7137.7137 0 01-.984-1.0316zm8.3389 6.71a.7107.7107 0 01.9395-.3625.7137.7137 0 11-.9405.3635z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.rust-lang.org",
      "guidelines": "https://www.rust-lang.org/policies/media-guide",
      "license": {
        "type": "CC-BY-SA-4.0",
        "url": "https://spdx.org/licenses/CC-BY-SA-4.0"
      }
    }
  },
  {
    "id": "sentry",
    "title": "Sentry",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "sentry.io"
    ],
    "viewBox": 24,
    "hex": "362D59",
    "path": "M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://sentry.io/branding/"
    }
  },
  {
    "id": "shopify",
    "title": "Shopify",
    "category": "business",
    "aliases": [],
    "domains": [
      "shopify.com"
    ],
    "viewBox": 24,
    "hex": "7AB55C",
    "path": "M15.337 23.979l7.216-1.561s-2.604-17.613-2.625-17.73c-.018-.116-.114-.192-.211-.192s-1.929-.136-1.929-.136-1.275-1.274-1.439-1.411c-.045-.037-.075-.057-.121-.074l-.914 21.104h.023zM11.71 11.305s-.81-.424-1.774-.424c-1.447 0-1.504.906-1.504 1.141 0 1.232 3.24 1.715 3.24 4.629 0 2.295-1.44 3.76-3.406 3.76-2.354 0-3.54-1.465-3.54-1.465l.646-2.086s1.245 1.066 2.28 1.066c.675 0 .975-.545.975-.932 0-1.619-2.654-1.694-2.654-4.359-.034-2.237 1.571-4.416 4.827-4.416 1.257 0 1.875.361 1.875.361l-.945 2.715-.02.01zM11.17.83c.136 0 .271.038.405.135-.984.465-2.064 1.639-2.508 3.992-.656.213-1.293.405-1.889.578C7.697 3.75 8.951.84 11.17.84V.83zm1.235 2.949v.135c-.754.232-1.583.484-2.394.736.466-1.777 1.333-2.645 2.085-2.971.193.501.309 1.176.309 2.1zm.539-2.234c.694.074 1.141.867 1.429 1.755-.349.114-.735.231-1.158.366v-.252c0-.752-.096-1.371-.271-1.871v.002zm2.992 1.289c-.02 0-.06.021-.078.021s-.289.075-.714.21c-.423-1.233-1.176-2.37-2.508-2.37h-.115C12.135.209 11.669 0 11.265 0 8.159 0 6.675 3.877 6.21 5.846c-1.194.365-2.063.636-2.16.674-.675.213-.694.232-.772.87-.075.462-1.83 14.063-1.83 14.063L15.009 24l.927-21.166z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.shopify.com/brand-assets",
      "guidelines": "https://www.shopify.com/brand-assets"
    }
  },
  {
    "id": "snowflake",
    "title": "Snowflake",
    "category": "data",
    "aliases": [],
    "domains": [
      "snowflake.com"
    ],
    "viewBox": 24,
    "hex": "29B5E8",
    "path": "M24 3.459c0 .646-.418 1.18-1.141 1.18-.723 0-1.142-.534-1.142-1.18 0-.647.419-1.18 1.142-1.18.723 0 1.141.533 1.141 1.18zm-.228 0c0-.533-.38-.951-.913-.951s-.913.38-.913.95c0 .533.38.952.913.952.57 0 .913-.419.913-.951zm-1.37-.533h.495c.266 0 .456.152.456.38 0 .153-.076.229-.19.305l.19.266v.038h-.266l-.19-.266h-.229v.266h-.266zm.495.228h-.229v.267h.229c.114 0 .152-.038.152-.114.038-.077-.038-.153-.152-.153zM7.602 12.4c.038-.151.076-.304.076-.456 0-.114-.038-.228-.038-.342-.114-.343-.304-.647-.646-.838l-4.87-2.777c-.685-.38-1.56-.152-1.94.533-.381.685-.153 1.56.532 1.94l2.701 1.56-2.701 1.56c-.685.38-.913 1.256-.533 1.94.38.685 1.256.914 1.94.533l4.832-2.777c.343-.267.571-.533.647-.876zm1.332 2.626c-.266-.038-.57.038-.837.19l-4.832 2.777c-.685.38-.913 1.256-.532 1.94.38.686 1.255.914 1.94.533l2.701-1.56v3.12c0 .8.647 1.408 1.446 1.408.799 0 1.407-.647 1.407-1.408v-5.592c0-.761-.57-1.37-1.293-1.408zm4.946-6.088c.266.038.57-.038.837-.19l4.832-2.777c.685-.38.913-1.256.532-1.94-.38-.686-1.255-.914-1.94-.533l-2.701 1.56V1.975c0-.799-.647-1.408-1.446-1.408-.799 0-1.446.609-1.446 1.408V7.53c0 .76.609 1.37 1.332 1.407zM3.265 5.97l4.832 2.777c.266.152.533.19.837.19.723-.038 1.331-.684 1.331-1.407V1.975c0-.799-.646-1.408-1.407-1.408-.799 0-1.446.647-1.446 1.408v3.12l-2.701-1.56c-.685-.38-1.56-.152-1.94.533-.419.646-.19 1.521.494 1.902zm9.093 6.011a.412.412 0 00-.114-.266l-.57-.571a.346.346 0 00-.267-.114.412.412 0 00-.266.114l-.571.57a.411.411 0 00-.114.267c0 .076.038.19.114.267l.57.57a.345.345 0 00.267.114c.076 0 .19-.038.266-.114l.571-.57a.412.412 0 00.114-.267zm1.598.533L11.94 14.53c-.039.038-.153.114-.229.114h-.608a.411.411 0 01-.267-.114L8.82 12.514a.408.408 0 01-.076-.229v-.608c0-.076.038-.19.114-.267l2.016-2.016a.41.41 0 01.267-.114h.608a.41.41 0 01.267.114l2.016 2.016a.347.347 0 01.114.267v.608c-.076.077-.114.19-.19.229zm5.593 5.44l-4.832-2.777c-.266-.152-.57-.19-.837-.152-.723.038-1.332.684-1.332 1.408v5.554c0 .8.647 1.408 1.408 1.408.799 0 1.446-.647 1.446-1.408v-3.12l2.7 1.56c.686.38 1.561.152 1.941-.533.419-.646.19-1.521-.494-1.94zm2.549-7.533l-2.701 1.56 2.7 1.56c.686.38.914 1.256.533 1.94-.38.685-1.255.913-1.94.533l-4.832-2.778a1.644 1.644 0 01-.647-.798c-.037-.153-.076-.305-.076-.457 0-.114.039-.228.039-.342.114-.343.342-.647.646-.837l4.832-2.778c.685-.38 1.56-.152 1.94.533.457.609.19 1.484-.494 1.864",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.snowflake.com/brand-guidelines/",
      "guidelines": "https://www.snowflake.com/brand-guidelines/"
    }
  },
  {
    "id": "spring",
    "title": "Spring",
    "category": "framework",
    "aliases": [
      "spring-boot"
    ],
    "domains": [
      "spring.io"
    ],
    "viewBox": 24,
    "hex": "6DB33F",
    "path": "M21.8537 1.4158a10.4504 10.4504 0 0 1-1.284 2.2471A11.9666 11.9666 0 1 0 3.8518 20.7757l.4445.3951a11.9543 11.9543 0 0 0 19.6316-8.2971c.3457-3.0126-.568-6.8649-2.0743-11.458zM5.5805 20.8745a1.0174 1.0174 0 1 1-.1482-1.4323 1.0396 1.0396 0 0 1 .1482 1.4323zm16.1991-3.5806c-2.9385 3.9263-9.2601 2.5928-13.2852 2.7904 0 0-.7161.0494-1.4323.1481 0 0 .2717-.1234.6174-.2469 2.8398-.9877 4.1732-1.1853 5.9018-2.0743 3.2349-1.6545 6.4698-5.2844 7.1118-9.0379-1.2347 3.6053-4.9881 6.7167-8.3959 7.9761-2.3459.8643-6.5685 1.7039-6.5685 1.7039l-.1729-.0988c-2.8645-1.4076-2.9632-7.6304 2.2718-9.6306 2.2966-.889 4.4696-.395 6.9637-.9877 2.6422-.6174 5.7043-2.5929 6.939-5.1857 1.3828 4.1732 3.062 10.643.0493 14.6434z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://spring.io/trademarks"
    }
  },
  {
    "id": "sqlite",
    "title": "SQLite",
    "category": "data",
    "aliases": [],
    "domains": [
      "sqlite.org"
    ],
    "viewBox": 24,
    "hex": "003B57",
    "path": "M21.678.521c-1.032-.92-2.28-.55-3.513.544a8.71 8.71 0 0 0-.547.535c-2.109 2.237-4.066 6.38-4.674 9.544.237.48.422 1.093.544 1.561a13.044 13.044 0 0 1 .164.703s-.019-.071-.096-.296l-.05-.146a1.689 1.689 0 0 0-.033-.08c-.138-.32-.518-.995-.686-1.289-.143.423-.27.818-.376 1.176.484.884.778 2.4.778 2.4s-.025-.099-.147-.442c-.107-.303-.644-1.244-.772-1.464-.217.804-.304 1.346-.226 1.478.152.256.296.698.422 1.186.286 1.1.485 2.44.485 2.44l.017.224a22.41 22.41 0 0 0 .056 2.748c.095 1.146.273 2.13.5 2.657l.155-.084c-.334-1.038-.47-2.399-.41-3.967.09-2.398.642-5.29 1.661-8.304 1.723-4.55 4.113-8.201 6.3-9.945-1.993 1.8-4.692 7.63-5.5 9.788-.904 2.416-1.545 4.684-1.931 6.857.666-2.037 2.821-2.912 2.821-2.912s1.057-1.304 2.292-3.166c-.74.169-1.955.458-2.362.629-.6.251-.762.337-.762.337s1.945-1.184 3.613-1.72C21.695 7.9 24.195 2.767 21.678.521m-18.573.543A1.842 1.842 0 0 0 1.27 2.9v16.608a1.84 1.84 0 0 0 1.835 1.834h9.418a22.953 22.953 0 0 1-.052-2.707c-.006-.062-.011-.141-.016-.2a27.01 27.01 0 0 0-.473-2.378c-.121-.47-.275-.898-.369-1.057-.116-.197-.098-.31-.097-.432 0-.12.015-.245.037-.386a9.98 9.98 0 0 1 .234-1.045l.217-.028c-.017-.035-.014-.065-.031-.097l-.041-.381a32.8 32.8 0 0 1 .382-1.194l.2-.019c-.008-.016-.01-.038-.018-.053l-.043-.316c.63-3.28 2.587-7.443 4.8-9.791.066-.069.133-.128.198-.194Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/sqlite/sqlite/blob/43e862723ec680542ca6f608f9963c0993dd7324/art/sqlite370.eps"
    }
  },
  {
    "id": "stripe",
    "title": "Stripe",
    "category": "business",
    "aliases": [],
    "domains": [
      "stripe.com"
    ],
    "viewBox": 24,
    "hex": "635BFF",
    "path": "M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://stripe.com/newsroom/information"
    }
  },
  {
    "id": "supabase",
    "title": "Supabase",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "supabase.com"
    ],
    "viewBox": 24,
    "hex": "3FCF8E",
    "path": "M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/supabase/supabase/blob/4031a7549f5d46da7bc79c01d56be4177dc7c114/packages/common/assets/images/supabase-logo-wordmark--light.svg"
    }
  },
  {
    "id": "svelte",
    "title": "Svelte",
    "category": "framework",
    "aliases": [],
    "domains": [
      "svelte.dev"
    ],
    "viewBox": 24,
    "hex": "FF3E00",
    "path": "M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/sveltejs/branding/blob/c4dfca6743572087a6aef0e109ffe3d95596e86a/svelte-logo.svg"
    }
  },
  {
    "id": "telegram",
    "title": "Telegram",
    "category": "channel",
    "aliases": [],
    "domains": [
      "telegram.org",
      "t.me"
    ],
    "viewBox": 24,
    "hex": "26A5E4",
    "path": "M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://telegram.org/tour/screenshots"
    }
  },
  {
    "id": "tensorflow",
    "title": "TensorFlow",
    "category": "framework",
    "aliases": [],
    "domains": [
      "tensorflow.org"
    ],
    "viewBox": 24,
    "hex": "FF6F00",
    "path": "M1.292 5.856L11.54 0v24l-4.095-2.378V7.603l-6.168 3.564.015-5.31zm21.43 5.311l-.014-5.31L12.46 0v24l4.095-2.378V14.87l3.092 1.788-.018-4.618-3.074-1.756V7.603l6.168 3.564z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.tensorflow.org"
    }
  },
  {
    "id": "terraform",
    "title": "Terraform",
    "category": "engineering",
    "aliases": [],
    "domains": [
      "terraform.io"
    ],
    "viewBox": 24,
    "hex": "844FBA",
    "path": "M1.44 0v7.575l6.561 3.79V3.787zm21.12 4.227l-6.561 3.791v7.574l6.56-3.787zM8.72 4.23v7.575l6.561 3.787V8.018zm0 8.405v7.575L15.28 24v-7.578z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.hashicorp.com/brand",
      "guidelines": "https://www.hashicorp.com/brand"
    }
  },
  {
    "id": "tiktok",
    "title": "TikTok",
    "category": "channel",
    "aliases": [
      "douyin",
      "抖音"
    ],
    "domains": [
      "tiktok.com",
      "douyin.com"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://tiktok.com"
    }
  },
  {
    "id": "trello",
    "title": "Trello",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "trello.com"
    ],
    "viewBox": 24,
    "hex": "0052CC",
    "path": "M21.147 0H2.853A2.86 2.86 0 000 2.853v18.294A2.86 2.86 0 002.853 24h18.294A2.86 2.86 0 0024 21.147V2.853A2.86 2.86 0 0021.147 0zM10.34 17.287a.953.953 0 01-.953.953h-4a.954.954 0 01-.954-.953V5.38a.953.953 0 01.954-.953h4a.954.954 0 01.953.953zm9.233-5.467a.944.944 0 01-.953.947h-4a.947.947 0 01-.953-.947V5.38a.953.953 0 01.953-.953h4a.954.954 0 01.953.953z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://atlassian.design/resources/logo-library",
      "guidelines": "https://atlassian.design/foundations/logos"
    }
  },
  {
    "id": "typescript",
    "title": "TypeScript",
    "category": "language",
    "aliases": [
      "ts"
    ],
    "domains": [
      "typescriptlang.org"
    ],
    "viewBox": 24,
    "hex": "3178C6",
    "path": "M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.typescriptlang.org/branding",
      "guidelines": "https://www.typescriptlang.org/branding"
    }
  },
  {
    "id": "vercel",
    "title": "Vercel",
    "category": "cloud",
    "aliases": [],
    "domains": [
      "vercel.com"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "m12 1.608 12 20.784H0Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://vercel.com/geist/brands",
      "guidelines": "https://vercel.com/geist/brands"
    }
  },
  {
    "id": "vue",
    "title": "Vue.js",
    "category": "framework",
    "aliases": [
      "vuejs",
      "vue.js"
    ],
    "domains": [
      "vuejs.org"
    ],
    "viewBox": 24,
    "hex": "4FC08D",
    "path": "M24,1.61H14.06L12,5.16,9.94,1.61H0L12,22.39ZM12,14.08,5.16,2.23H9.59L12,6.41l2.41-4.18h4.43Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://github.com/vuejs/art/blob/a1c78b74569b70a25300925b4eacfefcc143b8f6/logo.svg",
      "guidelines": "https://github.com/vuejs/art/blob/a1c78b74569b70a25300925b4eacfefcc143b8f6/README.md",
      "license": {
        "type": "CC-BY-NC-SA-4.0",
        "url": "https://spdx.org/licenses/CC-BY-NC-SA-4.0"
      }
    }
  },
  {
    "id": "wechat",
    "title": "WeChat",
    "category": "channel",
    "aliases": [
      "weixin",
      "微信"
    ],
    "domains": [
      "weixin.qq.com"
    ],
    "viewBox": 24,
    "hex": "07C160",
    "path": "M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://wechat.design/tool/brand",
      "guidelines": "https://wechat.design/brand/main-brand"
    }
  },
  {
    "id": "whatsapp",
    "title": "WhatsApp",
    "category": "channel",
    "aliases": [],
    "domains": [
      "whatsapp.com"
    ],
    "viewBox": 24,
    "hex": "25D366",
    "path": "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://about.meta.com/brand/resources/whatsapp/whatsapp-brand",
      "guidelines": "https://about.meta.com/brand/resources/whatsapp/whatsapp-brand"
    }
  },
  {
    "id": "woocommerce",
    "title": "WooCommerce",
    "category": "business",
    "aliases": [
      "woo-commerce"
    ],
    "domains": [
      "woocommerce.com"
    ],
    "viewBox": 24,
    "hex": "96588A",
    "path": "M.754 9.58a.754.754 0 00-.754.758v2.525c0 .42.339.758.758.758h3.135l1.431.799-.326-.799h2.373a.757.757 0 00.758-.758v-2.525a.757.757 0 00-.758-.758H.754zm2.709.445h.03c.065.001.124.023.179.067a.26.26 0 01.103.19.29.29 0 01-.033.16c-.13.239-.236.64-.322 1.199-.083.541-.114.965-.094 1.267a.392.392 0 01-.039.219.213.213 0 01-.176.12c-.086.006-.177-.034-.263-.124-.31-.316-.555-.788-.735-1.416-.216.425-.375.744-.478.957-.196.376-.363.568-.502.578-.09.007-.166-.069-.233-.228-.17-.436-.352-1.277-.548-2.524a.297.297 0 01.054-.222c.047-.064.116-.095.21-.102.169-.013.265.065.288.238.103.695.217 1.284.336 1.766l.727-1.387c.066-.126.15-.192.25-.199.146-.01.237.083.273.28.083.441.188.817.315 1.136.086-.844.233-1.453.44-1.828a.255.255 0 01.218-.147zm1.293.36c.056 0 .116.006.18.02.232.05.411.177.53.386.107.18.161.395.161.654 0 .343-.087.654-.26.94-.2.332-.459.5-.781.5a.88.88 0 01-.18-.022.763.763 0 01-.531-.384 1.287 1.287 0 01-.158-.659c0-.342.085-.655.258-.937.202-.333.462-.498.78-.498zm2.084 0c.056 0 .116.006.18.02.236.05.411.177.53.386.107.18.16.395.16.654 0 .343-.086.654-.259.94-.2.332-.459.5-.781.5a.88.88 0 01-.18-.022.763.763 0 01-.531-.384 1.287 1.287 0 01-.16-.659c0-.342.087-.655.26-.937.202-.333.462-.498.78-.498zm4.437.047c-.305 0-.546.102-.718.304-.173.203-.256.49-.256.856 0 .395.086.697.256.906.17.21.418.316.744.316.315 0 .559-.107.728-.316.17-.21.256-.504.256-.883s-.087-.673-.26-.879c-.176-.202-.424-.304-.75-.304zm-1.466.002a1.13 1.13 0 00-.84.326c-.223.22-.332.499-.332.838 0 .362.108.658.328.88.22.223.505.336.861.336.103 0 .22-.016.346-.052v-.54c-.117.034-.216.051-.303.051a.545.545 0 01-.422-.177c-.106-.12-.16-.278-.16-.48 0-.19.053-.348.156-.468a.498.498 0 01.397-.181c.103 0 .212.015.332.049v-.537a1.394 1.394 0 00-.363-.045zm12.414 0a1.135 1.135 0 00-.84.326c-.223.22-.332.499-.332.838 0 .362.108.658.328.88.22.223.506.336.861.336.103 0 .22-.016.346-.052v-.54c-.116.034-.216.051-.303.051a.545.545 0 01-.422-.177c-.106-.12-.16-.278-.16-.48 0-.19.053-.348.156-.468a.498.498 0 01.397-.181c.103 0 .212.015.332.049v-.537a1.394 1.394 0 00-.363-.045zm-9.598.06l-.29 2.264h.579l.156-1.559.395 1.559h.412l.379-1.555.164 1.555h.603l-.304-2.264h-.791l-.12.508c-.03.13-.06.264-.087.4l-.067.352a29.97 29.97 0 00-.258-1.26h-.771zm2.768 0l-.29 2.264h.579l.156-1.559.396 1.559h.412l.375-1.555.165 1.555h.603l-.305-2.264h-.789l-.119.508c-.03.13-.06.264-.086.4l-.066.352c-.063-.352-.15-.771-.26-1.26h-.771zm3.988 0v2.264h.611v-1.031h.012l.494 1.03h.645l-.489-1.019a.61.61 0 00.37-.552.598.598 0 00-.25-.506c-.167-.123-.394-.186-.68-.186h-.713zm3.377 0v2.264H24v-.483h-.63v-.414h.54v-.468h-.54v-.416h.626v-.483H22.76zm-4.793.004v2.264h1.24v-.483h-.627v-.416h.541v-.468h-.54v-.415h.622v-.482h-1.236zm2.025.432c.146.003.25.025.313.072.063.046.091.12.091.227 0 .156-.135.236-.404.24v-.54zm-15.22.011c-.104 0-.205.069-.301.211a1.078 1.078 0 00-.2.639c0 .096.02.2.06.303.049.13.117.198.196.215.083.016.173-.02.27-.106.123-.11.205-.273.252-.492.016-.077.023-.16.023-.246 0-.097-.02-.2-.06-.303-.05-.13-.116-.198-.196-.215a.246.246 0 00-.045-.006zm2.083 0c-.103 0-.204.069-.3.211a1.078 1.078 0 00-.2.639c0 .096.02.2.06.303.049.13.117.198.196.215.083.016.173-.02.27-.106.123-.11.205-.273.252-.492.013-.077.023-.16.023-.246 0-.097-.02-.2-.06-.303-.05-.13-.116-.198-.196-.215a.246.246 0 00-.045-.006zm4.428.006c.233 0 .354.218.354.66-.004.273-.038.46-.098.553a.293.293 0 01-.262.139.266.266 0 01-.242-.139c-.056-.093-.084-.28-.084-.562 0-.436.11-.65.332-.65Z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://woocommerce.com/style-guide",
      "guidelines": "https://woocommerce.com/trademark-guidelines"
    }
  },
  {
    "id": "wordpress",
    "title": "WordPress",
    "category": "business",
    "aliases": [],
    "domains": [
      "wordpress.org",
      "wordpress.com"
    ],
    "viewBox": 24,
    "hex": "21759B",
    "path": "M21.469 6.825c.84 1.537 1.318 3.3 1.318 5.175 0 3.979-2.156 7.456-5.363 9.325l3.295-9.527c.615-1.54.82-2.771.82-3.864 0-.405-.026-.78-.07-1.11m-7.981.105c.647-.03 1.232-.105 1.232-.105.582-.075.514-.93-.067-.899 0 0-1.755.135-2.88.135-1.064 0-2.85-.15-2.85-.15-.585-.03-.661.855-.075.885 0 0 .54.061 1.125.09l1.68 4.605-2.37 7.08L5.354 6.9c.649-.03 1.234-.1 1.234-.1.585-.075.516-.93-.065-.896 0 0-1.746.138-2.874.138-.2 0-.438-.008-.69-.015C4.911 3.15 8.235 1.215 12 1.215c2.809 0 5.365 1.072 7.286 2.833-.046-.003-.091-.009-.141-.009-1.06 0-1.812.923-1.812 1.914 0 .89.513 1.643 1.06 2.531.411.72.89 1.643.89 2.977 0 .915-.354 1.994-.821 3.479l-1.075 3.585-3.9-11.61.001.014zM12 22.784c-1.059 0-2.081-.153-3.048-.437l3.237-9.406 3.315 9.087c.024.053.05.101.078.149-1.12.393-2.325.609-3.582.609M1.211 12c0-1.564.336-3.05.935-4.39L7.29 21.709C3.694 19.96 1.212 16.271 1.211 12M12 0C5.385 0 0 5.385 0 12s5.385 12 12 12 12-5.385 12-12S18.615 0 12 0",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://wordpress.org/about/logos",
      "guidelines": "https://wordpressfoundation.org/trademark-policy"
    }
  },
  {
    "id": "x",
    "title": "X",
    "category": "channel",
    "aliases": [
      "twitter"
    ],
    "domains": [
      "x.com",
      "twitter.com"
    ],
    "viewBox": 24,
    "hex": "000000",
    "path": "M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://x.com",
      "guidelines": "https://about.x.com/en/who-we-are/brand-toolkit"
    }
  },
  {
    "id": "youtube",
    "title": "YouTube",
    "category": "channel",
    "aliases": [],
    "domains": [
      "youtube.com",
      "youtu.be"
    ],
    "viewBox": 24,
    "hex": "FF0000",
    "path": "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors",
      "guidelines": "https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors"
    }
  },
  {
    "id": "zendesk",
    "title": "Zendesk",
    "category": "business",
    "aliases": [],
    "domains": [
      "zendesk.com"
    ],
    "viewBox": 24,
    "hex": "03363D",
    "path": "M12.914 2.904V16.29L24 2.905H12.914zM0 2.906C0 5.966 2.483 8.45 5.543 8.45s5.542-2.484 5.543-5.544H0zm11.086 4.807L0 21.096h11.086V7.713zm7.37 7.84c-3.063 0-5.542 2.48-5.542 5.543H24c0-3.06-2.48-5.543-5.543-5.543z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://brandland.zendesk.com",
      "guidelines": "https://brandland.zendesk.com"
    }
  },
  {
    "id": "zoom",
    "title": "Zoom",
    "category": "collaboration",
    "aliases": [],
    "domains": [
      "zoom.us"
    ],
    "viewBox": 24,
    "hex": "0B5CFF",
    "path": "M5.033 14.649H.743a.74.74 0 0 1-.686-.458.74.74 0 0 1 .16-.808L3.19 10.41H1.06A1.06 1.06 0 0 1 0 9.35h3.957c.301 0 .57.18.686.458a.74.74 0 0 1-.161.808L1.51 13.59h2.464c.585 0 1.06.475 1.06 1.06zM24 11.338c0-1.14-.927-2.066-2.066-2.066-.61 0-1.158.265-1.537.686a2.061 2.061 0 0 0-1.536-.686c-1.14 0-2.066.926-2.066 2.066v3.311a1.06 1.06 0 0 0 1.06-1.06v-2.251a1.004 1.004 0 0 1 2.013 0v2.251c0 .586.474 1.06 1.06 1.06v-3.311a1.004 1.004 0 0 1 2.012 0v2.251c0 .586.475 1.06 1.06 1.06zM16.265 12a2.728 2.728 0 1 1-5.457 0 2.728 2.728 0 0 1 5.457 0zm-1.06 0a1.669 1.669 0 1 0-3.338 0 1.669 1.669 0 0 0 3.338 0zm-4.82 0a2.728 2.728 0 1 1-5.458 0 2.728 2.728 0 0 1 5.457 0zm-1.06 0a1.669 1.669 0 1 0-3.338 0 1.669 1.669 0 0 0 3.338 0z",
    "provenance": {
      "provider": "Simple Icons",
      "providerVersion": "16.28.0",
      "source": "https://brand.zoom.us/media-library/",
      "guidelines": "https://brand.zoom.us/usage-legal/"
    }
  }
]);
```

## renderers/shared/generated-validators.mjs

```js
// Generated by scripts/generate-validators.mjs. Do not edit by hand.
"use strict";export const workflow = validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/tt-a1i/archify/schemas/workflow.schema.json","title":"Archify Workflow Diagram","type":"object","additionalProperties":false,"required":["schema_version","diagram_type","meta","lanes","nodes","edges"],"properties":{"schema_version":{"enum":[1,2]},"diagram_type":{"const":"workflow"},"meta":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","minLength":1},"locale":{"$ref":"common.schema.json#/$defs/locale"},"subtitle":{"type":"string"},"output":{"type":"string"},"animation":{"enum":["trace","none"]},"visual_preset":{"enum":["classic","signal-flow","blueprint","editorial"]},"quality_profile":{"enum":["standard","showcase"]},"views":{"$ref":"common.schema.json#/$defs/guidedViews"},"legend":{"type":"object","additionalProperties":false,"properties":{"mode":{"$ref":"common.schema.json#/$defs/legendMode"},"entries":{"type":"object","additionalProperties":false,"properties":{"frontend":{"$ref":"common.schema.json#/$defs/legendEntry"},"backend":{"$ref":"common.schema.json#/$defs/legendEntry"},"database":{"$ref":"common.schema.json#/$defs/legendEntry"},"cloud":{"$ref":"common.schema.json#/$defs/legendEntry"},"security":{"$ref":"common.schema.json#/$defs/legendEntry"},"messagebus":{"$ref":"common.schema.json#/$defs/legendEntry"},"external":{"$ref":"common.schema.json#/$defs/legendEntry"}}}}},"viewBox":{"type":"array","prefixItems":[{"type":"number","minimum":700},{"type":"number","minimum":240}],"items":false,"minItems":2,"maxItems":2}}},"lanes":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["id","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"variant":{"enum":["normal","exception"]}}}},"phases":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["id","label","fromCol","toCol"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"fromCol":{"type":"integer","minimum":0,"maximum":5},"toCol":{"type":"integer","minimum":0,"maximum":5},"variant":{"enum":["default","emphasis","security","dashed"]}}}},"groups":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["id","label","lane","fromCol","toCol"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"lane":{"$ref":"common.schema.json#/$defs/id"},"fromCol":{"type":"integer","minimum":0,"maximum":5},"toCol":{"type":"integer","minimum":0,"maximum":5},"variant":{"enum":["default","emphasis","security","dashed"]}}}},"mainPath":{"type":"array","minItems":2,"items":{"$ref":"common.schema.json#/$defs/id"}},"semanticChecks":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"allowedRoots":{"type":"array","items":{"$ref":"common.schema.json#/$defs/id"}},"allowedTerminals":{"type":"array","items":{"$ref":"common.schema.json#/$defs/id"}},"requiredEdges":{"type":"array","items":{"$ref":"#/$defs/semanticRelation"}},"requiredPaths":{"type":"array","items":{"$ref":"#/$defs/semanticRelation"}}}},"nodes":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["id","lane","col","type","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"lane":{"$ref":"common.schema.json#/$defs/id"},"col":{"type":"integer","minimum":0,"maximum":5},"type":{"$ref":"common.schema.json#/$defs/componentType"},"label":{"type":"string","minLength":1},"sublabel":{"type":"string"},"tag":{"type":"string"},"brand":{"$ref":"common.schema.json#/$defs/brandMark"},"width":{"type":"number","minimum":32},"height":{"type":"number","minimum":32},"yOffset":{"type":"number"}}}},"edges":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string"},"variant":{"$ref":"common.schema.json#/$defs/variant"},"role":{"enum":["main","branch","async","return","error"]},"fromSide":{"$ref":"#/$defs/side"},"toSide":{"$ref":"#/$defs/side"},"route":{"enum":["auto","straight","drop","outside-right","return-left","bottom-channel","up-channel"]},"via":{"type":"array","items":{"$ref":"common.schema.json#/$defs/point"}},"labelAt":{"$ref":"common.schema.json#/$defs/point"},"labelDx":{"type":"number"},"labelDy":{"type":"number"},"labelSegment":{"type":"integer","minimum":0},"channelX":{"type":"number"},"channelY":{"type":"number"},"bias":{"type":"number","minimum":0,"maximum":1},"width":{"type":"number","minimum":0.5}}}},"cards":{"$ref":"common.schema.json#/$defs/cards"}},"$defs":{"semanticRelation":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"}}},"side":{"enum":["left","right","top","bottom"]}}};const schema33 = {"enum":["en","zh-CN"]};const schema37 = {"enum":["auto","all","hidden"]};const schema38 = {"type":"object","additionalProperties":false,"minProperties":1,"properties":{"label":{"type":"string","minLength":1,"maxLength":80},"visible":{"type":"boolean"}}};const schema35 = {"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9_-]*$"};const schema57 = {"enum":["frontend","backend","database","cloud","security","messagebus","external"]};const schema58 = {"oneOf":[{"type":"string","minLength":1,"maxLength":2048,"anyOf":[{"maxLength":80,"pattern":"^[^\\r\\n]+$"},{"pattern":"^https?://"}]},{"type":"object","additionalProperties":false,"required":["url","sha256"],"properties":{"url":{"type":"string","minLength":8,"maxLength":2048,"pattern":"^https?://"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}}}]};const schema62 = {"enum":["default","emphasis","security","dashed"]};const schema63 = {"enum":["left","right","top","bottom"]};const schema65 = {"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2};const schema67 = {"type":"array","items":{"type":"object","additionalProperties":false,"required":["dot","title","items"],"properties":{"dot":{"enum":["cyan","emerald","violet","amber","rose","orange","slate"]},"title":{"type":"string","minLength":1},"items":{"type":"array","items":{"type":"string"}}}}};const func1 = Object.prototype.hasOwnProperty;const func3 = function ucs2length(str) {
  const len = str.length;
  let length = 0;
  let pos = 0;
  while (pos < len) {
    length += 1;
    const value = str.charCodeAt(pos++);
    if (value >= 0xd800 && value <= 0xdbff && pos < len
      && (str.charCodeAt(pos) & 0xfc00) === 0xdc00) pos += 1;
  }
  return length;
};const schema34 = {"type":"array","maxItems":5,"items":{"type":"object","additionalProperties":false,"required":["id","label","focus"],"properties":{"id":{"$ref":"#/$defs/id"},"label":{"type":"string","minLength":1,"maxLength":48},"focus":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/id"}},"note":{"type":"string","maxLength":140}}}};const pattern4 = new RegExp("^[a-zA-Z][a-zA-Z0-9_-]*$", "u");function validate22(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate22.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(Array.isArray(data)){if(data.length > 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0<len0; i0++){let data0 = data[i0];if(data0 && typeof data0 == "object" && !Array.isArray(data0)){if(data0.id === undefined){const err1 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data0.label === undefined){const err2 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data0.focus === undefined){const err3 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "focus"},message:"must have required property '"+"focus"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data0){if(!((((key0 === "id") || (key0 === "label")) || (key0 === "focus")) || (key0 === "note"))){const err4 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data0.id !== undefined){let data1 = data0.id;if(typeof data1 === "string"){if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data0.label !== undefined){let data2 = data0.label;if(typeof data2 === "string"){if(func3(data2) > 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1<len1; i1++){let data4 = data3[i1];if(typeof data4 === "string"){if(!pattern4.test(data4)){const err11 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}else {const err13 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data0.note !== undefined){let data5 = data0.note;if(typeof data5 === "string"){if(func3(data5) > 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate22.errors = vErrors;return errors === 0;}validate22.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};const schema52 = {"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"}}};function validate24(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate24.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.from === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.to === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}for(const key0 in data){if(!((key0 === "from") || (key0 === "to"))){const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.from !== undefined){let data0 = data.from;if(typeof data0 === "string"){if(!pattern4.test(data0)){const err3 = {instancePath:instancePath+"/from",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}else {const err4 = {instancePath:instancePath+"/from",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.to !== undefined){let data1 = data.to;if(typeof data1 === "string"){if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/to",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/to",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}else {const err7 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}validate24.errors = vErrors;return errors === 0;}validate24.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const pattern17 = new RegExp("^[^\\r\\n]+$", "u");const pattern18 = new RegExp("^https?://", "u");const pattern20 = new RegExp("^[a-f0-9]{64}$", "u");function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/workflow.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate20.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.lanes === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "lanes"},message:"must have required property '"+"lanes"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.nodes === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "nodes"},message:"must have required property '"+"nodes"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.edges === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "edges"},message:"must have required property '"+"edges"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!(func1.call(schema31.properties, key0))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema_version !== undefined){let data0 = data.schema_version;if(!((data0 === 1) || (data0 === 2))){const err7 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/enum",keyword:"enum",params:{allowedValues: schema31.properties.schema_version.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.diagram_type !== undefined){if("workflow" !== data.diagram_type){const err8 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "workflow"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}for(const key1 in data2){if(!(func1.call(schema31.properties.meta.properties, key1))){const err10 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err13 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err14 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err15 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err16 = {instancePath:instancePath+"/meta/animation",schemaPath:"#/properties/meta/properties/animation/enum",keyword:"enum",params:{allowedValues: schema31.properties.meta.properties.animation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err17 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"#/properties/meta/properties/visual_preset/enum",keyword:"enum",params:{allowedValues: schema31.properties.meta.properties.visual_preset.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err18 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"#/properties/meta/properties/quality_profile/enum",keyword:"enum",params:{allowedValues: schema31.properties.meta.properties.quality_profile.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data2.views !== undefined){if(!(validate22(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate22.errors : vErrors.concat(validate22.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data11 = data2.legend;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){for(const key2 in data11){if(!((key2 === "mode") || (key2 === "entries"))){const err19 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data11.mode !== undefined){let data12 = data11.mode;if(!(((data12 === "auto") || (data12 === "all")) || (data12 === "hidden"))){const err20 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data11.entries !== undefined){let data13 = data11.entries;if(data13 && typeof data13 == "object" && !Array.isArray(data13)){for(const key3 in data13){if(!(((((((key3 === "frontend") || (key3 === "backend")) || (key3 === "database")) || (key3 === "cloud")) || (key3 === "security")) || (key3 === "messagebus")) || (key3 === "external"))){const err21 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data13.frontend !== undefined){let data14 = data13.frontend;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){if(Object.keys(data14).length < 1){const err22 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}for(const key4 in data14){if(!((key4 === "label") || (key4 === "visible"))){const err23 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data14.label !== undefined){let data15 = data14.label;if(typeof data15 === "string"){if(func3(data15) > 80){const err24 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(func3(data15) < 1){const err25 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data14.visible !== undefined){if(typeof data14.visible !== "boolean"){const err27 = {instancePath:instancePath+"/meta/legend/entries/frontend/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data13.backend !== undefined){let data17 = data13.backend;if(data17 && typeof data17 == "object" && !Array.isArray(data17)){if(Object.keys(data17).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data17){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data17.label !== undefined){let data18 = data17.label;if(typeof data18 === "string"){if(func3(data18) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data18) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data17.visible !== undefined){if(typeof data17.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/backend/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data13.database !== undefined){let data20 = data13.database;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(Object.keys(data20).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data20){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data20.label !== undefined){let data21 = data20.label;if(typeof data21 === "string"){if(func3(data21) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data21) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data20.visible !== undefined){if(typeof data20.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/database/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data13.cloud !== undefined){let data23 = data13.cloud;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(Object.keys(data23).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data23){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data23.label !== undefined){let data24 = data23.label;if(typeof data24 === "string"){if(func3(data24) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data24) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data23.visible !== undefined){if(typeof data23.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/cloud/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data13.security !== undefined){let data26 = data13.security;if(data26 && typeof data26 == "object" && !Array.isArray(data26)){if(Object.keys(data26).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data26){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data26.label !== undefined){let data27 = data26.label;if(typeof data27 === "string"){if(func3(data27) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data27) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data26.visible !== undefined){if(typeof data26.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/security/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data13.messagebus !== undefined){let data29 = data13.messagebus;if(data29 && typeof data29 == "object" && !Array.isArray(data29)){if(Object.keys(data29).length < 1){const err57 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}for(const key9 in data29){if(!((key9 === "label") || (key9 === "visible"))){const err58 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data29.label !== undefined){let data30 = data29.label;if(typeof data30 === "string"){if(func3(data30) > 80){const err59 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(func3(data30) < 1){const err60 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data29.visible !== undefined){if(typeof data29.visible !== "boolean"){const err62 = {instancePath:instancePath+"/meta/legend/entries/messagebus/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}}else {const err63 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data13.external !== undefined){let data32 = data13.external;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){if(Object.keys(data32).length < 1){const err64 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}for(const key10 in data32){if(!((key10 === "label") || (key10 === "visible"))){const err65 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data32.label !== undefined){let data33 = data32.label;if(typeof data33 === "string"){if(func3(data33) > 80){const err66 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(func3(data33) < 1){const err67 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}else {const err68 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}if(data32.visible !== undefined){if(typeof data32.visible !== "boolean"){const err69 = {instancePath:instancePath+"/meta/legend/entries/external/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}else {const err70 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}}else {const err71 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}}else {const err72 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data2.viewBox !== undefined){let data35 = data2.viewBox;if(Array.isArray(data35)){if(data35.length > 2){const err73 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(data35.length < 2){const err74 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}const len0 = data35.length;if(len0 > 0){let data36 = data35[0];if((typeof data36 == "number") && (isFinite(data36))){if(data36 < 700 || isNaN(data36)){const err75 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 700},message:"must be >= 700"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}else {const err76 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}if(len0 > 1){let data37 = data35[1];if((typeof data37 == "number") && (isFinite(data37))){if(data37 < 240 || isNaN(data37)){const err77 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 240},message:"must be >= 240"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}else {const err78 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}const len1 = data35.length;if(!(len1 <= 2)){const err79 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}else {const err80 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}}else {const err81 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data.lanes !== undefined){let data38 = data.lanes;if(Array.isArray(data38)){if(data38.length < 1){const err82 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}const len2 = data38.length;for(let i0=0; i0<len2; i0++){let data39 = data38[i0];if(data39 && typeof data39 == "object" && !Array.isArray(data39)){if(data39.id === undefined){const err83 = {instancePath:instancePath+"/lanes/" + i0,schemaPath:"#/properties/lanes/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}if(data39.label === undefined){const err84 = {instancePath:instancePath+"/lanes/" + i0,schemaPath:"#/properties/lanes/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}for(const key11 in data39){if(!(((key11 === "id") || (key11 === "label")) || (key11 === "variant"))){const err85 = {instancePath:instancePath+"/lanes/" + i0,schemaPath:"#/properties/lanes/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}if(data39.id !== undefined){let data40 = data39.id;if(typeof data40 === "string"){if(!pattern4.test(data40)){const err86 = {instancePath:instancePath+"/lanes/" + i0+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}else {const err87 = {instancePath:instancePath+"/lanes/" + i0+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}if(data39.label !== undefined){let data41 = data39.label;if(typeof data41 === "string"){if(func3(data41) < 1){const err88 = {instancePath:instancePath+"/lanes/" + i0+"/label",schemaPath:"#/properties/lanes/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}else {const err89 = {instancePath:instancePath+"/lanes/" + i0+"/label",schemaPath:"#/properties/lanes/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}if(data39.variant !== undefined){let data42 = data39.variant;if(!((data42 === "normal") || (data42 === "exception"))){const err90 = {instancePath:instancePath+"/lanes/" + i0+"/variant",schemaPath:"#/properties/lanes/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema31.properties.lanes.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}}else {const err91 = {instancePath:instancePath+"/lanes/" + i0,schemaPath:"#/properties/lanes/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}}else {const err92 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}if(data.phases !== undefined){let data43 = data.phases;if(Array.isArray(data43)){const len3 = data43.length;for(let i1=0; i1<len3; i1++){let data44 = data43[i1];if(data44 && typeof data44 == "object" && !Array.isArray(data44)){if(data44.id === undefined){const err93 = {instancePath:instancePath+"/phases/" + i1,schemaPath:"#/properties/phases/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}if(data44.label === undefined){const err94 = {instancePath:instancePath+"/phases/" + i1,schemaPath:"#/properties/phases/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}if(data44.fromCol === undefined){const err95 = {instancePath:instancePath+"/phases/" + i1,schemaPath:"#/properties/phases/items/required",keyword:"required",params:{missingProperty: "fromCol"},message:"must have required property '"+"fromCol"+"'"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}if(data44.toCol === undefined){const err96 = {instancePath:instancePath+"/phases/" + i1,schemaPath:"#/properties/phases/items/required",keyword:"required",params:{missingProperty: "toCol"},message:"must have required property '"+"toCol"+"'"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}for(const key12 in data44){if(!(((((key12 === "id") || (key12 === "label")) || (key12 === "fromCol")) || (key12 === "toCol")) || (key12 === "variant"))){const err97 = {instancePath:instancePath+"/phases/" + i1,schemaPath:"#/properties/phases/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key12},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}if(data44.id !== undefined){let data45 = data44.id;if(typeof data45 === "string"){if(!pattern4.test(data45)){const err98 = {instancePath:instancePath+"/phases/" + i1+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}else {const err99 = {instancePath:instancePath+"/phases/" + i1+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data44.label !== undefined){let data46 = data44.label;if(typeof data46 === "string"){if(func3(data46) < 1){const err100 = {instancePath:instancePath+"/phases/" + i1+"/label",schemaPath:"#/properties/phases/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}else {const err101 = {instancePath:instancePath+"/phases/" + i1+"/label",schemaPath:"#/properties/phases/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}}if(data44.fromCol !== undefined){let data47 = data44.fromCol;if(!(((typeof data47 == "number") && (!(data47 % 1) && !isNaN(data47))) && (isFinite(data47)))){const err102 = {instancePath:instancePath+"/phases/" + i1+"/fromCol",schemaPath:"#/properties/phases/items/properties/fromCol/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}if((typeof data47 == "number") && (isFinite(data47))){if(data47 > 5 || isNaN(data47)){const err103 = {instancePath:instancePath+"/phases/" + i1+"/fromCol",schemaPath:"#/properties/phases/items/properties/fromCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}if(data47 < 0 || isNaN(data47)){const err104 = {instancePath:instancePath+"/phases/" + i1+"/fromCol",schemaPath:"#/properties/phases/items/properties/fromCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}}if(data44.toCol !== undefined){let data48 = data44.toCol;if(!(((typeof data48 == "number") && (!(data48 % 1) && !isNaN(data48))) && (isFinite(data48)))){const err105 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if((typeof data48 == "number") && (isFinite(data48))){if(data48 > 5 || isNaN(data48)){const err106 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}if(data48 < 0 || isNaN(data48)){const err107 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}}if(data44.variant !== undefined){let data49 = data44.variant;if(!((((data49 === "default") || (data49 === "emphasis")) || (data49 === "security")) || (data49 === "dashed"))){const err108 = {instancePath:instancePath+"/phases/" + i1+"/variant",schemaPath:"#/properties/phases/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema31.properties.phases.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}}else {const err109 = {instancePath:instancePath+"/phases/" + i1,schemaPath:"#/properties/phases/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}}else {const err110 = {instancePath:instancePath+"/phases",schemaPath:"#/properties/phases/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data.groups !== undefined){let data50 = data.groups;if(Array.isArray(data50)){const len4 = data50.length;for(let i2=0; i2<len4; i2++){let data51 = data50[i2];if(data51 && typeof data51 == "object" && !Array.isArray(data51)){if(data51.id === undefined){const err111 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}if(data51.label === undefined){const err112 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}if(data51.lane === undefined){const err113 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/required",keyword:"required",params:{missingProperty: "lane"},message:"must have required property '"+"lane"+"'"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}if(data51.fromCol === undefined){const err114 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/required",keyword:"required",params:{missingProperty: "fromCol"},message:"must have required property '"+"fromCol"+"'"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}if(data51.toCol === undefined){const err115 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/required",keyword:"required",params:{missingProperty: "toCol"},message:"must have required property '"+"toCol"+"'"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}for(const key13 in data51){if(!((((((key13 === "id") || (key13 === "label")) || (key13 === "lane")) || (key13 === "fromCol")) || (key13 === "toCol")) || (key13 === "variant"))){const err116 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key13},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}if(data51.id !== undefined){let data52 = data51.id;if(typeof data52 === "string"){if(!pattern4.test(data52)){const err117 = {instancePath:instancePath+"/groups/" + i2+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}else {const err118 = {instancePath:instancePath+"/groups/" + i2+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}if(data51.label !== undefined){let data53 = data51.label;if(typeof data53 === "string"){if(func3(data53) < 1){const err119 = {instancePath:instancePath+"/groups/" + i2+"/label",schemaPath:"#/properties/groups/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}else {const err120 = {instancePath:instancePath+"/groups/" + i2+"/label",schemaPath:"#/properties/groups/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}if(data51.lane !== undefined){let data54 = data51.lane;if(typeof data54 === "string"){if(!pattern4.test(data54)){const err121 = {instancePath:instancePath+"/groups/" + i2+"/lane",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}else {const err122 = {instancePath:instancePath+"/groups/" + i2+"/lane",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}if(data51.fromCol !== undefined){let data55 = data51.fromCol;if(!(((typeof data55 == "number") && (!(data55 % 1) && !isNaN(data55))) && (isFinite(data55)))){const err123 = {instancePath:instancePath+"/groups/" + i2+"/fromCol",schemaPath:"#/properties/groups/items/properties/fromCol/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}if((typeof data55 == "number") && (isFinite(data55))){if(data55 > 5 || isNaN(data55)){const err124 = {instancePath:instancePath+"/groups/" + i2+"/fromCol",schemaPath:"#/properties/groups/items/properties/fromCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}if(data55 < 0 || isNaN(data55)){const err125 = {instancePath:instancePath+"/groups/" + i2+"/fromCol",schemaPath:"#/properties/groups/items/properties/fromCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}}if(data51.toCol !== undefined){let data56 = data51.toCol;if(!(((typeof data56 == "number") && (!(data56 % 1) && !isNaN(data56))) && (isFinite(data56)))){const err126 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}if((typeof data56 == "number") && (isFinite(data56))){if(data56 > 5 || isNaN(data56)){const err127 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}if(data56 < 0 || isNaN(data56)){const err128 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}}if(data51.variant !== undefined){let data57 = data51.variant;if(!((((data57 === "default") || (data57 === "emphasis")) || (data57 === "security")) || (data57 === "dashed"))){const err129 = {instancePath:instancePath+"/groups/" + i2+"/variant",schemaPath:"#/properties/groups/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema31.properties.groups.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}}}else {const err130 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}}}else {const err131 = {instancePath:instancePath+"/groups",schemaPath:"#/properties/groups/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}if(data.mainPath !== undefined){let data58 = data.mainPath;if(Array.isArray(data58)){if(data58.length < 2){const err132 = {instancePath:instancePath+"/mainPath",schemaPath:"#/properties/mainPath/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}const len5 = data58.length;for(let i3=0; i3<len5; i3++){let data59 = data58[i3];if(typeof data59 === "string"){if(!pattern4.test(data59)){const err133 = {instancePath:instancePath+"/mainPath/" + i3,schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}else {const err134 = {instancePath:instancePath+"/mainPath/" + i3,schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}}else {const err135 = {instancePath:instancePath+"/mainPath",schemaPath:"#/properties/mainPath/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}}if(data.semanticChecks !== undefined){let data60 = data.semanticChecks;if(data60 && typeof data60 == "object" && !Array.isArray(data60)){if(Object.keys(data60).length < 1){const err136 = {instancePath:instancePath+"/semanticChecks",schemaPath:"#/properties/semanticChecks/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}for(const key14 in data60){if(!((((key14 === "allowedRoots") || (key14 === "allowedTerminals")) || (key14 === "requiredEdges")) || (key14 === "requiredPaths"))){const err137 = {instancePath:instancePath+"/semanticChecks",schemaPath:"#/properties/semanticChecks/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key14},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}if(data60.allowedRoots !== undefined){let data61 = data60.allowedRoots;if(Array.isArray(data61)){const len6 = data61.length;for(let i4=0; i4<len6; i4++){let data62 = data61[i4];if(typeof data62 === "string"){if(!pattern4.test(data62)){const err138 = {instancePath:instancePath+"/semanticChecks/allowedRoots/" + i4,schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}else {const err139 = {instancePath:instancePath+"/semanticChecks/allowedRoots/" + i4,schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}}else {const err140 = {instancePath:instancePath+"/semanticChecks/allowedRoots",schemaPath:"#/properties/semanticChecks/properties/allowedRoots/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}if(data60.allowedTerminals !== undefined){let data63 = data60.allowedTerminals;if(Array.isArray(data63)){const len7 = data63.length;for(let i5=0; i5<len7; i5++){let data64 = data63[i5];if(typeof data64 === "string"){if(!pattern4.test(data64)){const err141 = {instancePath:instancePath+"/semanticChecks/allowedTerminals/" + i5,schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}else {const err142 = {instancePath:instancePath+"/semanticChecks/allowedTerminals/" + i5,schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}}else {const err143 = {instancePath:instancePath+"/semanticChecks/allowedTerminals",schemaPath:"#/properties/semanticChecks/properties/allowedTerminals/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data60.requiredEdges !== undefined){let data65 = data60.requiredEdges;if(Array.isArray(data65)){const len8 = data65.length;for(let i6=0; i6<len8; i6++){if(!(validate24(data65[i6], {instancePath:instancePath+"/semanticChecks/requiredEdges/" + i6,parentData:data65,parentDataProperty:i6,rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate24.errors : vErrors.concat(validate24.errors);errors = vErrors.length;}}}else {const err144 = {instancePath:instancePath+"/semanticChecks/requiredEdges",schemaPath:"#/properties/semanticChecks/properties/requiredEdges/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}if(data60.requiredPaths !== undefined){let data67 = data60.requiredPaths;if(Array.isArray(data67)){const len9 = data67.length;for(let i7=0; i7<len9; i7++){if(!(validate24(data67[i7], {instancePath:instancePath+"/semanticChecks/requiredPaths/" + i7,parentData:data67,parentDataProperty:i7,rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate24.errors : vErrors.concat(validate24.errors);errors = vErrors.length;}}}else {const err145 = {instancePath:instancePath+"/semanticChecks/requiredPaths",schemaPath:"#/properties/semanticChecks/properties/requiredPaths/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}}else {const err146 = {instancePath:instancePath+"/semanticChecks",schemaPath:"#/properties/semanticChecks/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}}if(data.nodes !== undefined){let data69 = data.nodes;if(Array.isArray(data69)){if(data69.length < 1){const err147 = {instancePath:instancePath+"/nodes",schemaPath:"#/properties/nodes/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}const len10 = data69.length;for(let i8=0; i8<len10; i8++){let data70 = data69[i8];if(data70 && typeof data70 == "object" && !Array.isArray(data70)){if(data70.id === undefined){const err148 = {instancePath:instancePath+"/nodes/" + i8,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}if(data70.lane === undefined){const err149 = {instancePath:instancePath+"/nodes/" + i8,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "lane"},message:"must have required property '"+"lane"+"'"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}if(data70.col === undefined){const err150 = {instancePath:instancePath+"/nodes/" + i8,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "col"},message:"must have required property '"+"col"+"'"};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}if(data70.type === undefined){const err151 = {instancePath:instancePath+"/nodes/" + i8,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}if(data70.label === undefined){const err152 = {instancePath:instancePath+"/nodes/" + i8,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err152];}else {vErrors.push(err152);}errors++;}for(const key15 in data70){if(!(func1.call(schema31.properties.nodes.items.properties, key15))){const err153 = {instancePath:instancePath+"/nodes/" + i8,schemaPath:"#/properties/nodes/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key15},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err153];}else {vErrors.push(err153);}errors++;}}if(data70.id !== undefined){let data71 = data70.id;if(typeof data71 === "string"){if(!pattern4.test(data71)){const err154 = {instancePath:instancePath+"/nodes/" + i8+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err154];}else {vErrors.push(err154);}errors++;}}else {const err155 = {instancePath:instancePath+"/nodes/" + i8+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err155];}else {vErrors.push(err155);}errors++;}}if(data70.lane !== undefined){let data72 = data70.lane;if(typeof data72 === "string"){if(!pattern4.test(data72)){const err156 = {instancePath:instancePath+"/nodes/" + i8+"/lane",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err156];}else {vErrors.push(err156);}errors++;}}else {const err157 = {instancePath:instancePath+"/nodes/" + i8+"/lane",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err157];}else {vErrors.push(err157);}errors++;}}if(data70.col !== undefined){let data73 = data70.col;if(!(((typeof data73 == "number") && (!(data73 % 1) && !isNaN(data73))) && (isFinite(data73)))){const err158 = {instancePath:instancePath+"/nodes/" + i8+"/col",schemaPath:"#/properties/nodes/items/properties/col/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err158];}else {vErrors.push(err158);}errors++;}if((typeof data73 == "number") && (isFinite(data73))){if(data73 > 5 || isNaN(data73)){const err159 = {instancePath:instancePath+"/nodes/" + i8+"/col",schemaPath:"#/properties/nodes/items/properties/col/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err159];}else {vErrors.push(err159);}errors++;}if(data73 < 0 || isNaN(data73)){const err160 = {instancePath:instancePath+"/nodes/" + i8+"/col",schemaPath:"#/properties/nodes/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err160];}else {vErrors.push(err160);}errors++;}}}if(data70.type !== undefined){let data74 = data70.type;if(!(((((((data74 === "frontend") || (data74 === "backend")) || (data74 === "database")) || (data74 === "cloud")) || (data74 === "security")) || (data74 === "messagebus")) || (data74 === "external"))){const err161 = {instancePath:instancePath+"/nodes/" + i8+"/type",schemaPath:"common.schema.json#/$defs/componentType/enum",keyword:"enum",params:{allowedValues: schema57.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err161];}else {vErrors.push(err161);}errors++;}}if(data70.label !== undefined){let data75 = data70.label;if(typeof data75 === "string"){if(func3(data75) < 1){const err162 = {instancePath:instancePath+"/nodes/" + i8+"/label",schemaPath:"#/properties/nodes/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}}else {const err163 = {instancePath:instancePath+"/nodes/" + i8+"/label",schemaPath:"#/properties/nodes/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}}if(data70.sublabel !== undefined){if(typeof data70.sublabel !== "string"){const err164 = {instancePath:instancePath+"/nodes/" + i8+"/sublabel",schemaPath:"#/properties/nodes/items/properties/sublabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}}if(data70.tag !== undefined){if(typeof data70.tag !== "string"){const err165 = {instancePath:instancePath+"/nodes/" + i8+"/tag",schemaPath:"#/properties/nodes/items/properties/tag/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}}if(data70.brand !== undefined){let data78 = data70.brand;const _errs180 = errors;let valid55 = false;let passing0 = null;const _errs181 = errors;const _errs183 = errors;let valid56 = false;const _errs184 = errors;if(typeof data78 === "string"){if(func3(data78) > 80){const err166 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}if(!pattern17.test(data78)){const err167 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}}var _valid1 = _errs184 === errors;valid56 = valid56 || _valid1;const _errs185 = errors;if(typeof data78 === "string"){if(!pattern18.test(data78)){const err168 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}}var _valid1 = _errs185 === errors;valid56 = valid56 || _valid1;if(!valid56){const err169 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}else {errors = _errs183;if(vErrors !== null){if(_errs183){vErrors.length = _errs183;}else {vErrors = null;}}}if(typeof data78 === "string"){if(func3(data78) > 2048){const err170 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}if(func3(data78) < 1){const err171 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err171];}else {vErrors.push(err171);}errors++;}}else {const err172 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err172];}else {vErrors.push(err172);}errors++;}var _valid0 = _errs181 === errors;if(_valid0){valid55 = true;passing0 = 0;}const _errs186 = errors;if(data78 && typeof data78 == "object" && !Array.isArray(data78)){if(data78.url === undefined){const err173 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err173];}else {vErrors.push(err173);}errors++;}if(data78.sha256 === undefined){const err174 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err174];}else {vErrors.push(err174);}errors++;}for(const key16 in data78){if(!((key16 === "url") || (key16 === "sha256"))){const err175 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key16},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err175];}else {vErrors.push(err175);}errors++;}}if(data78.url !== undefined){let data79 = data78.url;if(typeof data79 === "string"){if(func3(data79) > 2048){const err176 = {instancePath:instancePath+"/nodes/" + i8+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err176];}else {vErrors.push(err176);}errors++;}if(func3(data79) < 8){const err177 = {instancePath:instancePath+"/nodes/" + i8+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err177];}else {vErrors.push(err177);}errors++;}if(!pattern18.test(data79)){const err178 = {instancePath:instancePath+"/nodes/" + i8+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err178];}else {vErrors.push(err178);}errors++;}}else {const err179 = {instancePath:instancePath+"/nodes/" + i8+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err179];}else {vErrors.push(err179);}errors++;}}if(data78.sha256 !== undefined){let data80 = data78.sha256;if(typeof data80 === "string"){if(!pattern20.test(data80)){const err180 = {instancePath:instancePath+"/nodes/" + i8+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err180];}else {vErrors.push(err180);}errors++;}}else {const err181 = {instancePath:instancePath+"/nodes/" + i8+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err181];}else {vErrors.push(err181);}errors++;}}}else {const err182 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}var _valid0 = _errs186 === errors;if(_valid0 && valid55){valid55 = false;passing0 = [passing0, 1];}else {if(_valid0){valid55 = true;passing0 = 1;}}if(!valid55){const err183 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}else {errors = _errs180;if(vErrors !== null){if(_errs180){vErrors.length = _errs180;}else {vErrors = null;}}}}if(data70.width !== undefined){let data81 = data70.width;if((typeof data81 == "number") && (isFinite(data81))){if(data81 < 32 || isNaN(data81)){const err184 = {instancePath:instancePath+"/nodes/" + i8+"/width",schemaPath:"#/properties/nodes/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 32},message:"must be >= 32"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}}else {const err185 = {instancePath:instancePath+"/nodes/" + i8+"/width",schemaPath:"#/properties/nodes/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err185];}else {vErrors.push(err185);}errors++;}}if(data70.height !== undefined){let data82 = data70.height;if((typeof data82 == "number") && (isFinite(data82))){if(data82 < 32 || isNaN(data82)){const err186 = {instancePath:instancePath+"/nodes/" + i8+"/height",schemaPath:"#/properties/nodes/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 32},message:"must be >= 32"};if(vErrors === null){vErrors = [err186];}else {vErrors.push(err186);}errors++;}}else {const err187 = {instancePath:instancePath+"/nodes/" + i8+"/height",schemaPath:"#/properties/nodes/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}}if(data70.yOffset !== undefined){let data83 = data70.yOffset;if(!((typeof data83 == "number") && (isFinite(data83)))){const err188 = {instancePath:instancePath+"/nodes/" + i8+"/yOffset",schemaPath:"#/properties/nodes/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}}}else {const err189 = {instancePath:instancePath+"/nodes/" + i8,schemaPath:"#/properties/nodes/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}}else {const err190 = {instancePath:instancePath+"/nodes",schemaPath:"#/properties/nodes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}if(data.edges !== undefined){let data84 = data.edges;if(Array.isArray(data84)){const len11 = data84.length;for(let i9=0; i9<len11; i9++){let data85 = data84[i9];if(data85 && typeof data85 == "object" && !Array.isArray(data85)){if(data85.from === undefined){const err191 = {instancePath:instancePath+"/edges/" + i9,schemaPath:"#/properties/edges/items/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err191];}else {vErrors.push(err191);}errors++;}if(data85.to === undefined){const err192 = {instancePath:instancePath+"/edges/" + i9,schemaPath:"#/properties/edges/items/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err192];}else {vErrors.push(err192);}errors++;}for(const key17 in data85){if(!(func1.call(schema31.properties.edges.items.properties, key17))){const err193 = {instancePath:instancePath+"/edges/" + i9,schemaPath:"#/properties/edges/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key17},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err193];}else {vErrors.push(err193);}errors++;}}if(data85.id !== undefined){let data86 = data85.id;if(typeof data86 === "string"){if(!pattern4.test(data86)){const err194 = {instancePath:instancePath+"/edges/" + i9+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err194];}else {vErrors.push(err194);}errors++;}}else {const err195 = {instancePath:instancePath+"/edges/" + i9+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err195];}else {vErrors.push(err195);}errors++;}}if(data85.from !== undefined){let data87 = data85.from;if(typeof data87 === "string"){if(!pattern4.test(data87)){const err196 = {instancePath:instancePath+"/edges/" + i9+"/from",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err196];}else {vErrors.push(err196);}errors++;}}else {const err197 = {instancePath:instancePath+"/edges/" + i9+"/from",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err197];}else {vErrors.push(err197);}errors++;}}if(data85.to !== undefined){let data88 = data85.to;if(typeof data88 === "string"){if(!pattern4.test(data88)){const err198 = {instancePath:instancePath+"/edges/" + i9+"/to",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err198];}else {vErrors.push(err198);}errors++;}}else {const err199 = {instancePath:instancePath+"/edges/" + i9+"/to",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err199];}else {vErrors.push(err199);}errors++;}}if(data85.label !== undefined){if(typeof data85.label !== "string"){const err200 = {instancePath:instancePath+"/edges/" + i9+"/label",schemaPath:"#/properties/edges/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err200];}else {vErrors.push(err200);}errors++;}}if(data85.variant !== undefined){let data90 = data85.variant;if(!((((data90 === "default") || (data90 === "emphasis")) || (data90 === "security")) || (data90 === "dashed"))){const err201 = {instancePath:instancePath+"/edges/" + i9+"/variant",schemaPath:"common.schema.json#/$defs/variant/enum",keyword:"enum",params:{allowedValues: schema62.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err201];}else {vErrors.push(err201);}errors++;}}if(data85.role !== undefined){let data91 = data85.role;if(!(((((data91 === "main") || (data91 === "branch")) || (data91 === "async")) || (data91 === "return")) || (data91 === "error"))){const err202 = {instancePath:instancePath+"/edges/" + i9+"/role",schemaPath:"#/properties/edges/items/properties/role/enum",keyword:"enum",params:{allowedValues: schema31.properties.edges.items.properties.role.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err202];}else {vErrors.push(err202);}errors++;}}if(data85.fromSide !== undefined){let data92 = data85.fromSide;if(!((((data92 === "left") || (data92 === "right")) || (data92 === "top")) || (data92 === "bottom"))){const err203 = {instancePath:instancePath+"/edges/" + i9+"/fromSide",schemaPath:"#/$defs/side/enum",keyword:"enum",params:{allowedValues: schema63.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err203];}else {vErrors.push(err203);}errors++;}}if(data85.toSide !== undefined){let data93 = data85.toSide;if(!((((data93 === "left") || (data93 === "right")) || (data93 === "top")) || (data93 === "bottom"))){const err204 = {instancePath:instancePath+"/edges/" + i9+"/toSide",schemaPath:"#/$defs/side/enum",keyword:"enum",params:{allowedValues: schema63.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err204];}else {vErrors.push(err204);}errors++;}}if(data85.route !== undefined){let data94 = data85.route;if(!(((((((data94 === "auto") || (data94 === "straight")) || (data94 === "drop")) || (data94 === "outside-right")) || (data94 === "return-left")) || (data94 === "bottom-channel")) || (data94 === "up-channel"))){const err205 = {instancePath:instancePath+"/edges/" + i9+"/route",schemaPath:"#/properties/edges/items/properties/route/enum",keyword:"enum",params:{allowedValues: schema31.properties.edges.items.properties.route.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err205];}else {vErrors.push(err205);}errors++;}}if(data85.via !== undefined){let data95 = data85.via;if(Array.isArray(data95)){const len12 = data95.length;for(let i10=0; i10<len12; i10++){let data96 = data95[i10];if(Array.isArray(data96)){if(data96.length > 2){const err206 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err206];}else {vErrors.push(err206);}errors++;}if(data96.length < 2){const err207 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err207];}else {vErrors.push(err207);}errors++;}const len13 = data96.length;if(len13 > 0){let data97 = data96[0];if(!((typeof data97 == "number") && (isFinite(data97)))){const err208 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err208];}else {vErrors.push(err208);}errors++;}}if(len13 > 1){let data98 = data96[1];if(!((typeof data98 == "number") && (isFinite(data98)))){const err209 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err209];}else {vErrors.push(err209);}errors++;}}const len14 = data96.length;if(!(len14 <= 2)){const err210 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err210];}else {vErrors.push(err210);}errors++;}}else {const err211 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err211];}else {vErrors.push(err211);}errors++;}}}else {const err212 = {instancePath:instancePath+"/edges/" + i9+"/via",schemaPath:"#/properties/edges/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err212];}else {vErrors.push(err212);}errors++;}}if(data85.labelAt !== undefined){let data99 = data85.labelAt;if(Array.isArray(data99)){if(data99.length > 2){const err213 = {instancePath:instancePath+"/edges/" + i9+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err213];}else {vErrors.push(err213);}errors++;}if(data99.length < 2){const err214 = {instancePath:instancePath+"/edges/" + i9+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err214];}else {vErrors.push(err214);}errors++;}const len15 = data99.length;if(len15 > 0){let data100 = data99[0];if(!((typeof data100 == "number") && (isFinite(data100)))){const err215 = {instancePath:instancePath+"/edges/" + i9+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err215];}else {vErrors.push(err215);}errors++;}}if(len15 > 1){let data101 = data99[1];if(!((typeof data101 == "number") && (isFinite(data101)))){const err216 = {instancePath:instancePath+"/edges/" + i9+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err216];}else {vErrors.push(err216);}errors++;}}const len16 = data99.length;if(!(len16 <= 2)){const err217 = {instancePath:instancePath+"/edges/" + i9+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err217];}else {vErrors.push(err217);}errors++;}}else {const err218 = {instancePath:instancePath+"/edges/" + i9+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err218];}else {vErrors.push(err218);}errors++;}}if(data85.labelDx !== undefined){let data102 = data85.labelDx;if(!((typeof data102 == "number") && (isFinite(data102)))){const err219 = {instancePath:instancePath+"/edges/" + i9+"/labelDx",schemaPath:"#/properties/edges/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err219];}else {vErrors.push(err219);}errors++;}}if(data85.labelDy !== undefined){let data103 = data85.labelDy;if(!((typeof data103 == "number") && (isFinite(data103)))){const err220 = {instancePath:instancePath+"/edges/" + i9+"/labelDy",schemaPath:"#/properties/edges/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err220];}else {vErrors.push(err220);}errors++;}}if(data85.labelSegment !== undefined){let data104 = data85.labelSegment;if(!(((typeof data104 == "number") && (!(data104 % 1) && !isNaN(data104))) && (isFinite(data104)))){const err221 = {instancePath:instancePath+"/edges/" + i9+"/labelSegment",schemaPath:"#/properties/edges/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err221];}else {vErrors.push(err221);}errors++;}if((typeof data104 == "number") && (isFinite(data104))){if(data104 < 0 || isNaN(data104)){const err222 = {instancePath:instancePath+"/edges/" + i9+"/labelSegment",schemaPath:"#/properties/edges/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err222];}else {vErrors.push(err222);}errors++;}}}if(data85.channelX !== undefined){let data105 = data85.channelX;if(!((typeof data105 == "number") && (isFinite(data105)))){const err223 = {instancePath:instancePath+"/edges/" + i9+"/channelX",schemaPath:"#/properties/edges/items/properties/channelX/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err223];}else {vErrors.push(err223);}errors++;}}if(data85.channelY !== undefined){let data106 = data85.channelY;if(!((typeof data106 == "number") && (isFinite(data106)))){const err224 = {instancePath:instancePath+"/edges/" + i9+"/channelY",schemaPath:"#/properties/edges/items/properties/channelY/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err224];}else {vErrors.push(err224);}errors++;}}if(data85.bias !== undefined){let data107 = data85.bias;if((typeof data107 == "number") && (isFinite(data107))){if(data107 > 1 || isNaN(data107)){const err225 = {instancePath:instancePath+"/edges/" + i9+"/bias",schemaPath:"#/properties/edges/items/properties/bias/maximum",keyword:"maximum",params:{comparison: "<=", limit: 1},message:"must be <= 1"};if(vErrors === null){vErrors = [err225];}else {vErrors.push(err225);}errors++;}if(data107 < 0 || isNaN(data107)){const err226 = {instancePath:instancePath+"/edges/" + i9+"/bias",schemaPath:"#/properties/edges/items/properties/bias/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err226];}else {vErrors.push(err226);}errors++;}}else {const err227 = {instancePath:instancePath+"/edges/" + i9+"/bias",schemaPath:"#/properties/edges/items/properties/bias/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err227];}else {vErrors.push(err227);}errors++;}}if(data85.width !== undefined){let data108 = data85.width;if((typeof data108 == "number") && (isFinite(data108))){if(data108 < 0.5 || isNaN(data108)){const err228 = {instancePath:instancePath+"/edges/" + i9+"/width",schemaPath:"#/properties/edges/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err228];}else {vErrors.push(err228);}errors++;}}else {const err229 = {instancePath:instancePath+"/edges/" + i9+"/width",schemaPath:"#/properties/edges/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err229];}else {vErrors.push(err229);}errors++;}}}else {const err230 = {instancePath:instancePath+"/edges/" + i9,schemaPath:"#/properties/edges/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err230];}else {vErrors.push(err230);}errors++;}}}else {const err231 = {instancePath:instancePath+"/edges",schemaPath:"#/properties/edges/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err231];}else {vErrors.push(err231);}errors++;}}if(data.cards !== undefined){let data109 = data.cards;if(Array.isArray(data109)){const len17 = data109.length;for(let i11=0; i11<len17; i11++){let data110 = data109[i11];if(data110 && typeof data110 == "object" && !Array.isArray(data110)){if(data110.dot === undefined){const err232 = {instancePath:instancePath+"/cards/" + i11,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "dot"},message:"must have required property '"+"dot"+"'"};if(vErrors === null){vErrors = [err232];}else {vErrors.push(err232);}errors++;}if(data110.title === undefined){const err233 = {instancePath:instancePath+"/cards/" + i11,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err233];}else {vErrors.push(err233);}errors++;}if(data110.items === undefined){const err234 = {instancePath:instancePath+"/cards/" + i11,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "items"},message:"must have required property '"+"items"+"'"};if(vErrors === null){vErrors = [err234];}else {vErrors.push(err234);}errors++;}for(const key18 in data110){if(!(((key18 === "dot") || (key18 === "title")) || (key18 === "items"))){const err235 = {instancePath:instancePath+"/cards/" + i11,schemaPath:"common.schema.json#/$defs/cards/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key18},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err235];}else {vErrors.push(err235);}errors++;}}if(data110.dot !== undefined){let data111 = data110.dot;if(!(((((((data111 === "cyan") || (data111 === "emerald")) || (data111 === "violet")) || (data111 === "amber")) || (data111 === "rose")) || (data111 === "orange")) || (data111 === "slate"))){const err236 = {instancePath:instancePath+"/cards/" + i11+"/dot",schemaPath:"common.schema.json#/$defs/cards/items/properties/dot/enum",keyword:"enum",params:{allowedValues: schema67.items.properties.dot.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err236];}else {vErrors.push(err236);}errors++;}}if(data110.title !== undefined){let data112 = data110.title;if(typeof data112 === "string"){if(func3(data112) < 1){const err237 = {instancePath:instancePath+"/cards/" + i11+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err237];}else {vErrors.push(err237);}errors++;}}else {const err238 = {instancePath:instancePath+"/cards/" + i11+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err238];}else {vErrors.push(err238);}errors++;}}if(data110.items !== undefined){let data113 = data110.items;if(Array.isArray(data113)){const len18 = data113.length;for(let i12=0; i12<len18; i12++){if(typeof data113[i12] !== "string"){const err239 = {instancePath:instancePath+"/cards/" + i11+"/items/" + i12,schemaPath:"common.schema.json#/$defs/cards/items/properties/items/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err239];}else {vErrors.push(err239);}errors++;}}}else {const err240 = {instancePath:instancePath+"/cards/" + i11+"/items",schemaPath:"common.schema.json#/$defs/cards/items/properties/items/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err240];}else {vErrors.push(err240);}errors++;}}}else {const err241 = {instancePath:instancePath+"/cards/" + i11,schemaPath:"common.schema.json#/$defs/cards/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err241];}else {vErrors.push(err241);}errors++;}}}else {const err242 = {instancePath:instancePath+"/cards",schemaPath:"common.schema.json#/$defs/cards/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err242];}else {vErrors.push(err242);}errors++;}}}else {const err243 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err243];}else {vErrors.push(err243);}errors++;}validate20.errors = vErrors;return errors === 0;}validate20.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};export const sequence = validate27;const schema68 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/tt-a1i/archify/schemas/sequence.schema.json","title":"Archify Sequence Diagram","type":"object","additionalProperties":false,"required":["schema_version","diagram_type","meta","participants","messages"],"properties":{"schema_version":{"const":1},"diagram_type":{"const":"sequence"},"meta":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","minLength":1},"locale":{"$ref":"common.schema.json#/$defs/locale"},"subtitle":{"type":"string"},"output":{"type":"string"},"animation":{"$ref":"common.schema.json#/$defs/animation"},"visual_preset":{"$ref":"common.schema.json#/$defs/visualPreset"},"quality_profile":{"$ref":"common.schema.json#/$defs/qualityProfile"},"column_fit":{"description":"Horizontal participant layout. Omit this field or use fixed for the stable 86px boxes and 108px gap. Use spread when a wide viewBox would leave unused horizontal space or meaningful participant labels do not fit the fixed boxes; spread derives wider boxes and gaps from the viewBox without changing participant order or message semantics.","enum":["fixed","spread"]},"views":{"$ref":"common.schema.json#/$defs/guidedViews"},"legend":{"type":"object","additionalProperties":false,"properties":{"mode":{"$ref":"common.schema.json#/$defs/legendMode"},"entries":{"type":"object","additionalProperties":false,"properties":{"default":{"$ref":"common.schema.json#/$defs/legendEntry"},"emphasis":{"$ref":"common.schema.json#/$defs/legendEntry"},"security":{"$ref":"common.schema.json#/$defs/legendEntry"},"dashed":{"$ref":"common.schema.json#/$defs/legendEntry"},"return":{"$ref":"common.schema.json#/$defs/legendEntry"}}}}},"viewBox":{"type":"array","prefixItems":[{"type":"number","minimum":480},{"type":"number","minimum":480}],"items":false,"minItems":2,"maxItems":2}}},"participants":{"type":"array","minItems":2,"items":{"type":"object","additionalProperties":false,"required":["id","type","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"type":{"$ref":"common.schema.json#/$defs/componentType"},"label":{"type":"string","minLength":1},"sublabel":{"type":"string"},"brand":{"$ref":"common.schema.json#/$defs/brandMark"}}}},"segments":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["from","to","label"],"properties":{"from":{"type":"number"},"to":{"type":"number"},"label":{"type":"string","minLength":1}}}},"messages":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["from","to","y","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"},"y":{"type":"number","minimum":160},"label":{"type":"string","minLength":1},"variant":{"enum":["default","emphasis","security","dashed","return"]},"note":{"type":"string"}}}},"activations":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["participant","from","to"],"properties":{"participant":{"$ref":"common.schema.json#/$defs/id"},"from":{"type":"number"},"to":{"type":"number"},"type":{"$ref":"common.schema.json#/$defs/componentType"}}}},"cards":{"$ref":"common.schema.json#/$defs/cards"}}};const schema70 = {"enum":["trace","none"]};const schema71 = {"enum":["classic","signal-flow","blueprint","editorial"]};const schema72 = {"enum":["standard","showcase"]};function validate28(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate28.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(Array.isArray(data)){if(data.length > 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0<len0; i0++){let data0 = data[i0];if(data0 && typeof data0 == "object" && !Array.isArray(data0)){if(data0.id === undefined){const err1 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data0.label === undefined){const err2 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data0.focus === undefined){const err3 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "focus"},message:"must have required property '"+"focus"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data0){if(!((((key0 === "id") || (key0 === "label")) || (key0 === "focus")) || (key0 === "note"))){const err4 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data0.id !== undefined){let data1 = data0.id;if(typeof data1 === "string"){if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data0.label !== undefined){let data2 = data0.label;if(typeof data2 === "string"){if(func3(data2) > 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1<len1; i1++){let data4 = data3[i1];if(typeof data4 === "string"){if(!pattern4.test(data4)){const err11 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}else {const err13 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data0.note !== undefined){let data5 = data0.note;if(typeof data5 === "string"){if(func3(data5) > 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate28.errors = vErrors;return errors === 0;}validate28.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};function validate27(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/sequence.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate27.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.participants === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "participants"},message:"must have required property '"+"participants"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.messages === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "messages"},message:"must have required property '"+"messages"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema_version") || (key0 === "diagram_type")) || (key0 === "meta")) || (key0 === "participants")) || (key0 === "segments")) || (key0 === "messages")) || (key0 === "activations")) || (key0 === "cards"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err6 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.diagram_type !== undefined){if("sequence" !== data.diagram_type){const err7 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "sequence"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err8 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}for(const key1 in data2){if(!(func1.call(schema68.properties.meta.properties, key1))){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err10 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err12 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err13 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err14 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err15 = {instancePath:instancePath+"/meta/animation",schemaPath:"common.schema.json#/$defs/animation/enum",keyword:"enum",params:{allowedValues: schema70.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err16 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"common.schema.json#/$defs/visualPreset/enum",keyword:"enum",params:{allowedValues: schema71.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err17 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"common.schema.json#/$defs/qualityProfile/enum",keyword:"enum",params:{allowedValues: schema72.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.column_fit !== undefined){let data10 = data2.column_fit;if(!((data10 === "fixed") || (data10 === "spread"))){const err18 = {instancePath:instancePath+"/meta/column_fit",schemaPath:"#/properties/meta/properties/column_fit/enum",keyword:"enum",params:{allowedValues: schema68.properties.meta.properties.column_fit.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data2.views !== undefined){if(!(validate28(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data12 = data2.legend;if(data12 && typeof data12 == "object" && !Array.isArray(data12)){for(const key2 in data12){if(!((key2 === "mode") || (key2 === "entries"))){const err19 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data12.mode !== undefined){let data13 = data12.mode;if(!(((data13 === "auto") || (data13 === "all")) || (data13 === "hidden"))){const err20 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data12.entries !== undefined){let data14 = data12.entries;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){for(const key3 in data14){if(!(((((key3 === "default") || (key3 === "emphasis")) || (key3 === "security")) || (key3 === "dashed")) || (key3 === "return"))){const err21 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data14.default !== undefined){let data15 = data14.default;if(data15 && typeof data15 == "object" && !Array.isArray(data15)){if(Object.keys(data15).length < 1){const err22 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}for(const key4 in data15){if(!((key4 === "label") || (key4 === "visible"))){const err23 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data15.label !== undefined){let data16 = data15.label;if(typeof data16 === "string"){if(func3(data16) > 80){const err24 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(func3(data16) < 1){const err25 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data15.visible !== undefined){if(typeof data15.visible !== "boolean"){const err27 = {instancePath:instancePath+"/meta/legend/entries/default/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data14.emphasis !== undefined){let data18 = data14.emphasis;if(data18 && typeof data18 == "object" && !Array.isArray(data18)){if(Object.keys(data18).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data18){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data18.label !== undefined){let data19 = data18.label;if(typeof data19 === "string"){if(func3(data19) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data19) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data18.visible !== undefined){if(typeof data18.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/emphasis/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data14.security !== undefined){let data21 = data14.security;if(data21 && typeof data21 == "object" && !Array.isArray(data21)){if(Object.keys(data21).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data21){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data21.label !== undefined){let data22 = data21.label;if(typeof data22 === "string"){if(func3(data22) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data22) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data21.visible !== undefined){if(typeof data21.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/security/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data14.dashed !== undefined){let data24 = data14.dashed;if(data24 && typeof data24 == "object" && !Array.isArray(data24)){if(Object.keys(data24).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data24){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data24.label !== undefined){let data25 = data24.label;if(typeof data25 === "string"){if(func3(data25) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data25) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data24.visible !== undefined){if(typeof data24.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/dashed/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data14.return !== undefined){let data27 = data14.return;if(data27 && typeof data27 == "object" && !Array.isArray(data27)){if(Object.keys(data27).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/return",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data27){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/return",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data27.label !== undefined){let data28 = data27.label;if(typeof data28 === "string"){if(func3(data28) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/return/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data28) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/return/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/return/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data27.visible !== undefined){if(typeof data27.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/return/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/return",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}else {const err57 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}}else {const err58 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data2.viewBox !== undefined){let data30 = data2.viewBox;if(Array.isArray(data30)){if(data30.length > 2){const err59 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(data30.length < 2){const err60 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}const len0 = data30.length;if(len0 > 0){let data31 = data30[0];if((typeof data31 == "number") && (isFinite(data31))){if(data31 < 480 || isNaN(data31)){const err61 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 480},message:"must be >= 480"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}else {const err62 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(len0 > 1){let data32 = data30[1];if((typeof data32 == "number") && (isFinite(data32))){if(data32 < 480 || isNaN(data32)){const err63 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 480},message:"must be >= 480"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}else {const err64 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}const len1 = data30.length;if(!(len1 <= 2)){const err65 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}else {const err66 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}}else {const err67 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}if(data.participants !== undefined){let data33 = data.participants;if(Array.isArray(data33)){if(data33.length < 2){const err68 = {instancePath:instancePath+"/participants",schemaPath:"#/properties/participants/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}const len2 = data33.length;for(let i0=0; i0<len2; i0++){let data34 = data33[i0];if(data34 && typeof data34 == "object" && !Array.isArray(data34)){if(data34.id === undefined){const err69 = {instancePath:instancePath+"/participants/" + i0,schemaPath:"#/properties/participants/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}if(data34.type === undefined){const err70 = {instancePath:instancePath+"/participants/" + i0,schemaPath:"#/properties/participants/items/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(data34.label === undefined){const err71 = {instancePath:instancePath+"/participants/" + i0,schemaPath:"#/properties/participants/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}for(const key9 in data34){if(!(((((key9 === "id") || (key9 === "type")) || (key9 === "label")) || (key9 === "sublabel")) || (key9 === "brand"))){const err72 = {instancePath:instancePath+"/participants/" + i0,schemaPath:"#/properties/participants/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data34.id !== undefined){let data35 = data34.id;if(typeof data35 === "string"){if(!pattern4.test(data35)){const err73 = {instancePath:instancePath+"/participants/" + i0+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}else {const err74 = {instancePath:instancePath+"/participants/" + i0+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data34.type !== undefined){let data36 = data34.type;if(!(((((((data36 === "frontend") || (data36 === "backend")) || (data36 === "database")) || (data36 === "cloud")) || (data36 === "security")) || (data36 === "messagebus")) || (data36 === "external"))){const err75 = {instancePath:instancePath+"/participants/" + i0+"/type",schemaPath:"common.schema.json#/$defs/componentType/enum",keyword:"enum",params:{allowedValues: schema57.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data34.label !== undefined){let data37 = data34.label;if(typeof data37 === "string"){if(func3(data37) < 1){const err76 = {instancePath:instancePath+"/participants/" + i0+"/label",schemaPath:"#/properties/participants/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}else {const err77 = {instancePath:instancePath+"/participants/" + i0+"/label",schemaPath:"#/properties/participants/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}if(data34.sublabel !== undefined){if(typeof data34.sublabel !== "string"){const err78 = {instancePath:instancePath+"/participants/" + i0+"/sublabel",schemaPath:"#/properties/participants/items/properties/sublabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}if(data34.brand !== undefined){let data39 = data34.brand;const _errs93 = errors;let valid26 = false;let passing0 = null;const _errs94 = errors;const _errs96 = errors;let valid27 = false;const _errs97 = errors;if(typeof data39 === "string"){if(func3(data39) > 80){const err79 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}if(!pattern17.test(data39)){const err80 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}var _valid1 = _errs97 === errors;valid27 = valid27 || _valid1;const _errs98 = errors;if(typeof data39 === "string"){if(!pattern18.test(data39)){const err81 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}var _valid1 = _errs98 === errors;valid27 = valid27 || _valid1;if(!valid27){const err82 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}else {errors = _errs96;if(vErrors !== null){if(_errs96){vErrors.length = _errs96;}else {vErrors = null;}}}if(typeof data39 === "string"){if(func3(data39) > 2048){const err83 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}if(func3(data39) < 1){const err84 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}else {const err85 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}var _valid0 = _errs94 === errors;if(_valid0){valid26 = true;passing0 = 0;}const _errs99 = errors;if(data39 && typeof data39 == "object" && !Array.isArray(data39)){if(data39.url === undefined){const err86 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}if(data39.sha256 === undefined){const err87 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}for(const key10 in data39){if(!((key10 === "url") || (key10 === "sha256"))){const err88 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data39.url !== undefined){let data40 = data39.url;if(typeof data40 === "string"){if(func3(data40) > 2048){const err89 = {instancePath:instancePath+"/participants/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}if(func3(data40) < 8){const err90 = {instancePath:instancePath+"/participants/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}if(!pattern18.test(data40)){const err91 = {instancePath:instancePath+"/participants/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}else {const err92 = {instancePath:instancePath+"/participants/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}if(data39.sha256 !== undefined){let data41 = data39.sha256;if(typeof data41 === "string"){if(!pattern20.test(data41)){const err93 = {instancePath:instancePath+"/participants/" + i0+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}else {const err94 = {instancePath:instancePath+"/participants/" + i0+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}}else {const err95 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}var _valid0 = _errs99 === errors;if(_valid0 && valid26){valid26 = false;passing0 = [passing0, 1];}else {if(_valid0){valid26 = true;passing0 = 1;}}if(!valid26){const err96 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}else {errors = _errs93;if(vErrors !== null){if(_errs93){vErrors.length = _errs93;}else {vErrors = null;}}}}}else {const err97 = {instancePath:instancePath+"/participants/" + i0,schemaPath:"#/properties/participants/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}}else {const err98 = {instancePath:instancePath+"/participants",schemaPath:"#/properties/participants/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}if(data.segments !== undefined){let data42 = data.segments;if(Array.isArray(data42)){const len3 = data42.length;for(let i1=0; i1<len3; i1++){let data43 = data42[i1];if(data43 && typeof data43 == "object" && !Array.isArray(data43)){if(data43.from === undefined){const err99 = {instancePath:instancePath+"/segments/" + i1,schemaPath:"#/properties/segments/items/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}if(data43.to === undefined){const err100 = {instancePath:instancePath+"/segments/" + i1,schemaPath:"#/properties/segments/items/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}if(data43.label === undefined){const err101 = {instancePath:instancePath+"/segments/" + i1,schemaPath:"#/properties/segments/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}for(const key11 in data43){if(!(((key11 === "from") || (key11 === "to")) || (key11 === "label"))){const err102 = {instancePath:instancePath+"/segments/" + i1,schemaPath:"#/properties/segments/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}if(data43.from !== undefined){let data44 = data43.from;if(!((typeof data44 == "number") && (isFinite(data44)))){const err103 = {instancePath:instancePath+"/segments/" + i1+"/from",schemaPath:"#/properties/segments/items/properties/from/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data43.to !== undefined){let data45 = data43.to;if(!((typeof data45 == "number") && (isFinite(data45)))){const err104 = {instancePath:instancePath+"/segments/" + i1+"/to",schemaPath:"#/properties/segments/items/properties/to/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}if(data43.label !== undefined){let data46 = data43.label;if(typeof data46 === "string"){if(func3(data46) < 1){const err105 = {instancePath:instancePath+"/segments/" + i1+"/label",schemaPath:"#/properties/segments/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}else {const err106 = {instancePath:instancePath+"/segments/" + i1+"/label",schemaPath:"#/properties/segments/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}}else {const err107 = {instancePath:instancePath+"/segments/" + i1,schemaPath:"#/properties/segments/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}}else {const err108 = {instancePath:instancePath+"/segments",schemaPath:"#/properties/segments/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data.messages !== undefined){let data47 = data.messages;if(Array.isArray(data47)){if(data47.length < 1){const err109 = {instancePath:instancePath+"/messages",schemaPath:"#/properties/messages/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}const len4 = data47.length;for(let i2=0; i2<len4; i2++){let data48 = data47[i2];if(data48 && typeof data48 == "object" && !Array.isArray(data48)){if(data48.from === undefined){const err110 = {instancePath:instancePath+"/messages/" + i2,schemaPath:"#/properties/messages/items/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}if(data48.to === undefined){const err111 = {instancePath:instancePath+"/messages/" + i2,schemaPath:"#/properties/messages/items/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}if(data48.y === undefined){const err112 = {instancePath:instancePath+"/messages/" + i2,schemaPath:"#/properties/messages/items/required",keyword:"required",params:{missingProperty: "y"},message:"must have required property '"+"y"+"'"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}if(data48.label === undefined){const err113 = {instancePath:instancePath+"/messages/" + i2,schemaPath:"#/properties/messages/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}for(const key12 in data48){if(!(((((((key12 === "id") || (key12 === "from")) || (key12 === "to")) || (key12 === "y")) || (key12 === "label")) || (key12 === "variant")) || (key12 === "note"))){const err114 = {instancePath:instancePath+"/messages/" + i2,schemaPath:"#/properties/messages/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key12},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}if(data48.id !== undefined){let data49 = data48.id;if(typeof data49 === "string"){if(!pattern4.test(data49)){const err115 = {instancePath:instancePath+"/messages/" + i2+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}else {const err116 = {instancePath:instancePath+"/messages/" + i2+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}if(data48.from !== undefined){let data50 = data48.from;if(typeof data50 === "string"){if(!pattern4.test(data50)){const err117 = {instancePath:instancePath+"/messages/" + i2+"/from",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}else {const err118 = {instancePath:instancePath+"/messages/" + i2+"/from",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}if(data48.to !== undefined){let data51 = data48.to;if(typeof data51 === "string"){if(!pattern4.test(data51)){const err119 = {instancePath:instancePath+"/messages/" + i2+"/to",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}else {const err120 = {instancePath:instancePath+"/messages/" + i2+"/to",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}if(data48.y !== undefined){let data52 = data48.y;if((typeof data52 == "number") && (isFinite(data52))){if(data52 < 160 || isNaN(data52)){const err121 = {instancePath:instancePath+"/messages/" + i2+"/y",schemaPath:"#/properties/messages/items/properties/y/minimum",keyword:"minimum",params:{comparison: ">=", limit: 160},message:"must be >= 160"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}else {const err122 = {instancePath:instancePath+"/messages/" + i2+"/y",schemaPath:"#/properties/messages/items/properties/y/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}if(data48.label !== undefined){let data53 = data48.label;if(typeof data53 === "string"){if(func3(data53) < 1){const err123 = {instancePath:instancePath+"/messages/" + i2+"/label",schemaPath:"#/properties/messages/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}else {const err124 = {instancePath:instancePath+"/messages/" + i2+"/label",schemaPath:"#/properties/messages/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}if(data48.variant !== undefined){let data54 = data48.variant;if(!(((((data54 === "default") || (data54 === "emphasis")) || (data54 === "security")) || (data54 === "dashed")) || (data54 === "return"))){const err125 = {instancePath:instancePath+"/messages/" + i2+"/variant",schemaPath:"#/properties/messages/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema68.properties.messages.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}if(data48.note !== undefined){if(typeof data48.note !== "string"){const err126 = {instancePath:instancePath+"/messages/" + i2+"/note",schemaPath:"#/properties/messages/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}}else {const err127 = {instancePath:instancePath+"/messages/" + i2,schemaPath:"#/properties/messages/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}}else {const err128 = {instancePath:instancePath+"/messages",schemaPath:"#/properties/messages/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}if(data.activations !== undefined){let data56 = data.activations;if(Array.isArray(data56)){const len5 = data56.length;for(let i3=0; i3<len5; i3++){let data57 = data56[i3];if(data57 && typeof data57 == "object" && !Array.isArray(data57)){if(data57.participant === undefined){const err129 = {instancePath:instancePath+"/activations/" + i3,schemaPath:"#/properties/activations/items/required",keyword:"required",params:{missingProperty: "participant"},message:"must have required property '"+"participant"+"'"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}if(data57.from === undefined){const err130 = {instancePath:instancePath+"/activations/" + i3,schemaPath:"#/properties/activations/items/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}if(data57.to === undefined){const err131 = {instancePath:instancePath+"/activations/" + i3,schemaPath:"#/properties/activations/items/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}for(const key13 in data57){if(!((((key13 === "participant") || (key13 === "from")) || (key13 === "to")) || (key13 === "type"))){const err132 = {instancePath:instancePath+"/activations/" + i3,schemaPath:"#/properties/activations/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key13},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}if(data57.participant !== undefined){let data58 = data57.participant;if(typeof data58 === "string"){if(!pattern4.test(data58)){const err133 = {instancePath:instancePath+"/activations/" + i3+"/participant",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}else {const err134 = {instancePath:instancePath+"/activations/" + i3+"/participant",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}if(data57.from !== undefined){let data59 = data57.from;if(!((typeof data59 == "number") && (isFinite(data59)))){const err135 = {instancePath:instancePath+"/activations/" + i3+"/from",schemaPath:"#/properties/activations/items/properties/from/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}}if(data57.to !== undefined){let data60 = data57.to;if(!((typeof data60 == "number") && (isFinite(data60)))){const err136 = {instancePath:instancePath+"/activations/" + i3+"/to",schemaPath:"#/properties/activations/items/properties/to/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}if(data57.type !== undefined){let data61 = data57.type;if(!(((((((data61 === "frontend") || (data61 === "backend")) || (data61 === "database")) || (data61 === "cloud")) || (data61 === "security")) || (data61 === "messagebus")) || (data61 === "external"))){const err137 = {instancePath:instancePath+"/activations/" + i3+"/type",schemaPath:"common.schema.json#/$defs/componentType/enum",keyword:"enum",params:{allowedValues: schema57.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}}else {const err138 = {instancePath:instancePath+"/activations/" + i3,schemaPath:"#/properties/activations/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}}else {const err139 = {instancePath:instancePath+"/activations",schemaPath:"#/properties/activations/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data.cards !== undefined){let data62 = data.cards;if(Array.isArray(data62)){const len6 = data62.length;for(let i4=0; i4<len6; i4++){let data63 = data62[i4];if(data63 && typeof data63 == "object" && !Array.isArray(data63)){if(data63.dot === undefined){const err140 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "dot"},message:"must have required property '"+"dot"+"'"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}if(data63.title === undefined){const err141 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}if(data63.items === undefined){const err142 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "items"},message:"must have required property '"+"items"+"'"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}for(const key14 in data63){if(!(((key14 === "dot") || (key14 === "title")) || (key14 === "items"))){const err143 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key14},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data63.dot !== undefined){let data64 = data63.dot;if(!(((((((data64 === "cyan") || (data64 === "emerald")) || (data64 === "violet")) || (data64 === "amber")) || (data64 === "rose")) || (data64 === "orange")) || (data64 === "slate"))){const err144 = {instancePath:instancePath+"/cards/" + i4+"/dot",schemaPath:"common.schema.json#/$defs/cards/items/properties/dot/enum",keyword:"enum",params:{allowedValues: schema67.items.properties.dot.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}if(data63.title !== undefined){let data65 = data63.title;if(typeof data65 === "string"){if(func3(data65) < 1){const err145 = {instancePath:instancePath+"/cards/" + i4+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}else {const err146 = {instancePath:instancePath+"/cards/" + i4+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}}if(data63.items !== undefined){let data66 = data63.items;if(Array.isArray(data66)){const len7 = data66.length;for(let i5=0; i5<len7; i5++){if(typeof data66[i5] !== "string"){const err147 = {instancePath:instancePath+"/cards/" + i4+"/items/" + i5,schemaPath:"common.schema.json#/$defs/cards/items/properties/items/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}}}else {const err148 = {instancePath:instancePath+"/cards/" + i4+"/items",schemaPath:"common.schema.json#/$defs/cards/items/properties/items/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}}}else {const err149 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}}}else {const err150 = {instancePath:instancePath+"/cards",schemaPath:"common.schema.json#/$defs/cards/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}}}else {const err151 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}validate27.errors = vErrors;return errors === 0;}validate27.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};export const dataflow = validate30;const schema91 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/tt-a1i/archify/schemas/dataflow.schema.json","title":"Archify Data Flow Diagram","type":"object","additionalProperties":false,"required":["schema_version","diagram_type","meta","stages","nodes","flows"],"properties":{"schema_version":{"const":1},"diagram_type":{"const":"dataflow"},"meta":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","minLength":1},"locale":{"$ref":"common.schema.json#/$defs/locale"},"subtitle":{"type":"string"},"output":{"type":"string"},"animation":{"$ref":"common.schema.json#/$defs/animation"},"visual_preset":{"$ref":"common.schema.json#/$defs/visualPreset"},"quality_profile":{"$ref":"common.schema.json#/$defs/qualityProfile"},"views":{"$ref":"common.schema.json#/$defs/guidedViews"},"legend":{"type":"object","additionalProperties":false,"properties":{"mode":{"$ref":"common.schema.json#/$defs/legendMode"},"entries":{"type":"object","additionalProperties":false,"properties":{"default":{"$ref":"common.schema.json#/$defs/legendEntry"},"emphasis":{"$ref":"common.schema.json#/$defs/legendEntry"},"security":{"$ref":"common.schema.json#/$defs/legendEntry"},"dashed":{"$ref":"common.schema.json#/$defs/legendEntry"},"database":{"$ref":"common.schema.json#/$defs/legendEntry"}}}}},"viewBox":{"type":"array","prefixItems":[{"type":"number","minimum":360},{"type":"number","minimum":360}],"items":false,"minItems":2,"maxItems":2}}},"stages":{"type":"array","minItems":2,"maxItems":5,"items":{"type":"object","additionalProperties":false,"required":["label"],"properties":{"label":{"type":"string","minLength":1}}}},"nodes":{"type":"array","minItems":2,"items":{"type":"object","additionalProperties":false,"required":["id","type","label","stage","row"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"type":{"$ref":"common.schema.json#/$defs/componentType"},"label":{"type":"string","minLength":1},"sublabel":{"type":"string"},"tag":{"type":"string"},"brand":{"$ref":"common.schema.json#/$defs/brandMark"},"stage":{"type":"integer","minimum":0},"row":{"type":"integer","minimum":0},"width":{"type":"number","minimum":48},"height":{"type":"number","minimum":36},"yOffset":{"type":"number"}}}},"flows":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["from","to","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"classification":{"type":"string"},"variant":{"$ref":"common.schema.json#/$defs/variant"},"route":{"enum":["auto","straight","vertical-channel","bottom-channel","top-channel"]},"fromSide":{"$ref":"common.schema.json#/$defs/side"},"toSide":{"$ref":"common.schema.json#/$defs/side"},"channelX":{"type":"number"},"channelY":{"type":"number"},"labelAt":{"$ref":"common.schema.json#/$defs/point"},"labelDx":{"type":"number"},"labelDy":{"type":"number"},"labelSegment":{"type":"integer","minimum":0},"via":{"type":"array","items":{"$ref":"common.schema.json#/$defs/point"}},"width":{"$ref":"common.schema.json#/$defs/relationshipWidth"}}}},"cards":{"$ref":"common.schema.json#/$defs/cards"}}};const schema112 = {"enum":["left","right","top","bottom"]};const schema116 = {"type":"number","minimum":0.5};function validate31(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate31.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(Array.isArray(data)){if(data.length > 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0<len0; i0++){let data0 = data[i0];if(data0 && typeof data0 == "object" && !Array.isArray(data0)){if(data0.id === undefined){const err1 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data0.label === undefined){const err2 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data0.focus === undefined){const err3 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "focus"},message:"must have required property '"+"focus"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data0){if(!((((key0 === "id") || (key0 === "label")) || (key0 === "focus")) || (key0 === "note"))){const err4 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data0.id !== undefined){let data1 = data0.id;if(typeof data1 === "string"){if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data0.label !== undefined){let data2 = data0.label;if(typeof data2 === "string"){if(func3(data2) > 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1<len1; i1++){let data4 = data3[i1];if(typeof data4 === "string"){if(!pattern4.test(data4)){const err11 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}else {const err13 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data0.note !== undefined){let data5 = data0.note;if(typeof data5 === "string"){if(func3(data5) > 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate31.errors = vErrors;return errors === 0;}validate31.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};function validate30(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/dataflow.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate30.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.stages === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "stages"},message:"must have required property '"+"stages"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.nodes === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "nodes"},message:"must have required property '"+"nodes"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.flows === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "flows"},message:"must have required property '"+"flows"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!(((((((key0 === "schema_version") || (key0 === "diagram_type")) || (key0 === "meta")) || (key0 === "stages")) || (key0 === "nodes")) || (key0 === "flows")) || (key0 === "cards"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err7 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.diagram_type !== undefined){if("dataflow" !== data.diagram_type){const err8 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "dataflow"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}for(const key1 in data2){if(!(func1.call(schema91.properties.meta.properties, key1))){const err10 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err13 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err14 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err15 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err16 = {instancePath:instancePath+"/meta/animation",schemaPath:"common.schema.json#/$defs/animation/enum",keyword:"enum",params:{allowedValues: schema70.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err17 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"common.schema.json#/$defs/visualPreset/enum",keyword:"enum",params:{allowedValues: schema71.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err18 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"common.schema.json#/$defs/qualityProfile/enum",keyword:"enum",params:{allowedValues: schema72.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data2.views !== undefined){if(!(validate31(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate31.errors : vErrors.concat(validate31.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data11 = data2.legend;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){for(const key2 in data11){if(!((key2 === "mode") || (key2 === "entries"))){const err19 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data11.mode !== undefined){let data12 = data11.mode;if(!(((data12 === "auto") || (data12 === "all")) || (data12 === "hidden"))){const err20 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data11.entries !== undefined){let data13 = data11.entries;if(data13 && typeof data13 == "object" && !Array.isArray(data13)){for(const key3 in data13){if(!(((((key3 === "default") || (key3 === "emphasis")) || (key3 === "security")) || (key3 === "dashed")) || (key3 === "database"))){const err21 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data13.default !== undefined){let data14 = data13.default;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){if(Object.keys(data14).length < 1){const err22 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}for(const key4 in data14){if(!((key4 === "label") || (key4 === "visible"))){const err23 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data14.label !== undefined){let data15 = data14.label;if(typeof data15 === "string"){if(func3(data15) > 80){const err24 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(func3(data15) < 1){const err25 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data14.visible !== undefined){if(typeof data14.visible !== "boolean"){const err27 = {instancePath:instancePath+"/meta/legend/entries/default/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data13.emphasis !== undefined){let data17 = data13.emphasis;if(data17 && typeof data17 == "object" && !Array.isArray(data17)){if(Object.keys(data17).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data17){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data17.label !== undefined){let data18 = data17.label;if(typeof data18 === "string"){if(func3(data18) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data18) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data17.visible !== undefined){if(typeof data17.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/emphasis/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data13.security !== undefined){let data20 = data13.security;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(Object.keys(data20).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data20){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data20.label !== undefined){let data21 = data20.label;if(typeof data21 === "string"){if(func3(data21) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data21) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data20.visible !== undefined){if(typeof data20.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/security/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data13.dashed !== undefined){let data23 = data13.dashed;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(Object.keys(data23).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data23){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data23.label !== undefined){let data24 = data23.label;if(typeof data24 === "string"){if(func3(data24) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data24) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data23.visible !== undefined){if(typeof data23.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/dashed/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data13.database !== undefined){let data26 = data13.database;if(data26 && typeof data26 == "object" && !Array.isArray(data26)){if(Object.keys(data26).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data26){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data26.label !== undefined){let data27 = data26.label;if(typeof data27 === "string"){if(func3(data27) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data27) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data26.visible !== undefined){if(typeof data26.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/database/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}else {const err57 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}}else {const err58 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data2.viewBox !== undefined){let data29 = data2.viewBox;if(Array.isArray(data29)){if(data29.length > 2){const err59 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(data29.length < 2){const err60 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}const len0 = data29.length;if(len0 > 0){let data30 = data29[0];if((typeof data30 == "number") && (isFinite(data30))){if(data30 < 360 || isNaN(data30)){const err61 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 360},message:"must be >= 360"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}else {const err62 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(len0 > 1){let data31 = data29[1];if((typeof data31 == "number") && (isFinite(data31))){if(data31 < 360 || isNaN(data31)){const err63 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 360},message:"must be >= 360"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}else {const err64 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}const len1 = data29.length;if(!(len1 <= 2)){const err65 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}else {const err66 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}}else {const err67 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}if(data.stages !== undefined){let data32 = data.stages;if(Array.isArray(data32)){if(data32.length > 5){const err68 = {instancePath:instancePath+"/stages",schemaPath:"#/properties/stages/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}if(data32.length < 2){const err69 = {instancePath:instancePath+"/stages",schemaPath:"#/properties/stages/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}const len2 = data32.length;for(let i0=0; i0<len2; i0++){let data33 = data32[i0];if(data33 && typeof data33 == "object" && !Array.isArray(data33)){if(data33.label === undefined){const err70 = {instancePath:instancePath+"/stages/" + i0,schemaPath:"#/properties/stages/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}for(const key9 in data33){if(!(key9 === "label")){const err71 = {instancePath:instancePath+"/stages/" + i0,schemaPath:"#/properties/stages/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}if(data33.label !== undefined){let data34 = data33.label;if(typeof data34 === "string"){if(func3(data34) < 1){const err72 = {instancePath:instancePath+"/stages/" + i0+"/label",schemaPath:"#/properties/stages/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}else {const err73 = {instancePath:instancePath+"/stages/" + i0+"/label",schemaPath:"#/properties/stages/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}}else {const err74 = {instancePath:instancePath+"/stages/" + i0,schemaPath:"#/properties/stages/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}}else {const err75 = {instancePath:instancePath+"/stages",schemaPath:"#/properties/stages/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data.nodes !== undefined){let data35 = data.nodes;if(Array.isArray(data35)){if(data35.length < 2){const err76 = {instancePath:instancePath+"/nodes",schemaPath:"#/properties/nodes/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}const len3 = data35.length;for(let i1=0; i1<len3; i1++){let data36 = data35[i1];if(data36 && typeof data36 == "object" && !Array.isArray(data36)){if(data36.id === undefined){const err77 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}if(data36.type === undefined){const err78 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}if(data36.label === undefined){const err79 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}if(data36.stage === undefined){const err80 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "stage"},message:"must have required property '"+"stage"+"'"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}if(data36.row === undefined){const err81 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/required",keyword:"required",params:{missingProperty: "row"},message:"must have required property '"+"row"+"'"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}for(const key10 in data36){if(!(func1.call(schema91.properties.nodes.items.properties, key10))){const err82 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}if(data36.id !== undefined){let data37 = data36.id;if(typeof data37 === "string"){if(!pattern4.test(data37)){const err83 = {instancePath:instancePath+"/nodes/" + i1+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}else {const err84 = {instancePath:instancePath+"/nodes/" + i1+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}if(data36.type !== undefined){let data38 = data36.type;if(!(((((((data38 === "frontend") || (data38 === "backend")) || (data38 === "database")) || (data38 === "cloud")) || (data38 === "security")) || (data38 === "messagebus")) || (data38 === "external"))){const err85 = {instancePath:instancePath+"/nodes/" + i1+"/type",schemaPath:"common.schema.json#/$defs/componentType/enum",keyword:"enum",params:{allowedValues: schema57.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}if(data36.label !== undefined){let data39 = data36.label;if(typeof data39 === "string"){if(func3(data39) < 1){const err86 = {instancePath:instancePath+"/nodes/" + i1+"/label",schemaPath:"#/properties/nodes/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}else {const err87 = {instancePath:instancePath+"/nodes/" + i1+"/label",schemaPath:"#/properties/nodes/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}if(data36.sublabel !== undefined){if(typeof data36.sublabel !== "string"){const err88 = {instancePath:instancePath+"/nodes/" + i1+"/sublabel",schemaPath:"#/properties/nodes/items/properties/sublabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data36.tag !== undefined){if(typeof data36.tag !== "string"){const err89 = {instancePath:instancePath+"/nodes/" + i1+"/tag",schemaPath:"#/properties/nodes/items/properties/tag/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}if(data36.brand !== undefined){let data42 = data36.brand;const _errs101 = errors;let valid29 = false;let passing0 = null;const _errs102 = errors;const _errs104 = errors;let valid30 = false;const _errs105 = errors;if(typeof data42 === "string"){if(func3(data42) > 80){const err90 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}if(!pattern17.test(data42)){const err91 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}var _valid1 = _errs105 === errors;valid30 = valid30 || _valid1;const _errs106 = errors;if(typeof data42 === "string"){if(!pattern18.test(data42)){const err92 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}var _valid1 = _errs106 === errors;valid30 = valid30 || _valid1;if(!valid30){const err93 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}else {errors = _errs104;if(vErrors !== null){if(_errs104){vErrors.length = _errs104;}else {vErrors = null;}}}if(typeof data42 === "string"){if(func3(data42) > 2048){const err94 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}if(func3(data42) < 1){const err95 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}else {const err96 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}var _valid0 = _errs102 === errors;if(_valid0){valid29 = true;passing0 = 0;}const _errs107 = errors;if(data42 && typeof data42 == "object" && !Array.isArray(data42)){if(data42.url === undefined){const err97 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}if(data42.sha256 === undefined){const err98 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}for(const key11 in data42){if(!((key11 === "url") || (key11 === "sha256"))){const err99 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data42.url !== undefined){let data43 = data42.url;if(typeof data43 === "string"){if(func3(data43) > 2048){const err100 = {instancePath:instancePath+"/nodes/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}if(func3(data43) < 8){const err101 = {instancePath:instancePath+"/nodes/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}if(!pattern18.test(data43)){const err102 = {instancePath:instancePath+"/nodes/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}else {const err103 = {instancePath:instancePath+"/nodes/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data42.sha256 !== undefined){let data44 = data42.sha256;if(typeof data44 === "string"){if(!pattern20.test(data44)){const err104 = {instancePath:instancePath+"/nodes/" + i1+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}else {const err105 = {instancePath:instancePath+"/nodes/" + i1+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}}else {const err106 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}var _valid0 = _errs107 === errors;if(_valid0 && valid29){valid29 = false;passing0 = [passing0, 1];}else {if(_valid0){valid29 = true;passing0 = 1;}}if(!valid29){const err107 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}else {errors = _errs101;if(vErrors !== null){if(_errs101){vErrors.length = _errs101;}else {vErrors = null;}}}}if(data36.stage !== undefined){let data45 = data36.stage;if(!(((typeof data45 == "number") && (!(data45 % 1) && !isNaN(data45))) && (isFinite(data45)))){const err108 = {instancePath:instancePath+"/nodes/" + i1+"/stage",schemaPath:"#/properties/nodes/items/properties/stage/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}if((typeof data45 == "number") && (isFinite(data45))){if(data45 < 0 || isNaN(data45)){const err109 = {instancePath:instancePath+"/nodes/" + i1+"/stage",schemaPath:"#/properties/nodes/items/properties/stage/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}}if(data36.row !== undefined){let data46 = data36.row;if(!(((typeof data46 == "number") && (!(data46 % 1) && !isNaN(data46))) && (isFinite(data46)))){const err110 = {instancePath:instancePath+"/nodes/" + i1+"/row",schemaPath:"#/properties/nodes/items/properties/row/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}if((typeof data46 == "number") && (isFinite(data46))){if(data46 < 0 || isNaN(data46)){const err111 = {instancePath:instancePath+"/nodes/" + i1+"/row",schemaPath:"#/properties/nodes/items/properties/row/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}}if(data36.width !== undefined){let data47 = data36.width;if((typeof data47 == "number") && (isFinite(data47))){if(data47 < 48 || isNaN(data47)){const err112 = {instancePath:instancePath+"/nodes/" + i1+"/width",schemaPath:"#/properties/nodes/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 48},message:"must be >= 48"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}else {const err113 = {instancePath:instancePath+"/nodes/" + i1+"/width",schemaPath:"#/properties/nodes/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}if(data36.height !== undefined){let data48 = data36.height;if((typeof data48 == "number") && (isFinite(data48))){if(data48 < 36 || isNaN(data48)){const err114 = {instancePath:instancePath+"/nodes/" + i1+"/height",schemaPath:"#/properties/nodes/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 36},message:"must be >= 36"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}else {const err115 = {instancePath:instancePath+"/nodes/" + i1+"/height",schemaPath:"#/properties/nodes/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}if(data36.yOffset !== undefined){let data49 = data36.yOffset;if(!((typeof data49 == "number") && (isFinite(data49)))){const err116 = {instancePath:instancePath+"/nodes/" + i1+"/yOffset",schemaPath:"#/properties/nodes/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}}else {const err117 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}}else {const err118 = {instancePath:instancePath+"/nodes",schemaPath:"#/properties/nodes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}if(data.flows !== undefined){let data50 = data.flows;if(Array.isArray(data50)){const len4 = data50.length;for(let i2=0; i2<len4; i2++){let data51 = data50[i2];if(data51 && typeof data51 == "object" && !Array.isArray(data51)){if(data51.from === undefined){const err119 = {instancePath:instancePath+"/flows/" + i2,schemaPath:"#/properties/flows/items/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}if(data51.to === undefined){const err120 = {instancePath:instancePath+"/flows/" + i2,schemaPath:"#/properties/flows/items/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}if(data51.label === undefined){const err121 = {instancePath:instancePath+"/flows/" + i2,schemaPath:"#/properties/flows/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}for(const key12 in data51){if(!(func1.call(schema91.properties.flows.items.properties, key12))){const err122 = {instancePath:instancePath+"/flows/" + i2,schemaPath:"#/properties/flows/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key12},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}if(data51.id !== undefined){let data52 = data51.id;if(typeof data52 === "string"){if(!pattern4.test(data52)){const err123 = {instancePath:instancePath+"/flows/" + i2+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}else {const err124 = {instancePath:instancePath+"/flows/" + i2+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}if(data51.from !== undefined){let data53 = data51.from;if(typeof data53 === "string"){if(!pattern4.test(data53)){const err125 = {instancePath:instancePath+"/flows/" + i2+"/from",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}else {const err126 = {instancePath:instancePath+"/flows/" + i2+"/from",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}if(data51.to !== undefined){let data54 = data51.to;if(typeof data54 === "string"){if(!pattern4.test(data54)){const err127 = {instancePath:instancePath+"/flows/" + i2+"/to",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}else {const err128 = {instancePath:instancePath+"/flows/" + i2+"/to",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}if(data51.label !== undefined){let data55 = data51.label;if(typeof data55 === "string"){if(func3(data55) < 1){const err129 = {instancePath:instancePath+"/flows/" + i2+"/label",schemaPath:"#/properties/flows/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}}else {const err130 = {instancePath:instancePath+"/flows/" + i2+"/label",schemaPath:"#/properties/flows/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}}if(data51.classification !== undefined){if(typeof data51.classification !== "string"){const err131 = {instancePath:instancePath+"/flows/" + i2+"/classification",schemaPath:"#/properties/flows/items/properties/classification/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}if(data51.variant !== undefined){let data57 = data51.variant;if(!((((data57 === "default") || (data57 === "emphasis")) || (data57 === "security")) || (data57 === "dashed"))){const err132 = {instancePath:instancePath+"/flows/" + i2+"/variant",schemaPath:"common.schema.json#/$defs/variant/enum",keyword:"enum",params:{allowedValues: schema62.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}if(data51.route !== undefined){let data58 = data51.route;if(!(((((data58 === "auto") || (data58 === "straight")) || (data58 === "vertical-channel")) || (data58 === "bottom-channel")) || (data58 === "top-channel"))){const err133 = {instancePath:instancePath+"/flows/" + i2+"/route",schemaPath:"#/properties/flows/items/properties/route/enum",keyword:"enum",params:{allowedValues: schema91.properties.flows.items.properties.route.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}if(data51.fromSide !== undefined){let data59 = data51.fromSide;if(!((((data59 === "left") || (data59 === "right")) || (data59 === "top")) || (data59 === "bottom"))){const err134 = {instancePath:instancePath+"/flows/" + i2+"/fromSide",schemaPath:"common.schema.json#/$defs/side/enum",keyword:"enum",params:{allowedValues: schema112.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}if(data51.toSide !== undefined){let data60 = data51.toSide;if(!((((data60 === "left") || (data60 === "right")) || (data60 === "top")) || (data60 === "bottom"))){const err135 = {instancePath:instancePath+"/flows/" + i2+"/toSide",schemaPath:"common.schema.json#/$defs/side/enum",keyword:"enum",params:{allowedValues: schema112.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}}if(data51.channelX !== undefined){let data61 = data51.channelX;if(!((typeof data61 == "number") && (isFinite(data61)))){const err136 = {instancePath:instancePath+"/flows/" + i2+"/channelX",schemaPath:"#/properties/flows/items/properties/channelX/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}if(data51.channelY !== undefined){let data62 = data51.channelY;if(!((typeof data62 == "number") && (isFinite(data62)))){const err137 = {instancePath:instancePath+"/flows/" + i2+"/channelY",schemaPath:"#/properties/flows/items/properties/channelY/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}if(data51.labelAt !== undefined){let data63 = data51.labelAt;if(Array.isArray(data63)){if(data63.length > 2){const err138 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}if(data63.length < 2){const err139 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}const len5 = data63.length;if(len5 > 0){let data64 = data63[0];if(!((typeof data64 == "number") && (isFinite(data64)))){const err140 = {instancePath:instancePath+"/flows/" + i2+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}if(len5 > 1){let data65 = data63[1];if(!((typeof data65 == "number") && (isFinite(data65)))){const err141 = {instancePath:instancePath+"/flows/" + i2+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}const len6 = data63.length;if(!(len6 <= 2)){const err142 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}else {const err143 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data51.labelDx !== undefined){let data66 = data51.labelDx;if(!((typeof data66 == "number") && (isFinite(data66)))){const err144 = {instancePath:instancePath+"/flows/" + i2+"/labelDx",schemaPath:"#/properties/flows/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}if(data51.labelDy !== undefined){let data67 = data51.labelDy;if(!((typeof data67 == "number") && (isFinite(data67)))){const err145 = {instancePath:instancePath+"/flows/" + i2+"/labelDy",schemaPath:"#/properties/flows/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}if(data51.labelSegment !== undefined){let data68 = data51.labelSegment;if(!(((typeof data68 == "number") && (!(data68 % 1) && !isNaN(data68))) && (isFinite(data68)))){const err146 = {instancePath:instancePath+"/flows/" + i2+"/labelSegment",schemaPath:"#/properties/flows/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}if((typeof data68 == "number") && (isFinite(data68))){if(data68 < 0 || isNaN(data68)){const err147 = {instancePath:instancePath+"/flows/" + i2+"/labelSegment",schemaPath:"#/properties/flows/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}}}if(data51.via !== undefined){let data69 = data51.via;if(Array.isArray(data69)){const len7 = data69.length;for(let i3=0; i3<len7; i3++){let data70 = data69[i3];if(Array.isArray(data70)){if(data70.length > 2){const err148 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}if(data70.length < 2){const err149 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}const len8 = data70.length;if(len8 > 0){let data71 = data70[0];if(!((typeof data71 == "number") && (isFinite(data71)))){const err150 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}}if(len8 > 1){let data72 = data70[1];if(!((typeof data72 == "number") && (isFinite(data72)))){const err151 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}}const len9 = data70.length;if(!(len9 <= 2)){const err152 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err152];}else {vErrors.push(err152);}errors++;}}else {const err153 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err153];}else {vErrors.push(err153);}errors++;}}}else {const err154 = {instancePath:instancePath+"/flows/" + i2+"/via",schemaPath:"#/properties/flows/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err154];}else {vErrors.push(err154);}errors++;}}if(data51.width !== undefined){let data73 = data51.width;if((typeof data73 == "number") && (isFinite(data73))){if(data73 < 0.5 || isNaN(data73)){const err155 = {instancePath:instancePath+"/flows/" + i2+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err155];}else {vErrors.push(err155);}errors++;}}else {const err156 = {instancePath:instancePath+"/flows/" + i2+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err156];}else {vErrors.push(err156);}errors++;}}}else {const err157 = {instancePath:instancePath+"/flows/" + i2,schemaPath:"#/properties/flows/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err157];}else {vErrors.push(err157);}errors++;}}}else {const err158 = {instancePath:instancePath+"/flows",schemaPath:"#/properties/flows/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err158];}else {vErrors.push(err158);}errors++;}}if(data.cards !== undefined){let data74 = data.cards;if(Array.isArray(data74)){const len10 = data74.length;for(let i4=0; i4<len10; i4++){let data75 = data74[i4];if(data75 && typeof data75 == "object" && !Array.isArray(data75)){if(data75.dot === undefined){const err159 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "dot"},message:"must have required property '"+"dot"+"'"};if(vErrors === null){vErrors = [err159];}else {vErrors.push(err159);}errors++;}if(data75.title === undefined){const err160 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err160];}else {vErrors.push(err160);}errors++;}if(data75.items === undefined){const err161 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "items"},message:"must have required property '"+"items"+"'"};if(vErrors === null){vErrors = [err161];}else {vErrors.push(err161);}errors++;}for(const key13 in data75){if(!(((key13 === "dot") || (key13 === "title")) || (key13 === "items"))){const err162 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key13},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}}if(data75.dot !== undefined){let data76 = data75.dot;if(!(((((((data76 === "cyan") || (data76 === "emerald")) || (data76 === "violet")) || (data76 === "amber")) || (data76 === "rose")) || (data76 === "orange")) || (data76 === "slate"))){const err163 = {instancePath:instancePath+"/cards/" + i4+"/dot",schemaPath:"common.schema.json#/$defs/cards/items/properties/dot/enum",keyword:"enum",params:{allowedValues: schema67.items.properties.dot.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}}if(data75.title !== undefined){let data77 = data75.title;if(typeof data77 === "string"){if(func3(data77) < 1){const err164 = {instancePath:instancePath+"/cards/" + i4+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}}else {const err165 = {instancePath:instancePath+"/cards/" + i4+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}}if(data75.items !== undefined){let data78 = data75.items;if(Array.isArray(data78)){const len11 = data78.length;for(let i5=0; i5<len11; i5++){if(typeof data78[i5] !== "string"){const err166 = {instancePath:instancePath+"/cards/" + i4+"/items/" + i5,schemaPath:"common.schema.json#/$defs/cards/items/properties/items/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}}}else {const err167 = {instancePath:instancePath+"/cards/" + i4+"/items",schemaPath:"common.schema.json#/$defs/cards/items/properties/items/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}}}else {const err168 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}}}else {const err169 = {instancePath:instancePath+"/cards",schemaPath:"common.schema.json#/$defs/cards/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}}}else {const err170 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}validate30.errors = vErrors;return errors === 0;}validate30.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};export const lifecycle = validate33;const schema118 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/tt-a1i/archify/schemas/lifecycle.schema.json","title":"Archify Lifecycle Diagram","type":"object","additionalProperties":false,"required":["schema_version","diagram_type","meta","lanes","states","transitions"],"properties":{"schema_version":{"const":1},"diagram_type":{"const":"lifecycle"},"meta":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","minLength":1},"locale":{"$ref":"common.schema.json#/$defs/locale"},"subtitle":{"type":"string"},"output":{"type":"string"},"animation":{"$ref":"common.schema.json#/$defs/animation"},"visual_preset":{"$ref":"common.schema.json#/$defs/visualPreset"},"quality_profile":{"$ref":"common.schema.json#/$defs/qualityProfile"},"views":{"$ref":"common.schema.json#/$defs/guidedViews"},"legend":{"type":"object","additionalProperties":false,"properties":{"mode":{"$ref":"common.schema.json#/$defs/legendMode"},"entries":{"type":"object","additionalProperties":false,"properties":{"start":{"$ref":"common.schema.json#/$defs/legendEntry"},"active":{"$ref":"common.schema.json#/$defs/legendEntry"},"waiting":{"$ref":"common.schema.json#/$defs/legendEntry"},"decision":{"$ref":"common.schema.json#/$defs/legendEntry"},"success":{"$ref":"common.schema.json#/$defs/legendEntry"},"failure":{"$ref":"common.schema.json#/$defs/legendEntry"},"neutral":{"$ref":"common.schema.json#/$defs/legendEntry"},"external":{"$ref":"common.schema.json#/$defs/legendEntry"}}}}},"viewBox":{"type":"array","prefixItems":[{"type":"number","minimum":420},{"type":"number","minimum":566}],"items":false,"minItems":2,"maxItems":2}}},"lanes":{"type":"array","minItems":1,"maxItems":4,"items":{"type":"object","additionalProperties":false,"required":["id","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1}}}},"states":{"type":"array","minItems":2,"items":{"type":"object","additionalProperties":false,"required":["id","type","label","lane","col"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"type":{"enum":["start","active","waiting","decision","success","failure","neutral","external"]},"label":{"type":"string","minLength":1},"sublabel":{"type":"string"},"tag":{"type":"string"},"brand":{"$ref":"common.schema.json#/$defs/brandMark"},"step":{"type":"string"},"lane":{"$ref":"common.schema.json#/$defs/id"},"col":{"type":"integer","minimum":0,"maximum":4},"width":{"type":"number","minimum":48},"height":{"type":"number","minimum":36},"yOffset":{"type":"number"}}}},"transitions":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string"},"note":{"type":"string"},"variant":{"$ref":"common.schema.json#/$defs/variant"},"route":{"enum":["auto","straight","drop","bottom-channel","top-channel","right-channel","left-channel"]},"fromSide":{"$ref":"common.schema.json#/$defs/side"},"toSide":{"$ref":"common.schema.json#/$defs/side"},"channelX":{"type":"number"},"channelY":{"type":"number"},"cornerRadius":{"type":"number","minimum":0},"labelAt":{"$ref":"common.schema.json#/$defs/point"},"labelDx":{"type":"number"},"labelDy":{"type":"number"},"labelSegment":{"type":"integer","minimum":0},"via":{"type":"array","items":{"$ref":"common.schema.json#/$defs/point"}},"width":{"$ref":"common.schema.json#/$defs/relationshipWidth"}}}},"cards":{"$ref":"common.schema.json#/$defs/cards"}}};function validate34(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate34.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(Array.isArray(data)){if(data.length > 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0<len0; i0++){let data0 = data[i0];if(data0 && typeof data0 == "object" && !Array.isArray(data0)){if(data0.id === undefined){const err1 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data0.label === undefined){const err2 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data0.focus === undefined){const err3 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "focus"},message:"must have required property '"+"focus"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data0){if(!((((key0 === "id") || (key0 === "label")) || (key0 === "focus")) || (key0 === "note"))){const err4 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data0.id !== undefined){let data1 = data0.id;if(typeof data1 === "string"){if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data0.label !== undefined){let data2 = data0.label;if(typeof data2 === "string"){if(func3(data2) > 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1<len1; i1++){let data4 = data3[i1];if(typeof data4 === "string"){if(!pattern4.test(data4)){const err11 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}else {const err13 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data0.note !== undefined){let data5 = data0.note;if(typeof data5 === "string"){if(func3(data5) > 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate34.errors = vErrors;return errors === 0;}validate34.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};function validate33(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/lifecycle.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate33.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.lanes === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "lanes"},message:"must have required property '"+"lanes"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.states === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "states"},message:"must have required property '"+"states"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.transitions === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "transitions"},message:"must have required property '"+"transitions"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!(((((((key0 === "schema_version") || (key0 === "diagram_type")) || (key0 === "meta")) || (key0 === "lanes")) || (key0 === "states")) || (key0 === "transitions")) || (key0 === "cards"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err7 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.diagram_type !== undefined){if("lifecycle" !== data.diagram_type){const err8 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "lifecycle"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}for(const key1 in data2){if(!(func1.call(schema118.properties.meta.properties, key1))){const err10 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err13 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err14 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err15 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err16 = {instancePath:instancePath+"/meta/animation",schemaPath:"common.schema.json#/$defs/animation/enum",keyword:"enum",params:{allowedValues: schema70.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err17 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"common.schema.json#/$defs/visualPreset/enum",keyword:"enum",params:{allowedValues: schema71.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err18 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"common.schema.json#/$defs/qualityProfile/enum",keyword:"enum",params:{allowedValues: schema72.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data2.views !== undefined){if(!(validate34(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate34.errors : vErrors.concat(validate34.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data11 = data2.legend;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){for(const key2 in data11){if(!((key2 === "mode") || (key2 === "entries"))){const err19 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data11.mode !== undefined){let data12 = data11.mode;if(!(((data12 === "auto") || (data12 === "all")) || (data12 === "hidden"))){const err20 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data11.entries !== undefined){let data13 = data11.entries;if(data13 && typeof data13 == "object" && !Array.isArray(data13)){for(const key3 in data13){if(!((((((((key3 === "start") || (key3 === "active")) || (key3 === "waiting")) || (key3 === "decision")) || (key3 === "success")) || (key3 === "failure")) || (key3 === "neutral")) || (key3 === "external"))){const err21 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data13.start !== undefined){let data14 = data13.start;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){if(Object.keys(data14).length < 1){const err22 = {instancePath:instancePath+"/meta/legend/entries/start",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}for(const key4 in data14){if(!((key4 === "label") || (key4 === "visible"))){const err23 = {instancePath:instancePath+"/meta/legend/entries/start",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data14.label !== undefined){let data15 = data14.label;if(typeof data15 === "string"){if(func3(data15) > 80){const err24 = {instancePath:instancePath+"/meta/legend/entries/start/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(func3(data15) < 1){const err25 = {instancePath:instancePath+"/meta/legend/entries/start/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/legend/entries/start/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data14.visible !== undefined){if(typeof data14.visible !== "boolean"){const err27 = {instancePath:instancePath+"/meta/legend/entries/start/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/meta/legend/entries/start",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data13.active !== undefined){let data17 = data13.active;if(data17 && typeof data17 == "object" && !Array.isArray(data17)){if(Object.keys(data17).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/active",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data17){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/active",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data17.label !== undefined){let data18 = data17.label;if(typeof data18 === "string"){if(func3(data18) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/active/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data18) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/active/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/active/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data17.visible !== undefined){if(typeof data17.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/active/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/active",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data13.waiting !== undefined){let data20 = data13.waiting;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(Object.keys(data20).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/waiting",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data20){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/waiting",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data20.label !== undefined){let data21 = data20.label;if(typeof data21 === "string"){if(func3(data21) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/waiting/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data21) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/waiting/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/waiting/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data20.visible !== undefined){if(typeof data20.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/waiting/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/waiting",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data13.decision !== undefined){let data23 = data13.decision;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(Object.keys(data23).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/decision",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data23){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/decision",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data23.label !== undefined){let data24 = data23.label;if(typeof data24 === "string"){if(func3(data24) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/decision/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data24) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/decision/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/decision/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data23.visible !== undefined){if(typeof data23.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/decision/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/decision",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data13.success !== undefined){let data26 = data13.success;if(data26 && typeof data26 == "object" && !Array.isArray(data26)){if(Object.keys(data26).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/success",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data26){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/success",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data26.label !== undefined){let data27 = data26.label;if(typeof data27 === "string"){if(func3(data27) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/success/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data27) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/success/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/success/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data26.visible !== undefined){if(typeof data26.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/success/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/success",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data13.failure !== undefined){let data29 = data13.failure;if(data29 && typeof data29 == "object" && !Array.isArray(data29)){if(Object.keys(data29).length < 1){const err57 = {instancePath:instancePath+"/meta/legend/entries/failure",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}for(const key9 in data29){if(!((key9 === "label") || (key9 === "visible"))){const err58 = {instancePath:instancePath+"/meta/legend/entries/failure",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data29.label !== undefined){let data30 = data29.label;if(typeof data30 === "string"){if(func3(data30) > 80){const err59 = {instancePath:instancePath+"/meta/legend/entries/failure/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(func3(data30) < 1){const err60 = {instancePath:instancePath+"/meta/legend/entries/failure/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/meta/legend/entries/failure/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data29.visible !== undefined){if(typeof data29.visible !== "boolean"){const err62 = {instancePath:instancePath+"/meta/legend/entries/failure/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}}else {const err63 = {instancePath:instancePath+"/meta/legend/entries/failure",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data13.neutral !== undefined){let data32 = data13.neutral;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){if(Object.keys(data32).length < 1){const err64 = {instancePath:instancePath+"/meta/legend/entries/neutral",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}for(const key10 in data32){if(!((key10 === "label") || (key10 === "visible"))){const err65 = {instancePath:instancePath+"/meta/legend/entries/neutral",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data32.label !== undefined){let data33 = data32.label;if(typeof data33 === "string"){if(func3(data33) > 80){const err66 = {instancePath:instancePath+"/meta/legend/entries/neutral/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(func3(data33) < 1){const err67 = {instancePath:instancePath+"/meta/legend/entries/neutral/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}else {const err68 = {instancePath:instancePath+"/meta/legend/entries/neutral/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}if(data32.visible !== undefined){if(typeof data32.visible !== "boolean"){const err69 = {instancePath:instancePath+"/meta/legend/entries/neutral/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}else {const err70 = {instancePath:instancePath+"/meta/legend/entries/neutral",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}if(data13.external !== undefined){let data35 = data13.external;if(data35 && typeof data35 == "object" && !Array.isArray(data35)){if(Object.keys(data35).length < 1){const err71 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}for(const key11 in data35){if(!((key11 === "label") || (key11 === "visible"))){const err72 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data35.label !== undefined){let data36 = data35.label;if(typeof data36 === "string"){if(func3(data36) > 80){const err73 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(func3(data36) < 1){const err74 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}else {const err75 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data35.visible !== undefined){if(typeof data35.visible !== "boolean"){const err76 = {instancePath:instancePath+"/meta/legend/entries/external/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}}else {const err77 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}}else {const err78 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}}else {const err79 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}if(data2.viewBox !== undefined){let data38 = data2.viewBox;if(Array.isArray(data38)){if(data38.length > 2){const err80 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}if(data38.length < 2){const err81 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}const len0 = data38.length;if(len0 > 0){let data39 = data38[0];if((typeof data39 == "number") && (isFinite(data39))){if(data39 < 420 || isNaN(data39)){const err82 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 420},message:"must be >= 420"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}else {const err83 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}if(len0 > 1){let data40 = data38[1];if((typeof data40 == "number") && (isFinite(data40))){if(data40 < 566 || isNaN(data40)){const err84 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 566},message:"must be >= 566"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}else {const err85 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}const len1 = data38.length;if(!(len1 <= 2)){const err86 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}else {const err87 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}}else {const err88 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data.lanes !== undefined){let data41 = data.lanes;if(Array.isArray(data41)){if(data41.length > 4){const err89 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/maxItems",keyword:"maxItems",params:{limit: 4},message:"must NOT have more than 4 items"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}if(data41.length < 1){const err90 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}const len2 = data41.length;for(let i0=0; i0<len2; i0++){let data42 = data41[i0];if(data42 && typeof data42 == "object" && !Array.isArray(data42)){if(data42.id === undefined){const err91 = {instancePath:instancePath+"/lanes/" + i0,schemaPath:"#/properties/lanes/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}if(data42.label === undefined){const err92 = {instancePath:instancePath+"/lanes/" + i0,schemaPath:"#/properties/lanes/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}for(const key12 in data42){if(!((key12 === "id") || (key12 === "label"))){const err93 = {instancePath:instancePath+"/lanes/" + i0,schemaPath:"#/properties/lanes/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key12},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}if(data42.id !== undefined){let data43 = data42.id;if(typeof data43 === "string"){if(!pattern4.test(data43)){const err94 = {instancePath:instancePath+"/lanes/" + i0+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}else {const err95 = {instancePath:instancePath+"/lanes/" + i0+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}if(data42.label !== undefined){let data44 = data42.label;if(typeof data44 === "string"){if(func3(data44) < 1){const err96 = {instancePath:instancePath+"/lanes/" + i0+"/label",schemaPath:"#/properties/lanes/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}}else {const err97 = {instancePath:instancePath+"/lanes/" + i0+"/label",schemaPath:"#/properties/lanes/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}}else {const err98 = {instancePath:instancePath+"/lanes/" + i0,schemaPath:"#/properties/lanes/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}}else {const err99 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data.states !== undefined){let data45 = data.states;if(Array.isArray(data45)){if(data45.length < 2){const err100 = {instancePath:instancePath+"/states",schemaPath:"#/properties/states/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}const len3 = data45.length;for(let i1=0; i1<len3; i1++){let data46 = data45[i1];if(data46 && typeof data46 == "object" && !Array.isArray(data46)){if(data46.id === undefined){const err101 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}if(data46.type === undefined){const err102 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}if(data46.label === undefined){const err103 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}if(data46.lane === undefined){const err104 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/required",keyword:"required",params:{missingProperty: "lane"},message:"must have required property '"+"lane"+"'"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}if(data46.col === undefined){const err105 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/required",keyword:"required",params:{missingProperty: "col"},message:"must have required property '"+"col"+"'"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}for(const key13 in data46){if(!(func1.call(schema118.properties.states.items.properties, key13))){const err106 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key13},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}if(data46.id !== undefined){let data47 = data46.id;if(typeof data47 === "string"){if(!pattern4.test(data47)){const err107 = {instancePath:instancePath+"/states/" + i1+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/states/" + i1+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data46.type !== undefined){let data48 = data46.type;if(!((((((((data48 === "start") || (data48 === "active")) || (data48 === "waiting")) || (data48 === "decision")) || (data48 === "success")) || (data48 === "failure")) || (data48 === "neutral")) || (data48 === "external"))){const err109 = {instancePath:instancePath+"/states/" + i1+"/type",schemaPath:"#/properties/states/items/properties/type/enum",keyword:"enum",params:{allowedValues: schema118.properties.states.items.properties.type.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}if(data46.label !== undefined){let data49 = data46.label;if(typeof data49 === "string"){if(func3(data49) < 1){const err110 = {instancePath:instancePath+"/states/" + i1+"/label",schemaPath:"#/properties/states/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}else {const err111 = {instancePath:instancePath+"/states/" + i1+"/label",schemaPath:"#/properties/states/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}if(data46.sublabel !== undefined){if(typeof data46.sublabel !== "string"){const err112 = {instancePath:instancePath+"/states/" + i1+"/sublabel",schemaPath:"#/properties/states/items/properties/sublabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}if(data46.tag !== undefined){if(typeof data46.tag !== "string"){const err113 = {instancePath:instancePath+"/states/" + i1+"/tag",schemaPath:"#/properties/states/items/properties/tag/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}if(data46.brand !== undefined){let data52 = data46.brand;const _errs127 = errors;let valid35 = false;let passing0 = null;const _errs128 = errors;const _errs130 = errors;let valid36 = false;const _errs131 = errors;if(typeof data52 === "string"){if(func3(data52) > 80){const err114 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}if(!pattern17.test(data52)){const err115 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}var _valid1 = _errs131 === errors;valid36 = valid36 || _valid1;const _errs132 = errors;if(typeof data52 === "string"){if(!pattern18.test(data52)){const err116 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}var _valid1 = _errs132 === errors;valid36 = valid36 || _valid1;if(!valid36){const err117 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}else {errors = _errs130;if(vErrors !== null){if(_errs130){vErrors.length = _errs130;}else {vErrors = null;}}}if(typeof data52 === "string"){if(func3(data52) > 2048){const err118 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}if(func3(data52) < 1){const err119 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}else {const err120 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}var _valid0 = _errs128 === errors;if(_valid0){valid35 = true;passing0 = 0;}const _errs133 = errors;if(data52 && typeof data52 == "object" && !Array.isArray(data52)){if(data52.url === undefined){const err121 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}if(data52.sha256 === undefined){const err122 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}for(const key14 in data52){if(!((key14 === "url") || (key14 === "sha256"))){const err123 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key14},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}if(data52.url !== undefined){let data53 = data52.url;if(typeof data53 === "string"){if(func3(data53) > 2048){const err124 = {instancePath:instancePath+"/states/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}if(func3(data53) < 8){const err125 = {instancePath:instancePath+"/states/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}if(!pattern18.test(data53)){const err126 = {instancePath:instancePath+"/states/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}else {const err127 = {instancePath:instancePath+"/states/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}if(data52.sha256 !== undefined){let data54 = data52.sha256;if(typeof data54 === "string"){if(!pattern20.test(data54)){const err128 = {instancePath:instancePath+"/states/" + i1+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}else {const err129 = {instancePath:instancePath+"/states/" + i1+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}}}else {const err130 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}var _valid0 = _errs133 === errors;if(_valid0 && valid35){valid35 = false;passing0 = [passing0, 1];}else {if(_valid0){valid35 = true;passing0 = 1;}}if(!valid35){const err131 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}else {errors = _errs127;if(vErrors !== null){if(_errs127){vErrors.length = _errs127;}else {vErrors = null;}}}}if(data46.step !== undefined){if(typeof data46.step !== "string"){const err132 = {instancePath:instancePath+"/states/" + i1+"/step",schemaPath:"#/properties/states/items/properties/step/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}if(data46.lane !== undefined){let data56 = data46.lane;if(typeof data56 === "string"){if(!pattern4.test(data56)){const err133 = {instancePath:instancePath+"/states/" + i1+"/lane",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}else {const err134 = {instancePath:instancePath+"/states/" + i1+"/lane",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}if(data46.col !== undefined){let data57 = data46.col;if(!(((typeof data57 == "number") && (!(data57 % 1) && !isNaN(data57))) && (isFinite(data57)))){const err135 = {instancePath:instancePath+"/states/" + i1+"/col",schemaPath:"#/properties/states/items/properties/col/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}if((typeof data57 == "number") && (isFinite(data57))){if(data57 > 4 || isNaN(data57)){const err136 = {instancePath:instancePath+"/states/" + i1+"/col",schemaPath:"#/properties/states/items/properties/col/maximum",keyword:"maximum",params:{comparison: "<=", limit: 4},message:"must be <= 4"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}if(data57 < 0 || isNaN(data57)){const err137 = {instancePath:instancePath+"/states/" + i1+"/col",schemaPath:"#/properties/states/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}}if(data46.width !== undefined){let data58 = data46.width;if((typeof data58 == "number") && (isFinite(data58))){if(data58 < 48 || isNaN(data58)){const err138 = {instancePath:instancePath+"/states/" + i1+"/width",schemaPath:"#/properties/states/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 48},message:"must be >= 48"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}else {const err139 = {instancePath:instancePath+"/states/" + i1+"/width",schemaPath:"#/properties/states/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data46.height !== undefined){let data59 = data46.height;if((typeof data59 == "number") && (isFinite(data59))){if(data59 < 36 || isNaN(data59)){const err140 = {instancePath:instancePath+"/states/" + i1+"/height",schemaPath:"#/properties/states/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 36},message:"must be >= 36"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}else {const err141 = {instancePath:instancePath+"/states/" + i1+"/height",schemaPath:"#/properties/states/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}if(data46.yOffset !== undefined){let data60 = data46.yOffset;if(!((typeof data60 == "number") && (isFinite(data60)))){const err142 = {instancePath:instancePath+"/states/" + i1+"/yOffset",schemaPath:"#/properties/states/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}}else {const err143 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}}else {const err144 = {instancePath:instancePath+"/states",schemaPath:"#/properties/states/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}if(data.transitions !== undefined){let data61 = data.transitions;if(Array.isArray(data61)){const len4 = data61.length;for(let i2=0; i2<len4; i2++){let data62 = data61[i2];if(data62 && typeof data62 == "object" && !Array.isArray(data62)){if(data62.from === undefined){const err145 = {instancePath:instancePath+"/transitions/" + i2,schemaPath:"#/properties/transitions/items/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}if(data62.to === undefined){const err146 = {instancePath:instancePath+"/transitions/" + i2,schemaPath:"#/properties/transitions/items/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}for(const key15 in data62){if(!(func1.call(schema118.properties.transitions.items.properties, key15))){const err147 = {instancePath:instancePath+"/transitions/" + i2,schemaPath:"#/properties/transitions/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key15},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}}if(data62.id !== undefined){let data63 = data62.id;if(typeof data63 === "string"){if(!pattern4.test(data63)){const err148 = {instancePath:instancePath+"/transitions/" + i2+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}}else {const err149 = {instancePath:instancePath+"/transitions/" + i2+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}}if(data62.from !== undefined){let data64 = data62.from;if(typeof data64 === "string"){if(!pattern4.test(data64)){const err150 = {instancePath:instancePath+"/transitions/" + i2+"/from",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}}else {const err151 = {instancePath:instancePath+"/transitions/" + i2+"/from",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}}if(data62.to !== undefined){let data65 = data62.to;if(typeof data65 === "string"){if(!pattern4.test(data65)){const err152 = {instancePath:instancePath+"/transitions/" + i2+"/to",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err152];}else {vErrors.push(err152);}errors++;}}else {const err153 = {instancePath:instancePath+"/transitions/" + i2+"/to",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err153];}else {vErrors.push(err153);}errors++;}}if(data62.label !== undefined){if(typeof data62.label !== "string"){const err154 = {instancePath:instancePath+"/transitions/" + i2+"/label",schemaPath:"#/properties/transitions/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err154];}else {vErrors.push(err154);}errors++;}}if(data62.note !== undefined){if(typeof data62.note !== "string"){const err155 = {instancePath:instancePath+"/transitions/" + i2+"/note",schemaPath:"#/properties/transitions/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err155];}else {vErrors.push(err155);}errors++;}}if(data62.variant !== undefined){let data68 = data62.variant;if(!((((data68 === "default") || (data68 === "emphasis")) || (data68 === "security")) || (data68 === "dashed"))){const err156 = {instancePath:instancePath+"/transitions/" + i2+"/variant",schemaPath:"common.schema.json#/$defs/variant/enum",keyword:"enum",params:{allowedValues: schema62.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err156];}else {vErrors.push(err156);}errors++;}}if(data62.route !== undefined){let data69 = data62.route;if(!(((((((data69 === "auto") || (data69 === "straight")) || (data69 === "drop")) || (data69 === "bottom-channel")) || (data69 === "top-channel")) || (data69 === "right-channel")) || (data69 === "left-channel"))){const err157 = {instancePath:instancePath+"/transitions/" + i2+"/route",schemaPath:"#/properties/transitions/items/properties/route/enum",keyword:"enum",params:{allowedValues: schema118.properties.transitions.items.properties.route.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err157];}else {vErrors.push(err157);}errors++;}}if(data62.fromSide !== undefined){let data70 = data62.fromSide;if(!((((data70 === "left") || (data70 === "right")) || (data70 === "top")) || (data70 === "bottom"))){const err158 = {instancePath:instancePath+"/transitions/" + i2+"/fromSide",schemaPath:"common.schema.json#/$defs/side/enum",keyword:"enum",params:{allowedValues: schema112.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err158];}else {vErrors.push(err158);}errors++;}}if(data62.toSide !== undefined){let data71 = data62.toSide;if(!((((data71 === "left") || (data71 === "right")) || (data71 === "top")) || (data71 === "bottom"))){const err159 = {instancePath:instancePath+"/transitions/" + i2+"/toSide",schemaPath:"common.schema.json#/$defs/side/enum",keyword:"enum",params:{allowedValues: schema112.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err159];}else {vErrors.push(err159);}errors++;}}if(data62.channelX !== undefined){let data72 = data62.channelX;if(!((typeof data72 == "number") && (isFinite(data72)))){const err160 = {instancePath:instancePath+"/transitions/" + i2+"/channelX",schemaPath:"#/properties/transitions/items/properties/channelX/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err160];}else {vErrors.push(err160);}errors++;}}if(data62.channelY !== undefined){let data73 = data62.channelY;if(!((typeof data73 == "number") && (isFinite(data73)))){const err161 = {instancePath:instancePath+"/transitions/" + i2+"/channelY",schemaPath:"#/properties/transitions/items/properties/channelY/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err161];}else {vErrors.push(err161);}errors++;}}if(data62.cornerRadius !== undefined){let data74 = data62.cornerRadius;if((typeof data74 == "number") && (isFinite(data74))){if(data74 < 0 || isNaN(data74)){const err162 = {instancePath:instancePath+"/transitions/" + i2+"/cornerRadius",schemaPath:"#/properties/transitions/items/properties/cornerRadius/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}}else {const err163 = {instancePath:instancePath+"/transitions/" + i2+"/cornerRadius",schemaPath:"#/properties/transitions/items/properties/cornerRadius/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}}if(data62.labelAt !== undefined){let data75 = data62.labelAt;if(Array.isArray(data75)){if(data75.length > 2){const err164 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}if(data75.length < 2){const err165 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}const len5 = data75.length;if(len5 > 0){let data76 = data75[0];if(!((typeof data76 == "number") && (isFinite(data76)))){const err166 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}}if(len5 > 1){let data77 = data75[1];if(!((typeof data77 == "number") && (isFinite(data77)))){const err167 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}}const len6 = data75.length;if(!(len6 <= 2)){const err168 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}}else {const err169 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}}if(data62.labelDx !== undefined){let data78 = data62.labelDx;if(!((typeof data78 == "number") && (isFinite(data78)))){const err170 = {instancePath:instancePath+"/transitions/" + i2+"/labelDx",schemaPath:"#/properties/transitions/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}}if(data62.labelDy !== undefined){let data79 = data62.labelDy;if(!((typeof data79 == "number") && (isFinite(data79)))){const err171 = {instancePath:instancePath+"/transitions/" + i2+"/labelDy",schemaPath:"#/properties/transitions/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err171];}else {vErrors.push(err171);}errors++;}}if(data62.labelSegment !== undefined){let data80 = data62.labelSegment;if(!(((typeof data80 == "number") && (!(data80 % 1) && !isNaN(data80))) && (isFinite(data80)))){const err172 = {instancePath:instancePath+"/transitions/" + i2+"/labelSegment",schemaPath:"#/properties/transitions/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err172];}else {vErrors.push(err172);}errors++;}if((typeof data80 == "number") && (isFinite(data80))){if(data80 < 0 || isNaN(data80)){const err173 = {instancePath:instancePath+"/transitions/" + i2+"/labelSegment",schemaPath:"#/properties/transitions/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err173];}else {vErrors.push(err173);}errors++;}}}if(data62.via !== undefined){let data81 = data62.via;if(Array.isArray(data81)){const len7 = data81.length;for(let i3=0; i3<len7; i3++){let data82 = data81[i3];if(Array.isArray(data82)){if(data82.length > 2){const err174 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err174];}else {vErrors.push(err174);}errors++;}if(data82.length < 2){const err175 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err175];}else {vErrors.push(err175);}errors++;}const len8 = data82.length;if(len8 > 0){let data83 = data82[0];if(!((typeof data83 == "number") && (isFinite(data83)))){const err176 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err176];}else {vErrors.push(err176);}errors++;}}if(len8 > 1){let data84 = data82[1];if(!((typeof data84 == "number") && (isFinite(data84)))){const err177 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err177];}else {vErrors.push(err177);}errors++;}}const len9 = data82.length;if(!(len9 <= 2)){const err178 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err178];}else {vErrors.push(err178);}errors++;}}else {const err179 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err179];}else {vErrors.push(err179);}errors++;}}}else {const err180 = {instancePath:instancePath+"/transitions/" + i2+"/via",schemaPath:"#/properties/transitions/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err180];}else {vErrors.push(err180);}errors++;}}if(data62.width !== undefined){let data85 = data62.width;if((typeof data85 == "number") && (isFinite(data85))){if(data85 < 0.5 || isNaN(data85)){const err181 = {instancePath:instancePath+"/transitions/" + i2+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err181];}else {vErrors.push(err181);}errors++;}}else {const err182 = {instancePath:instancePath+"/transitions/" + i2+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}}}else {const err183 = {instancePath:instancePath+"/transitions/" + i2,schemaPath:"#/properties/transitions/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}}}else {const err184 = {instancePath:instancePath+"/transitions",schemaPath:"#/properties/transitions/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}}if(data.cards !== undefined){let data86 = data.cards;if(Array.isArray(data86)){const len10 = data86.length;for(let i4=0; i4<len10; i4++){let data87 = data86[i4];if(data87 && typeof data87 == "object" && !Array.isArray(data87)){if(data87.dot === undefined){const err185 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "dot"},message:"must have required property '"+"dot"+"'"};if(vErrors === null){vErrors = [err185];}else {vErrors.push(err185);}errors++;}if(data87.title === undefined){const err186 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err186];}else {vErrors.push(err186);}errors++;}if(data87.items === undefined){const err187 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "items"},message:"must have required property '"+"items"+"'"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}for(const key16 in data87){if(!(((key16 === "dot") || (key16 === "title")) || (key16 === "items"))){const err188 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key16},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}}if(data87.dot !== undefined){let data88 = data87.dot;if(!(((((((data88 === "cyan") || (data88 === "emerald")) || (data88 === "violet")) || (data88 === "amber")) || (data88 === "rose")) || (data88 === "orange")) || (data88 === "slate"))){const err189 = {instancePath:instancePath+"/cards/" + i4+"/dot",schemaPath:"common.schema.json#/$defs/cards/items/properties/dot/enum",keyword:"enum",params:{allowedValues: schema67.items.properties.dot.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}if(data87.title !== undefined){let data89 = data87.title;if(typeof data89 === "string"){if(func3(data89) < 1){const err190 = {instancePath:instancePath+"/cards/" + i4+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}else {const err191 = {instancePath:instancePath+"/cards/" + i4+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err191];}else {vErrors.push(err191);}errors++;}}if(data87.items !== undefined){let data90 = data87.items;if(Array.isArray(data90)){const len11 = data90.length;for(let i5=0; i5<len11; i5++){if(typeof data90[i5] !== "string"){const err192 = {instancePath:instancePath+"/cards/" + i4+"/items/" + i5,schemaPath:"common.schema.json#/$defs/cards/items/properties/items/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err192];}else {vErrors.push(err192);}errors++;}}}else {const err193 = {instancePath:instancePath+"/cards/" + i4+"/items",schemaPath:"common.schema.json#/$defs/cards/items/properties/items/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err193];}else {vErrors.push(err193);}errors++;}}}else {const err194 = {instancePath:instancePath+"/cards/" + i4,schemaPath:"common.schema.json#/$defs/cards/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err194];}else {vErrors.push(err194);}errors++;}}}else {const err195 = {instancePath:instancePath+"/cards",schemaPath:"common.schema.json#/$defs/cards/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err195];}else {vErrors.push(err195);}errors++;}}}else {const err196 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err196];}else {vErrors.push(err196);}errors++;}validate33.errors = vErrors;return errors === 0;}validate33.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};export const architecture = validate36;const schema149 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/tt-a1i/archify/schemas/architecture.schema.json","title":"Archify Architecture Diagram","type":"object","additionalProperties":false,"required":["schema_version","diagram_type","meta","components"],"properties":{"schema_version":{"const":1},"diagram_type":{"const":"architecture"},"meta":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","minLength":1},"locale":{"$ref":"common.schema.json#/$defs/locale"},"subtitle":{"type":"string"},"output":{"type":"string"},"animation":{"$ref":"common.schema.json#/$defs/animation"},"visual_preset":{"$ref":"common.schema.json#/$defs/visualPreset"},"quality_profile":{"$ref":"common.schema.json#/$defs/qualityProfile"},"engineering_profile":{"enum":["deployment-ownership"]},"repository":{"type":"object","additionalProperties":false,"required":["url","revision"],"properties":{"url":{"type":"string","minLength":1},"provider":{"enum":["github","gitee"]},"link_mode":{"enum":["web","local-only"]},"revision":{"type":"string","pattern":"^[a-fA-F0-9]{40}$"}}},"views":{"$ref":"common.schema.json#/$defs/guidedViews"},"legend":{"type":"object","additionalProperties":false,"properties":{"mode":{"$ref":"common.schema.json#/$defs/legendMode"},"entries":{"type":"object","additionalProperties":false,"properties":{"frontend":{"$ref":"common.schema.json#/$defs/legendEntry"},"backend":{"$ref":"common.schema.json#/$defs/legendEntry"},"database":{"$ref":"common.schema.json#/$defs/legendEntry"},"cloud":{"$ref":"common.schema.json#/$defs/legendEntry"},"security":{"$ref":"common.schema.json#/$defs/legendEntry"},"messagebus":{"$ref":"common.schema.json#/$defs/legendEntry"},"external":{"$ref":"common.schema.json#/$defs/legendEntry"}}}}},"viewBox":{"type":"array","prefixItems":[{"type":"number","minimum":320},{"type":"number","minimum":240}],"items":false,"minItems":2,"maxItems":2}}},"layout":{"type":"object","additionalProperties":false,"required":["mode"],"properties":{"mode":{"enum":["grid"]},"origin":{"$ref":"common.schema.json#/$defs/point"},"cols":{"type":"integer","minimum":1,"maximum":12},"gapX":{"type":"number","minimum":0},"gapY":{"type":"number","minimum":0},"cellW":{"type":"number","minimum":40},"cellH":{"type":"number","minimum":24}}},"components":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["id","type","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"type":{"$ref":"common.schema.json#/$defs/componentType"},"label":{"type":"string","minLength":1},"sublabel":{"type":"string"},"tag":{"type":"string"},"brand":{"$ref":"common.schema.json#/$defs/brandMark"},"sources":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"object","additionalProperties":false,"required":["path"],"properties":{"path":{"type":"string","minLength":1,"maxLength":240},"line":{"type":"integer","minimum":1},"end_line":{"type":"integer","minimum":1},"label":{"type":"string","minLength":1,"maxLength":48}}}},"row":{"type":"integer","minimum":0},"col":{"type":"integer","minimum":0},"pos":{"$ref":"common.schema.json#/$defs/point"},"size":{"type":"array","prefixItems":[{"type":"number","exclusiveMinimum":0},{"type":"number","exclusiveMinimum":0}],"items":false,"minItems":2,"maxItems":2}}}},"boundaries":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["kind","label","wraps"],"properties":{"kind":{"enum":["region","security-group"]},"label":{"type":"string","minLength":1},"wraps":{"type":"array","minItems":1,"items":{"$ref":"common.schema.json#/$defs/id"}},"pad":{"type":"number","minimum":0}}}},"connections":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string"},"variant":{"$ref":"common.schema.json#/$defs/variant"},"fromSide":{"$ref":"common.schema.json#/$defs/side"},"toSide":{"$ref":"common.schema.json#/$defs/side"},"route":{"enum":["auto","straight","orthogonal-h","orthogonal-v"]},"via":{"type":"array","items":{"$ref":"common.schema.json#/$defs/point"}},"labelAt":{"$ref":"common.schema.json#/$defs/point"},"labelDx":{"type":"number"},"labelDy":{"type":"number"},"labelSegment":{"type":"integer","minimum":0},"width":{"$ref":"common.schema.json#/$defs/relationshipWidth"}}}},"cards":{"$ref":"common.schema.json#/$defs/cards"}}};const pattern57 = new RegExp("^[a-fA-F0-9]{40}$", "u");function validate37(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate37.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(Array.isArray(data)){if(data.length > 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0<len0; i0++){let data0 = data[i0];if(data0 && typeof data0 == "object" && !Array.isArray(data0)){if(data0.id === undefined){const err1 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data0.label === undefined){const err2 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data0.focus === undefined){const err3 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/required",keyword:"required",params:{missingProperty: "focus"},message:"must have required property '"+"focus"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data0){if(!((((key0 === "id") || (key0 === "label")) || (key0 === "focus")) || (key0 === "note"))){const err4 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data0.id !== undefined){let data1 = data0.id;if(typeof data1 === "string"){if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/" + i0+"/id",schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data0.label !== undefined){let data2 = data0.label;if(typeof data2 === "string"){if(func3(data2) > 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1<len1; i1++){let data4 = data3[i1];if(typeof data4 === "string"){if(!pattern4.test(data4)){const err11 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/" + i0+"/focus/" + i1,schemaPath:"#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}}else {const err13 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data0.note !== undefined){let data5 = data0.note;if(typeof data5 === "string"){if(func3(data5) > 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate37.errors = vErrors;return errors === 0;}validate37.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};function validate36(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/architecture.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate36.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.components === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "components"},message:"must have required property '"+"components"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema_version") || (key0 === "diagram_type")) || (key0 === "meta")) || (key0 === "layout")) || (key0 === "components")) || (key0 === "boundaries")) || (key0 === "connections")) || (key0 === "cards"))){const err4 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err5 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.diagram_type !== undefined){if("architecture" !== data.diagram_type){const err6 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "architecture"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err7 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}for(const key1 in data2){if(!(func1.call(schema149.properties.meta.properties, key1))){const err8 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err9 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err11 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err12 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err13 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err14 = {instancePath:instancePath+"/meta/animation",schemaPath:"common.schema.json#/$defs/animation/enum",keyword:"enum",params:{allowedValues: schema70.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err15 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"common.schema.json#/$defs/visualPreset/enum",keyword:"enum",params:{allowedValues: schema71.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err16 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"common.schema.json#/$defs/qualityProfile/enum",keyword:"enum",params:{allowedValues: schema72.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.engineering_profile !== undefined){if(!(data2.engineering_profile === "deployment-ownership")){const err17 = {instancePath:instancePath+"/meta/engineering_profile",schemaPath:"#/properties/meta/properties/engineering_profile/enum",keyword:"enum",params:{allowedValues: schema149.properties.meta.properties.engineering_profile.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.repository !== undefined){let data11 = data2.repository;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){if(data11.url === undefined){const err18 = {instancePath:instancePath+"/meta/repository",schemaPath:"#/properties/meta/properties/repository/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(data11.revision === undefined){const err19 = {instancePath:instancePath+"/meta/repository",schemaPath:"#/properties/meta/properties/repository/required",keyword:"required",params:{missingProperty: "revision"},message:"must have required property '"+"revision"+"'"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}for(const key2 in data11){if(!((((key2 === "url") || (key2 === "provider")) || (key2 === "link_mode")) || (key2 === "revision"))){const err20 = {instancePath:instancePath+"/meta/repository",schemaPath:"#/properties/meta/properties/repository/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data11.url !== undefined){let data12 = data11.url;if(typeof data12 === "string"){if(func3(data12) < 1){const err21 = {instancePath:instancePath+"/meta/repository/url",schemaPath:"#/properties/meta/properties/repository/properties/url/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/meta/repository/url",schemaPath:"#/properties/meta/properties/repository/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data11.provider !== undefined){let data13 = data11.provider;if(!((data13 === "github") || (data13 === "gitee"))){const err23 = {instancePath:instancePath+"/meta/repository/provider",schemaPath:"#/properties/meta/properties/repository/properties/provider/enum",keyword:"enum",params:{allowedValues: schema149.properties.meta.properties.repository.properties.provider.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data11.link_mode !== undefined){let data14 = data11.link_mode;if(!((data14 === "web") || (data14 === "local-only"))){const err24 = {instancePath:instancePath+"/meta/repository/link_mode",schemaPath:"#/properties/meta/properties/repository/properties/link_mode/enum",keyword:"enum",params:{allowedValues: schema149.properties.meta.properties.repository.properties.link_mode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data11.revision !== undefined){let data15 = data11.revision;if(typeof data15 === "string"){if(!pattern57.test(data15)){const err25 = {instancePath:instancePath+"/meta/repository/revision",schemaPath:"#/properties/meta/properties/repository/properties/revision/pattern",keyword:"pattern",params:{pattern: "^[a-fA-F0-9]{40}$"},message:"must match pattern \""+"^[a-fA-F0-9]{40}$"+"\""};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/repository/revision",schemaPath:"#/properties/meta/properties/repository/properties/revision/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}}else {const err27 = {instancePath:instancePath+"/meta/repository",schemaPath:"#/properties/meta/properties/repository/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data2.views !== undefined){if(!(validate37(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate37.errors : vErrors.concat(validate37.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data17 = data2.legend;if(data17 && typeof data17 == "object" && !Array.isArray(data17)){for(const key3 in data17){if(!((key3 === "mode") || (key3 === "entries"))){const err28 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data17.mode !== undefined){let data18 = data17.mode;if(!(((data18 === "auto") || (data18 === "all")) || (data18 === "hidden"))){const err29 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}if(data17.entries !== undefined){let data19 = data17.entries;if(data19 && typeof data19 == "object" && !Array.isArray(data19)){for(const key4 in data19){if(!(((((((key4 === "frontend") || (key4 === "backend")) || (key4 === "database")) || (key4 === "cloud")) || (key4 === "security")) || (key4 === "messagebus")) || (key4 === "external"))){const err30 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data19.frontend !== undefined){let data20 = data19.frontend;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(Object.keys(data20).length < 1){const err31 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}for(const key5 in data20){if(!((key5 === "label") || (key5 === "visible"))){const err32 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}if(data20.label !== undefined){let data21 = data20.label;if(typeof data21 === "string"){if(func3(data21) > 80){const err33 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(func3(data21) < 1){const err34 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data20.visible !== undefined){if(typeof data20.visible !== "boolean"){const err36 = {instancePath:instancePath+"/meta/legend/entries/frontend/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}}else {const err37 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data19.backend !== undefined){let data23 = data19.backend;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(Object.keys(data23).length < 1){const err38 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}for(const key6 in data23){if(!((key6 === "label") || (key6 === "visible"))){const err39 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}if(data23.label !== undefined){let data24 = data23.label;if(typeof data24 === "string"){if(func3(data24) > 80){const err40 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}if(func3(data24) < 1){const err41 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data23.visible !== undefined){if(typeof data23.visible !== "boolean"){const err43 = {instancePath:instancePath+"/meta/legend/entries/backend/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}}else {const err44 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data19.database !== undefined){let data26 = data19.database;if(data26 && typeof data26 == "object" && !Array.isArray(data26)){if(Object.keys(data26).length < 1){const err45 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}for(const key7 in data26){if(!((key7 === "label") || (key7 === "visible"))){const err46 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}if(data26.label !== undefined){let data27 = data26.label;if(typeof data27 === "string"){if(func3(data27) > 80){const err47 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}if(func3(data27) < 1){const err48 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data26.visible !== undefined){if(typeof data26.visible !== "boolean"){const err50 = {instancePath:instancePath+"/meta/legend/entries/database/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}else {const err51 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data19.cloud !== undefined){let data29 = data19.cloud;if(data29 && typeof data29 == "object" && !Array.isArray(data29)){if(Object.keys(data29).length < 1){const err52 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}for(const key8 in data29){if(!((key8 === "label") || (key8 === "visible"))){const err53 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data29.label !== undefined){let data30 = data29.label;if(typeof data30 === "string"){if(func3(data30) > 80){const err54 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}if(func3(data30) < 1){const err55 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data29.visible !== undefined){if(typeof data29.visible !== "boolean"){const err57 = {instancePath:instancePath+"/meta/legend/entries/cloud/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}}else {const err58 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data19.security !== undefined){let data32 = data19.security;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){if(Object.keys(data32).length < 1){const err59 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}for(const key9 in data32){if(!((key9 === "label") || (key9 === "visible"))){const err60 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}if(data32.label !== undefined){let data33 = data32.label;if(typeof data33 === "string"){if(func3(data33) > 80){const err61 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}if(func3(data33) < 1){const err62 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}else {const err63 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data32.visible !== undefined){if(typeof data32.visible !== "boolean"){const err64 = {instancePath:instancePath+"/meta/legend/entries/security/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}}else {const err65 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data19.messagebus !== undefined){let data35 = data19.messagebus;if(data35 && typeof data35 == "object" && !Array.isArray(data35)){if(Object.keys(data35).length < 1){const err66 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}for(const key10 in data35){if(!((key10 === "label") || (key10 === "visible"))){const err67 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}if(data35.label !== undefined){let data36 = data35.label;if(typeof data36 === "string"){if(func3(data36) > 80){const err68 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}if(func3(data36) < 1){const err69 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}else {const err70 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}if(data35.visible !== undefined){if(typeof data35.visible !== "boolean"){const err71 = {instancePath:instancePath+"/meta/legend/entries/messagebus/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}}else {const err72 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data19.external !== undefined){let data38 = data19.external;if(data38 && typeof data38 == "object" && !Array.isArray(data38)){if(Object.keys(data38).length < 1){const err73 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}for(const key11 in data38){if(!((key11 === "label") || (key11 === "visible"))){const err74 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data38.label !== undefined){let data39 = data38.label;if(typeof data39 === "string"){if(func3(data39) > 80){const err75 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}if(func3(data39) < 1){const err76 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}else {const err77 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}if(data38.visible !== undefined){if(typeof data38.visible !== "boolean"){const err78 = {instancePath:instancePath+"/meta/legend/entries/external/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}}else {const err79 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}}else {const err80 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}}else {const err81 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data2.viewBox !== undefined){let data41 = data2.viewBox;if(Array.isArray(data41)){if(data41.length > 2){const err82 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}if(data41.length < 2){const err83 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}const len0 = data41.length;if(len0 > 0){let data42 = data41[0];if((typeof data42 == "number") && (isFinite(data42))){if(data42 < 320 || isNaN(data42)){const err84 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 320},message:"must be >= 320"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}else {const err85 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}if(len0 > 1){let data43 = data41[1];if((typeof data43 == "number") && (isFinite(data43))){if(data43 < 240 || isNaN(data43)){const err86 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 240},message:"must be >= 240"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}else {const err87 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}const len1 = data41.length;if(!(len1 <= 2)){const err88 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}else {const err89 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}}else {const err90 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}if(data.layout !== undefined){let data44 = data.layout;if(data44 && typeof data44 == "object" && !Array.isArray(data44)){if(data44.mode === undefined){const err91 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/required",keyword:"required",params:{missingProperty: "mode"},message:"must have required property '"+"mode"+"'"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}for(const key12 in data44){if(!(((((((key12 === "mode") || (key12 === "origin")) || (key12 === "cols")) || (key12 === "gapX")) || (key12 === "gapY")) || (key12 === "cellW")) || (key12 === "cellH"))){const err92 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key12},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}if(data44.mode !== undefined){if(!(data44.mode === "grid")){const err93 = {instancePath:instancePath+"/layout/mode",schemaPath:"#/properties/layout/properties/mode/enum",keyword:"enum",params:{allowedValues: schema149.properties.layout.properties.mode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}if(data44.origin !== undefined){let data46 = data44.origin;if(Array.isArray(data46)){if(data46.length > 2){const err94 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}if(data46.length < 2){const err95 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}const len2 = data46.length;if(len2 > 0){let data47 = data46[0];if(!((typeof data47 == "number") && (isFinite(data47)))){const err96 = {instancePath:instancePath+"/layout/origin/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}}if(len2 > 1){let data48 = data46[1];if(!((typeof data48 == "number") && (isFinite(data48)))){const err97 = {instancePath:instancePath+"/layout/origin/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}const len3 = data46.length;if(!(len3 <= 2)){const err98 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}else {const err99 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data44.cols !== undefined){let data49 = data44.cols;if(!(((typeof data49 == "number") && (!(data49 % 1) && !isNaN(data49))) && (isFinite(data49)))){const err100 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}if((typeof data49 == "number") && (isFinite(data49))){if(data49 > 12 || isNaN(data49)){const err101 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/maximum",keyword:"maximum",params:{comparison: "<=", limit: 12},message:"must be <= 12"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}if(data49 < 1 || isNaN(data49)){const err102 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}}if(data44.gapX !== undefined){let data50 = data44.gapX;if((typeof data50 == "number") && (isFinite(data50))){if(data50 < 0 || isNaN(data50)){const err103 = {instancePath:instancePath+"/layout/gapX",schemaPath:"#/properties/layout/properties/gapX/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}else {const err104 = {instancePath:instancePath+"/layout/gapX",schemaPath:"#/properties/layout/properties/gapX/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}if(data44.gapY !== undefined){let data51 = data44.gapY;if((typeof data51 == "number") && (isFinite(data51))){if(data51 < 0 || isNaN(data51)){const err105 = {instancePath:instancePath+"/layout/gapY",schemaPath:"#/properties/layout/properties/gapY/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}else {const err106 = {instancePath:instancePath+"/layout/gapY",schemaPath:"#/properties/layout/properties/gapY/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}if(data44.cellW !== undefined){let data52 = data44.cellW;if((typeof data52 == "number") && (isFinite(data52))){if(data52 < 40 || isNaN(data52)){const err107 = {instancePath:instancePath+"/layout/cellW",schemaPath:"#/properties/layout/properties/cellW/minimum",keyword:"minimum",params:{comparison: ">=", limit: 40},message:"must be >= 40"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/layout/cellW",schemaPath:"#/properties/layout/properties/cellW/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}if(data44.cellH !== undefined){let data53 = data44.cellH;if((typeof data53 == "number") && (isFinite(data53))){if(data53 < 24 || isNaN(data53)){const err109 = {instancePath:instancePath+"/layout/cellH",schemaPath:"#/properties/layout/properties/cellH/minimum",keyword:"minimum",params:{comparison: ">=", limit: 24},message:"must be >= 24"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}else {const err110 = {instancePath:instancePath+"/layout/cellH",schemaPath:"#/properties/layout/properties/cellH/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}}else {const err111 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}if(data.components !== undefined){let data54 = data.components;if(Array.isArray(data54)){if(data54.length < 1){const err112 = {instancePath:instancePath+"/components",schemaPath:"#/properties/components/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}const len4 = data54.length;for(let i0=0; i0<len4; i0++){let data55 = data54[i0];if(data55 && typeof data55 == "object" && !Array.isArray(data55)){if(data55.id === undefined){const err113 = {instancePath:instancePath+"/components/" + i0,schemaPath:"#/properties/components/items/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}if(data55.type === undefined){const err114 = {instancePath:instancePath+"/components/" + i0,schemaPath:"#/properties/components/items/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}if(data55.label === undefined){const err115 = {instancePath:instancePath+"/components/" + i0,schemaPath:"#/properties/components/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}for(const key13 in data55){if(!(func1.call(schema149.properties.components.items.properties, key13))){const err116 = {instancePath:instancePath+"/components/" + i0,schemaPath:"#/properties/components/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key13},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}if(data55.id !== undefined){let data56 = data55.id;if(typeof data56 === "string"){if(!pattern4.test(data56)){const err117 = {instancePath:instancePath+"/components/" + i0+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}else {const err118 = {instancePath:instancePath+"/components/" + i0+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}if(data55.type !== undefined){let data57 = data55.type;if(!(((((((data57 === "frontend") || (data57 === "backend")) || (data57 === "database")) || (data57 === "cloud")) || (data57 === "security")) || (data57 === "messagebus")) || (data57 === "external"))){const err119 = {instancePath:instancePath+"/components/" + i0+"/type",schemaPath:"common.schema.json#/$defs/componentType/enum",keyword:"enum",params:{allowedValues: schema57.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}if(data55.label !== undefined){let data58 = data55.label;if(typeof data58 === "string"){if(func3(data58) < 1){const err120 = {instancePath:instancePath+"/components/" + i0+"/label",schemaPath:"#/properties/components/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}else {const err121 = {instancePath:instancePath+"/components/" + i0+"/label",schemaPath:"#/properties/components/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}if(data55.sublabel !== undefined){if(typeof data55.sublabel !== "string"){const err122 = {instancePath:instancePath+"/components/" + i0+"/sublabel",schemaPath:"#/properties/components/items/properties/sublabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}if(data55.tag !== undefined){if(typeof data55.tag !== "string"){const err123 = {instancePath:instancePath+"/components/" + i0+"/tag",schemaPath:"#/properties/components/items/properties/tag/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}if(data55.brand !== undefined){let data61 = data55.brand;const _errs141 = errors;let valid34 = false;let passing0 = null;const _errs142 = errors;const _errs144 = errors;let valid35 = false;const _errs145 = errors;if(typeof data61 === "string"){if(func3(data61) > 80){const err124 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}if(!pattern17.test(data61)){const err125 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}var _valid1 = _errs145 === errors;valid35 = valid35 || _valid1;const _errs146 = errors;if(typeof data61 === "string"){if(!pattern18.test(data61)){const err126 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}var _valid1 = _errs146 === errors;valid35 = valid35 || _valid1;if(!valid35){const err127 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}else {errors = _errs144;if(vErrors !== null){if(_errs144){vErrors.length = _errs144;}else {vErrors = null;}}}if(typeof data61 === "string"){if(func3(data61) > 2048){const err128 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}if(func3(data61) < 1){const err129 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}}else {const err130 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}var _valid0 = _errs142 === errors;if(_valid0){valid34 = true;passing0 = 0;}const _errs147 = errors;if(data61 && typeof data61 == "object" && !Array.isArray(data61)){if(data61.url === undefined){const err131 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}if(data61.sha256 === undefined){const err132 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}for(const key14 in data61){if(!((key14 === "url") || (key14 === "sha256"))){const err133 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key14},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}if(data61.url !== undefined){let data62 = data61.url;if(typeof data62 === "string"){if(func3(data62) > 2048){const err134 = {instancePath:instancePath+"/components/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}if(func3(data62) < 8){const err135 = {instancePath:instancePath+"/components/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}if(!pattern18.test(data62)){const err136 = {instancePath:instancePath+"/components/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}else {const err137 = {instancePath:instancePath+"/components/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}if(data61.sha256 !== undefined){let data63 = data61.sha256;if(typeof data63 === "string"){if(!pattern20.test(data63)){const err138 = {instancePath:instancePath+"/components/" + i0+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}else {const err139 = {instancePath:instancePath+"/components/" + i0+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}}else {const err140 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}var _valid0 = _errs147 === errors;if(_valid0 && valid34){valid34 = false;passing0 = [passing0, 1];}else {if(_valid0){valid34 = true;passing0 = 1;}}if(!valid34){const err141 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}else {errors = _errs141;if(vErrors !== null){if(_errs141){vErrors.length = _errs141;}else {vErrors = null;}}}}if(data55.sources !== undefined){let data64 = data55.sources;if(Array.isArray(data64)){if(data64.length > 3){const err142 = {instancePath:instancePath+"/components/" + i0+"/sources",schemaPath:"#/properties/components/items/properties/sources/maxItems",keyword:"maxItems",params:{limit: 3},message:"must NOT have more than 3 items"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}if(data64.length < 1){const err143 = {instancePath:instancePath+"/components/" + i0+"/sources",schemaPath:"#/properties/components/items/properties/sources/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}const len5 = data64.length;for(let i1=0; i1<len5; i1++){let data65 = data64[i1];if(data65 && typeof data65 == "object" && !Array.isArray(data65)){if(data65.path === undefined){const err144 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1,schemaPath:"#/properties/components/items/properties/sources/items/required",keyword:"required",params:{missingProperty: "path"},message:"must have required property '"+"path"+"'"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}for(const key15 in data65){if(!((((key15 === "path") || (key15 === "line")) || (key15 === "end_line")) || (key15 === "label"))){const err145 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1,schemaPath:"#/properties/components/items/properties/sources/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key15},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}if(data65.path !== undefined){let data66 = data65.path;if(typeof data66 === "string"){if(func3(data66) > 240){const err146 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/path",schemaPath:"#/properties/components/items/properties/sources/items/properties/path/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}if(func3(data66) < 1){const err147 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/path",schemaPath:"#/properties/components/items/properties/sources/items/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}}else {const err148 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/path",schemaPath:"#/properties/components/items/properties/sources/items/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}}if(data65.line !== undefined){let data67 = data65.line;if(!(((typeof data67 == "number") && (!(data67 % 1) && !isNaN(data67))) && (isFinite(data67)))){const err149 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/line",schemaPath:"#/properties/components/items/properties/sources/items/properties/line/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}if((typeof data67 == "number") && (isFinite(data67))){if(data67 < 1 || isNaN(data67)){const err150 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/line",schemaPath:"#/properties/components/items/properties/sources/items/properties/line/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}}}if(data65.end_line !== undefined){let data68 = data65.end_line;if(!(((typeof data68 == "number") && (!(data68 % 1) && !isNaN(data68))) && (isFinite(data68)))){const err151 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/end_line",schemaPath:"#/properties/components/items/properties/sources/items/properties/end_line/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}if((typeof data68 == "number") && (isFinite(data68))){if(data68 < 1 || isNaN(data68)){const err152 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/end_line",schemaPath:"#/properties/components/items/properties/sources/items/properties/end_line/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err152];}else {vErrors.push(err152);}errors++;}}}if(data65.label !== undefined){let data69 = data65.label;if(typeof data69 === "string"){if(func3(data69) > 48){const err153 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/label",schemaPath:"#/properties/components/items/properties/sources/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err153];}else {vErrors.push(err153);}errors++;}if(func3(data69) < 1){const err154 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/label",schemaPath:"#/properties/components/items/properties/sources/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err154];}else {vErrors.push(err154);}errors++;}}else {const err155 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/label",schemaPath:"#/properties/components/items/properties/sources/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err155];}else {vErrors.push(err155);}errors++;}}}else {const err156 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1,schemaPath:"#/properties/components/items/properties/sources/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err156];}else {vErrors.push(err156);}errors++;}}}else {const err157 = {instancePath:instancePath+"/components/" + i0+"/sources",schemaPath:"#/properties/components/items/properties/sources/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err157];}else {vErrors.push(err157);}errors++;}}if(data55.row !== undefined){let data70 = data55.row;if(!(((typeof data70 == "number") && (!(data70 % 1) && !isNaN(data70))) && (isFinite(data70)))){const err158 = {instancePath:instancePath+"/components/" + i0+"/row",schemaPath:"#/properties/components/items/properties/row/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err158];}else {vErrors.push(err158);}errors++;}if((typeof data70 == "number") && (isFinite(data70))){if(data70 < 0 || isNaN(data70)){const err159 = {instancePath:instancePath+"/components/" + i0+"/row",schemaPath:"#/properties/components/items/properties/row/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err159];}else {vErrors.push(err159);}errors++;}}}if(data55.col !== undefined){let data71 = data55.col;if(!(((typeof data71 == "number") && (!(data71 % 1) && !isNaN(data71))) && (isFinite(data71)))){const err160 = {instancePath:instancePath+"/components/" + i0+"/col",schemaPath:"#/properties/components/items/properties/col/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err160];}else {vErrors.push(err160);}errors++;}if((typeof data71 == "number") && (isFinite(data71))){if(data71 < 0 || isNaN(data71)){const err161 = {instancePath:instancePath+"/components/" + i0+"/col",schemaPath:"#/properties/components/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err161];}else {vErrors.push(err161);}errors++;}}}if(data55.pos !== undefined){let data72 = data55.pos;if(Array.isArray(data72)){if(data72.length > 2){const err162 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}if(data72.length < 2){const err163 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}const len6 = data72.length;if(len6 > 0){let data73 = data72[0];if(!((typeof data73 == "number") && (isFinite(data73)))){const err164 = {instancePath:instancePath+"/components/" + i0+"/pos/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}}if(len6 > 1){let data74 = data72[1];if(!((typeof data74 == "number") && (isFinite(data74)))){const err165 = {instancePath:instancePath+"/components/" + i0+"/pos/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}}const len7 = data72.length;if(!(len7 <= 2)){const err166 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}}else {const err167 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}}if(data55.size !== undefined){let data75 = data55.size;if(Array.isArray(data75)){if(data75.length > 2){const err168 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}if(data75.length < 2){const err169 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}const len8 = data75.length;if(len8 > 0){let data76 = data75[0];if((typeof data76 == "number") && (isFinite(data76))){if(data76 <= 0 || isNaN(data76)){const err170 = {instancePath:instancePath+"/components/" + i0+"/size/0",schemaPath:"#/properties/components/items/properties/size/prefixItems/0/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}}else {const err171 = {instancePath:instancePath+"/components/" + i0+"/size/0",schemaPath:"#/properties/components/items/properties/size/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err171];}else {vErrors.push(err171);}errors++;}}if(len8 > 1){let data77 = data75[1];if((typeof data77 == "number") && (isFinite(data77))){if(data77 <= 0 || isNaN(data77)){const err172 = {instancePath:instancePath+"/components/" + i0+"/size/1",schemaPath:"#/properties/components/items/properties/size/prefixItems/1/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err172];}else {vErrors.push(err172);}errors++;}}else {const err173 = {instancePath:instancePath+"/components/" + i0+"/size/1",schemaPath:"#/properties/components/items/properties/size/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err173];}else {vErrors.push(err173);}errors++;}}const len9 = data75.length;if(!(len9 <= 2)){const err174 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err174];}else {vErrors.push(err174);}errors++;}}else {const err175 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err175];}else {vErrors.push(err175);}errors++;}}}else {const err176 = {instancePath:instancePath+"/components/" + i0,schemaPath:"#/properties/components/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err176];}else {vErrors.push(err176);}errors++;}}}else {const err177 = {instancePath:instancePath+"/components",schemaPath:"#/properties/components/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err177];}else {vErrors.push(err177);}errors++;}}if(data.boundaries !== undefined){let data78 = data.boundaries;if(Array.isArray(data78)){const len10 = data78.length;for(let i2=0; i2<len10; i2++){let data79 = data78[i2];if(data79 && typeof data79 == "object" && !Array.isArray(data79)){if(data79.kind === undefined){const err178 = {instancePath:instancePath+"/boundaries/" + i2,schemaPath:"#/properties/boundaries/items/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"};if(vErrors === null){vErrors = [err178];}else {vErrors.push(err178);}errors++;}if(data79.label === undefined){const err179 = {instancePath:instancePath+"/boundaries/" + i2,schemaPath:"#/properties/boundaries/items/required",keyword:"required",params:{missingProperty: "label"},message:"must have required property '"+"label"+"'"};if(vErrors === null){vErrors = [err179];}else {vErrors.push(err179);}errors++;}if(data79.wraps === undefined){const err180 = {instancePath:instancePath+"/boundaries/" + i2,schemaPath:"#/properties/boundaries/items/required",keyword:"required",params:{missingProperty: "wraps"},message:"must have required property '"+"wraps"+"'"};if(vErrors === null){vErrors = [err180];}else {vErrors.push(err180);}errors++;}for(const key16 in data79){if(!((((key16 === "kind") || (key16 === "label")) || (key16 === "wraps")) || (key16 === "pad"))){const err181 = {instancePath:instancePath+"/boundaries/" + i2,schemaPath:"#/properties/boundaries/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key16},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err181];}else {vErrors.push(err181);}errors++;}}if(data79.kind !== undefined){let data80 = data79.kind;if(!((data80 === "region") || (data80 === "security-group"))){const err182 = {instancePath:instancePath+"/boundaries/" + i2+"/kind",schemaPath:"#/properties/boundaries/items/properties/kind/enum",keyword:"enum",params:{allowedValues: schema149.properties.boundaries.items.properties.kind.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}}if(data79.label !== undefined){let data81 = data79.label;if(typeof data81 === "string"){if(func3(data81) < 1){const err183 = {instancePath:instancePath+"/boundaries/" + i2+"/label",schemaPath:"#/properties/boundaries/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}}else {const err184 = {instancePath:instancePath+"/boundaries/" + i2+"/label",schemaPath:"#/properties/boundaries/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}}if(data79.wraps !== undefined){let data82 = data79.wraps;if(Array.isArray(data82)){if(data82.length < 1){const err185 = {instancePath:instancePath+"/boundaries/" + i2+"/wraps",schemaPath:"#/properties/boundaries/items/properties/wraps/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err185];}else {vErrors.push(err185);}errors++;}const len11 = data82.length;for(let i3=0; i3<len11; i3++){let data83 = data82[i3];if(typeof data83 === "string"){if(!pattern4.test(data83)){const err186 = {instancePath:instancePath+"/boundaries/" + i2+"/wraps/" + i3,schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err186];}else {vErrors.push(err186);}errors++;}}else {const err187 = {instancePath:instancePath+"/boundaries/" + i2+"/wraps/" + i3,schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}}}else {const err188 = {instancePath:instancePath+"/boundaries/" + i2+"/wraps",schemaPath:"#/properties/boundaries/items/properties/wraps/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}}if(data79.pad !== undefined){let data84 = data79.pad;if((typeof data84 == "number") && (isFinite(data84))){if(data84 < 0 || isNaN(data84)){const err189 = {instancePath:instancePath+"/boundaries/" + i2+"/pad",schemaPath:"#/properties/boundaries/items/properties/pad/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}else {const err190 = {instancePath:instancePath+"/boundaries/" + i2+"/pad",schemaPath:"#/properties/boundaries/items/properties/pad/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}}else {const err191 = {instancePath:instancePath+"/boundaries/" + i2,schemaPath:"#/properties/boundaries/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err191];}else {vErrors.push(err191);}errors++;}}}else {const err192 = {instancePath:instancePath+"/boundaries",schemaPath:"#/properties/boundaries/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err192];}else {vErrors.push(err192);}errors++;}}if(data.connections !== undefined){let data85 = data.connections;if(Array.isArray(data85)){const len12 = data85.length;for(let i4=0; i4<len12; i4++){let data86 = data85[i4];if(data86 && typeof data86 == "object" && !Array.isArray(data86)){if(data86.from === undefined){const err193 = {instancePath:instancePath+"/connections/" + i4,schemaPath:"#/properties/connections/items/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err193];}else {vErrors.push(err193);}errors++;}if(data86.to === undefined){const err194 = {instancePath:instancePath+"/connections/" + i4,schemaPath:"#/properties/connections/items/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err194];}else {vErrors.push(err194);}errors++;}for(const key17 in data86){if(!(func1.call(schema149.properties.connections.items.properties, key17))){const err195 = {instancePath:instancePath+"/connections/" + i4,schemaPath:"#/properties/connections/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key17},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err195];}else {vErrors.push(err195);}errors++;}}if(data86.id !== undefined){let data87 = data86.id;if(typeof data87 === "string"){if(!pattern4.test(data87)){const err196 = {instancePath:instancePath+"/connections/" + i4+"/id",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err196];}else {vErrors.push(err196);}errors++;}}else {const err197 = {instancePath:instancePath+"/connections/" + i4+"/id",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err197];}else {vErrors.push(err197);}errors++;}}if(data86.from !== undefined){let data88 = data86.from;if(typeof data88 === "string"){if(!pattern4.test(data88)){const err198 = {instancePath:instancePath+"/connections/" + i4+"/from",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err198];}else {vErrors.push(err198);}errors++;}}else {const err199 = {instancePath:instancePath+"/connections/" + i4+"/from",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err199];}else {vErrors.push(err199);}errors++;}}if(data86.to !== undefined){let data89 = data86.to;if(typeof data89 === "string"){if(!pattern4.test(data89)){const err200 = {instancePath:instancePath+"/connections/" + i4+"/to",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err200];}else {vErrors.push(err200);}errors++;}}else {const err201 = {instancePath:instancePath+"/connections/" + i4+"/to",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err201];}else {vErrors.push(err201);}errors++;}}if(data86.label !== undefined){if(typeof data86.label !== "string"){const err202 = {instancePath:instancePath+"/connections/" + i4+"/label",schemaPath:"#/properties/connections/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err202];}else {vErrors.push(err202);}errors++;}}if(data86.variant !== undefined){let data91 = data86.variant;if(!((((data91 === "default") || (data91 === "emphasis")) || (data91 === "security")) || (data91 === "dashed"))){const err203 = {instancePath:instancePath+"/connections/" + i4+"/variant",schemaPath:"common.schema.json#/$defs/variant/enum",keyword:"enum",params:{allowedValues: schema62.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err203];}else {vErrors.push(err203);}errors++;}}if(data86.fromSide !== undefined){let data92 = data86.fromSide;if(!((((data92 === "left") || (data92 === "right")) || (data92 === "top")) || (data92 === "bottom"))){const err204 = {instancePath:instancePath+"/connections/" + i4+"/fromSide",schemaPath:"common.schema.json#/$defs/side/enum",keyword:"enum",params:{allowedValues: schema112.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err204];}else {vErrors.push(err204);}errors++;}}if(data86.toSide !== undefined){let data93 = data86.toSide;if(!((((data93 === "left") || (data93 === "right")) || (data93 === "top")) || (data93 === "bottom"))){const err205 = {instancePath:instancePath+"/connections/" + i4+"/toSide",schemaPath:"common.schema.json#/$defs/side/enum",keyword:"enum",params:{allowedValues: schema112.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err205];}else {vErrors.push(err205);}errors++;}}if(data86.route !== undefined){let data94 = data86.route;if(!((((data94 === "auto") || (data94 === "straight")) || (data94 === "orthogonal-h")) || (data94 === "orthogonal-v"))){const err206 = {instancePath:instancePath+"/connections/" + i4+"/route",schemaPath:"#/properties/connections/items/properties/route/enum",keyword:"enum",params:{allowedValues: schema149.properties.connections.items.properties.route.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err206];}else {vErrors.push(err206);}errors++;}}if(data86.via !== undefined){let data95 = data86.via;if(Array.isArray(data95)){const len13 = data95.length;for(let i5=0; i5<len13; i5++){let data96 = data95[i5];if(Array.isArray(data96)){if(data96.length > 2){const err207 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err207];}else {vErrors.push(err207);}errors++;}if(data96.length < 2){const err208 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err208];}else {vErrors.push(err208);}errors++;}const len14 = data96.length;if(len14 > 0){let data97 = data96[0];if(!((typeof data97 == "number") && (isFinite(data97)))){const err209 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err209];}else {vErrors.push(err209);}errors++;}}if(len14 > 1){let data98 = data96[1];if(!((typeof data98 == "number") && (isFinite(data98)))){const err210 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err210];}else {vErrors.push(err210);}errors++;}}const len15 = data96.length;if(!(len15 <= 2)){const err211 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err211];}else {vErrors.push(err211);}errors++;}}else {const err212 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err212];}else {vErrors.push(err212);}errors++;}}}else {const err213 = {instancePath:instancePath+"/connections/" + i4+"/via",schemaPath:"#/properties/connections/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err213];}else {vErrors.push(err213);}errors++;}}if(data86.labelAt !== undefined){let data99 = data86.labelAt;if(Array.isArray(data99)){if(data99.length > 2){const err214 = {instancePath:instancePath+"/connections/" + i4+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err214];}else {vErrors.push(err214);}errors++;}if(data99.length < 2){const err215 = {instancePath:instancePath+"/connections/" + i4+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err215];}else {vErrors.push(err215);}errors++;}const len16 = data99.length;if(len16 > 0){let data100 = data99[0];if(!((typeof data100 == "number") && (isFinite(data100)))){const err216 = {instancePath:instancePath+"/connections/" + i4+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err216];}else {vErrors.push(err216);}errors++;}}if(len16 > 1){let data101 = data99[1];if(!((typeof data101 == "number") && (isFinite(data101)))){const err217 = {instancePath:instancePath+"/connections/" + i4+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err217];}else {vErrors.push(err217);}errors++;}}const len17 = data99.length;if(!(len17 <= 2)){const err218 = {instancePath:instancePath+"/connections/" + i4+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err218];}else {vErrors.push(err218);}errors++;}}else {const err219 = {instancePath:instancePath+"/connections/" + i4+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err219];}else {vErrors.push(err219);}errors++;}}if(data86.labelDx !== undefined){let data102 = data86.labelDx;if(!((typeof data102 == "number") && (isFinite(data102)))){const err220 = {instancePath:instancePath+"/connections/" + i4+"/labelDx",schemaPath:"#/properties/connections/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err220];}else {vErrors.push(err220);}errors++;}}if(data86.labelDy !== undefined){let data103 = data86.labelDy;if(!((typeof data103 == "number") && (isFinite(data103)))){const err221 = {instancePath:instancePath+"/connections/" + i4+"/labelDy",schemaPath:"#/properties/connections/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err221];}else {vErrors.push(err221);}errors++;}}if(data86.labelSegment !== undefined){let data104 = data86.labelSegment;if(!(((typeof data104 == "number") && (!(data104 % 1) && !isNaN(data104))) && (isFinite(data104)))){const err222 = {instancePath:instancePath+"/connections/" + i4+"/labelSegment",schemaPath:"#/properties/connections/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err222];}else {vErrors.push(err222);}errors++;}if((typeof data104 == "number") && (isFinite(data104))){if(data104 < 0 || isNaN(data104)){const err223 = {instancePath:instancePath+"/connections/" + i4+"/labelSegment",schemaPath:"#/properties/connections/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err223];}else {vErrors.push(err223);}errors++;}}}if(data86.width !== undefined){let data105 = data86.width;if((typeof data105 == "number") && (isFinite(data105))){if(data105 < 0.5 || isNaN(data105)){const err224 = {instancePath:instancePath+"/connections/" + i4+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err224];}else {vErrors.push(err224);}errors++;}}else {const err225 = {instancePath:instancePath+"/connections/" + i4+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err225];}else {vErrors.push(err225);}errors++;}}}else {const err226 = {instancePath:instancePath+"/connections/" + i4,schemaPath:"#/properties/connections/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err226];}else {vErrors.push(err226);}errors++;}}}else {const err227 = {instancePath:instancePath+"/connections",schemaPath:"#/properties/connections/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err227];}else {vErrors.push(err227);}errors++;}}if(data.cards !== undefined){let data106 = data.cards;if(Array.isArray(data106)){const len18 = data106.length;for(let i6=0; i6<len18; i6++){let data107 = data106[i6];if(data107 && typeof data107 == "object" && !Array.isArray(data107)){if(data107.dot === undefined){const err228 = {instancePath:instancePath+"/cards/" + i6,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "dot"},message:"must have required property '"+"dot"+"'"};if(vErrors === null){vErrors = [err228];}else {vErrors.push(err228);}errors++;}if(data107.title === undefined){const err229 = {instancePath:instancePath+"/cards/" + i6,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err229];}else {vErrors.push(err229);}errors++;}if(data107.items === undefined){const err230 = {instancePath:instancePath+"/cards/" + i6,schemaPath:"common.schema.json#/$defs/cards/items/required",keyword:"required",params:{missingProperty: "items"},message:"must have required property '"+"items"+"'"};if(vErrors === null){vErrors = [err230];}else {vErrors.push(err230);}errors++;}for(const key18 in data107){if(!(((key18 === "dot") || (key18 === "title")) || (key18 === "items"))){const err231 = {instancePath:instancePath+"/cards/" + i6,schemaPath:"common.schema.json#/$defs/cards/items/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key18},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err231];}else {vErrors.push(err231);}errors++;}}if(data107.dot !== undefined){let data108 = data107.dot;if(!(((((((data108 === "cyan") || (data108 === "emerald")) || (data108 === "violet")) || (data108 === "amber")) || (data108 === "rose")) || (data108 === "orange")) || (data108 === "slate"))){const err232 = {instancePath:instancePath+"/cards/" + i6+"/dot",schemaPath:"common.schema.json#/$defs/cards/items/properties/dot/enum",keyword:"enum",params:{allowedValues: schema67.items.properties.dot.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err232];}else {vErrors.push(err232);}errors++;}}if(data107.title !== undefined){let data109 = data107.title;if(typeof data109 === "string"){if(func3(data109) < 1){const err233 = {instancePath:instancePath+"/cards/" + i6+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err233];}else {vErrors.push(err233);}errors++;}}else {const err234 = {instancePath:instancePath+"/cards/" + i6+"/title",schemaPath:"common.schema.json#/$defs/cards/items/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err234];}else {vErrors.push(err234);}errors++;}}if(data107.items !== undefined){let data110 = data107.items;if(Array.isArray(data110)){const len19 = data110.length;for(let i7=0; i7<len19; i7++){if(typeof data110[i7] !== "string"){const err235 = {instancePath:instancePath+"/cards/" + i6+"/items/" + i7,schemaPath:"common.schema.json#/$defs/cards/items/properties/items/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err235];}else {vErrors.push(err235);}errors++;}}}else {const err236 = {instancePath:instancePath+"/cards/" + i6+"/items",schemaPath:"common.schema.json#/$defs/cards/items/properties/items/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err236];}else {vErrors.push(err236);}errors++;}}}else {const err237 = {instancePath:instancePath+"/cards/" + i6,schemaPath:"common.schema.json#/$defs/cards/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err237];}else {vErrors.push(err237);}errors++;}}}else {const err238 = {instancePath:instancePath+"/cards",schemaPath:"common.schema.json#/$defs/cards/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err238];}else {vErrors.push(err238);}errors++;}}}else {const err239 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err239];}else {vErrors.push(err239);}errors++;}validate36.errors = vErrors;return errors === 0;}validate36.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};
```

## renderers/shared/geometry.mjs

```js
// Geometry helpers shared by all typed renderers. Every function here is
// pure; renderers own their layout tables and pass measured rects
// ({x, y, width, height, cx, cy}) in.

import { recordDiagnostic } from './diagnostics.mjs';

// In degraded mode (no ajv) a type-wrong top-level field reaches the renderer.
// Coerce non-arrays to [] so the module-level Maps build without throwing and
// the friendly validator checks (which run later) report the real problem.
export function asArray(value) {
  return Array.isArray(value) ? value : [];
}

// A computed coordinate must be a finite number; NaN/undefined would silently
// write `<rect x="NaN">` into the output. Used by the validators as a backstop.
export function isFinitePoint(...coords) {
  return coords.every((c) => Number.isFinite(c));
}

export function rectsOverlap(a, b, gap = 0) {
  // Non-finite geometry means "unknown", not "overlapping". Every comparison
  // below is false for NaN, so without this guard the negation reports a
  // collision for every pair. Callers surface non-finite pos/size through their
  // own diagnostic; reporting it again as an overlap buries that message under
  // one bogus separation hint per pair.
  if (!isFinitePoint(a.x, a.y, a.width, a.height, b.x, b.y, b.width, b.height)) {
    return false;
  }
  return !(
    a.x + a.width + gap <= b.x ||
    b.x + b.width + gap <= a.x ||
    a.y + a.height + gap <= b.y ||
    b.y + b.height + gap <= a.y
  );
}

export function segmentIntersectsRect(segment, rect, gap = 0) {
  const box = {
    x1: rect.x - gap,
    y1: rect.y - gap,
    x2: rect.x + rect.width + gap,
    y2: rect.y + rect.height + gap
  };
  const [a, b] = [segment.start, segment.end];
  if (pointInBox(a, box) || pointInBox(b, box)) return true;
  return (
    segmentsIntersect(a, b, [box.x1, box.y1], [box.x2, box.y1]) ||
    segmentsIntersect(a, b, [box.x2, box.y1], [box.x2, box.y2]) ||
    segmentsIntersect(a, b, [box.x2, box.y2], [box.x1, box.y2]) ||
    segmentsIntersect(a, b, [box.x1, box.y2], [box.x1, box.y1])
  );
}

export function segmentRectClearance(segment, rect) {
  if (!segment || !rect) return null;
  const { start, end } = segment;
  if (!Array.isArray(start) || !Array.isArray(end) || start.length !== 2 || end.length !== 2) return null;
  if (!isFinitePoint(...start, ...end, rect.x, rect.y, rect.width, rect.height)) return null;
  if (rect.width < 0 || rect.height < 0) return null;
  if (segmentIntersectsRect(segment, rect)) return 0;

  const corners = [
    [rect.x, rect.y],
    [rect.x + rect.width, rect.y],
    [rect.x + rect.width, rect.y + rect.height],
    [rect.x, rect.y + rect.height],
  ];
  return Math.min(
    pointRectDistance(start, rect),
    pointRectDistance(end, rect),
    ...corners.map((corner) => pointSegmentDistance(corner, start, end)),
  );
}

export function segmentRectIntersectionLength(segment, rect) {
  if (!segment || !rect) return null;
  const { start, end } = segment;
  if (!Array.isArray(start) || !Array.isArray(end) || start.length !== 2 || end.length !== 2) return null;
  if (!isFinitePoint(...start, ...end, rect.x, rect.y, rect.width, rect.height)) return null;
  if (rect.width < 0 || rect.height < 0) return null;

  const dx = end[0] - start[0];
  const dy = end[1] - start[1];
  const length = Math.hypot(dx, dy);
  if (length <= 0.0000001) return 0;
  const bounds = [
    [-dx, start[0] - rect.x],
    [dx, rect.x + rect.width - start[0]],
    [-dy, start[1] - rect.y],
    [dy, rect.y + rect.height - start[1]],
  ];
  let enter = 0;
  let leave = 1;
  for (const [direction, distance] of bounds) {
    if (Math.abs(direction) <= 0.0000001) {
      if (distance < -0.0000001) return 0;
      continue;
    }
    const ratio = distance / direction;
    if (direction < 0) enter = Math.max(enter, ratio);
    else leave = Math.min(leave, ratio);
    if (enter > leave + 0.0000001) return 0;
  }
  return length * Math.max(0, leave - enter);
}

export function collectLabelRouteClearance({ labels, routedRelations, threshold }) {
  if (!Number.isFinite(threshold) || threshold < 0) return [];
  const routeCandidates = asArray(routedRelations).map((entry, fallbackIndex) => {
    const relation = entry?.relation || entry;
    const points = normalizeRoutePoints(entry?.points || relation?.routePoints);
    if (!relation || points.length < 2) return null;
    return {
      relation,
      relationIndex: Number.isInteger(entry?.relationIndex) ? entry.relationIndex : fallbackIndex,
      points,
    };
  }).filter(Boolean);
  const seenRoutes = new Set();
  const routes = routeCandidates.filter((route) => {
    const identity = relationshipIdentity(route.relation, route.relationIndex);
    if (seenRoutes.has(identity)) return false;
    seenRoutes.add(identity);
    return true;
  });
  const hits = [];
  const seenLabels = new Set();

  for (const [fallbackIndex, label] of asArray(labels).entries()) {
    const rect = label?.rect || label;
    if (!rect || !isFinitePoint(rect.x, rect.y, rect.width, rect.height) || rect.width < 0 || rect.height < 0) continue;
    const relationIndex = Number.isInteger(label?.relationIndex) ? label.relationIndex : fallbackIndex;
    const labelIdentity = relationshipIdentity(label?.relation, relationIndex);
    if (seenLabels.has(labelIdentity)) continue;
    seenLabels.add(labelIdentity);
    for (const route of routes) {
      if (relationIndex === route.relationIndex || sameRelationship(label?.relation, route.relation)) continue;
      let nearest = null;
      for (let segmentIndex = 0; segmentIndex < route.points.length - 1; segmentIndex += 1) {
        const start = route.points[segmentIndex];
        const end = route.points[segmentIndex + 1];
        const clearance = segmentRectClearance({ start, end }, rect);
        if (clearance == null) continue;
        if (!nearest || clearance < nearest.clearance) {
          nearest = {
            clearance,
            intersectionLength: segmentRectIntersectionLength({ start, end }, rect),
            segmentIndex,
            start,
            end,
          };
        }
      }
      if (!nearest || nearest.clearance + 0.0001 >= threshold) continue;
      hits.push({
        label,
        labelRelation: label?.relation,
        labelRelationIndex: relationIndex,
        otherRelation: route.relation,
        otherRelationIndex: route.relationIndex,
        rect,
        ...nearest,
        threshold,
      });
    }
  }
  return hits;
}

function relationshipIdentity(relation, relationIndex) {
  if (relation?.key !== undefined) return `key:${relation.key}`;
  if (relation?.id) return `id:${relation.from || ''}\u0000${relation.to || ''}\u0000${relation.id}`;
  return `index:${relationIndex}`;
}

function sameRelationship(left, right) {
  if (!left || !right) return false;
  if (left === right) return true;
  if (left.key !== undefined && right.key !== undefined) return left.key === right.key;
  return Boolean(left.id && right.id && left.id === right.id && left.from === right.from && left.to === right.to);
}

function relationshipSubject(diagramType, relationCollection, relationIndex, relation) {
  return {
    diagramType,
    collection: relationCollection,
    index: relationIndex,
    ...(relation?.id ? { id: relation.id } : {}),
    ...(relation?.from ? { from: relation.from } : {}),
    ...(relation?.to ? { to: relation.to } : {}),
  };
}

const ENDPOINT_SIDE_RULES = {
  left: {
    axis: 'horizontal',
    sourceSign: -1,
    targetSign: 1,
    sourceDirection: 'leftward',
    targetDirection: 'rightward from the left',
  },
  right: {
    axis: 'horizontal',
    sourceSign: 1,
    targetSign: -1,
    sourceDirection: 'rightward',
    targetDirection: 'leftward from the right',
  },
  top: {
    axis: 'vertical',
    sourceSign: -1,
    targetSign: 1,
    sourceDirection: 'upward',
    targetDirection: 'downward from above',
  },
  bottom: {
    axis: 'vertical',
    sourceSign: 1,
    targetSign: -1,
    sourceDirection: 'downward',
    targetDirection: 'upward from below',
  },
};

function endpointSideIssue(points, endpoint, side) {
  const rule = ENDPOINT_SIDE_RULES[side];
  if (!rule) return null;
  const normalized = normalizeRoutePoints(points);
  if (normalized.length < 2) return null;
  const segmentIndex = endpoint === 'source' ? 0 : normalized.length - 2;
  const start = normalized[segmentIndex];
  const end = normalized[segmentIndex + 1];
  const dx = end[0] - start[0];
  const dy = end[1] - start[1];
  const along = rule.axis === 'horizontal' ? dx : dy;
  const across = rule.axis === 'horizontal' ? dy : dx;
  const expectedSign = endpoint === 'source' ? rule.sourceSign : rule.targetSign;
  if (Math.abs(across) <= 0.0001 && along * expectedSign > 0.0001) return null;
  return {
    endpoint,
    side,
    segmentIndex,
    start,
    end,
    expectedAxis: rule.axis,
    expectedDirection: endpoint === 'source' ? rule.sourceDirection : rule.targetDirection,
  };
}

// A side is a direction contract, not just a point on a box border. This pure
// predicate lets automatic routers prefer a dogleg whose first and final
// segments leave/enter the chosen sides perpendicularly.
export function routeHonorsEndpointSides(points, fromSide, toSide) {
  return !endpointSideIssue(points, 'source', fromSide)
    && !endpointSideIssue(points, 'target', toSide);
}

// Explicit fromSide/toSide are authored geometry, so a tangent or backwards
// endpoint segment changes their meaning. Fail this universally instead of
// leaving a malformed arrow for visual review to discover. Named routes and
// authored via points already carry their own geometry semantics: when they
// omit endpoint sides, do not invent a relative-position side and then reject
// the route for disagreeing with that invention. Pure automatic routes may
// still be checked against renderer-inferred sides.
export function cleanEndpointSideProblems({
  relations,
  endpointIds,
  pathFor,
  diagramType,
  relationCollection,
  fromSideFor,
  toSideFor,
  shouldCheckRelation = () => true,
  routeHint = 'align the first/final via segment with fromSide/toSide, change the side, or remove explicit routing so auto can choose a perpendicular approach',
}) {
  const problems = [];
  for (const [relationIndex, relation] of asArray(relations).entries()) {
    if (!relation || !endpointIds?.has(relation.from) || !endpointIds?.has(relation.to)) continue;
    if (!shouldCheckRelation(relation, relationIndex)) continue;
    const points = pathFor(relation)?.points;
    if (!Array.isArray(points) || points.length < 2) continue;
    const authoredFromSide = relation.fromSide && relation.fromSide !== 'auto' ? relation.fromSide : null;
    const authoredToSide = relation.toSide && relation.toSide !== 'auto' ? relation.toSide : null;
    const hasAuthoredRouteGeometry = Boolean(
      (relation.route && relation.route !== 'auto') || Array.isArray(relation.via),
    );
    const inferredFromSide = !hasAuthoredRouteGeometry && typeof fromSideFor === 'function'
      ? fromSideFor(relation)
      : null;
    const inferredToSide = !hasAuthoredRouteGeometry && typeof toSideFor === 'function'
      ? toSideFor(relation)
      : null;
    const fromSide = authoredFromSide ?? inferredFromSide;
    const toSide = authoredToSide ?? inferredToSide;
    const checks = [
      fromSide
        ? { ...endpointSideIssue(points, 'source', fromSide), sideOrigin: authoredFromSide ? 'authored' : 'inferred' }
        : null,
      toSide
        ? { ...endpointSideIssue(points, 'target', toSide), sideOrigin: authoredToSide ? 'authored' : 'inferred' }
        : null,
    ].filter((issue) => issue?.endpoint);
    for (const issue of checks) {
      const relationId = relation.id ? ` id "${relation.id}"` : '';
      const authoredField = issue.endpoint === 'source' ? 'fromSide' : 'toSide';
      const sideField = issue.sideOrigin === 'inferred' ? `inferred ${authoredField}` : authoredField;
      const segmentRole = issue.endpoint === 'source' ? 'first' : 'final';
      const from = issue.start.map((value) => Math.round(value * 10) / 10).join(', ');
      const to = issue.end.map((value) => Math.round(value * 10) / 10).join(', ');
      const message = `[clean-flow/endpoint-side-direction] ${diagramType} ${relationCollection}[${relationIndex}]${relationId} "${relation.from}" -> "${relation.to}" ${segmentRole} segment ${issue.segmentIndex} [${from}] -> [${to}] does not honor ${sideField} "${issue.side}" — it must run ${issue.expectedAxis} ${issue.expectedDirection}; ${routeHint}.`;
      recordDiagnostic({
        code: 'clean-flow/endpoint-side-direction',
        severity: 'error',
        message,
        subject: relationshipSubject(diagramType, relationCollection, relationIndex, relation),
        evidence: {
          endpoint: issue.endpoint,
          authoredField,
          sideOrigin: issue.sideOrigin,
          side: issue.side,
          segmentIndex: issue.segmentIndex,
          from: issue.start,
          to: issue.end,
          expectedAxis: issue.expectedAxis,
          expectedDirection: issue.expectedDirection,
        },
        supportedFixes: [routeHint],
      });
      problems.push(message);
    }
  }
  return problems;
}

// One mechanical quality gate for every renderer-owned relationship path.
// A renderer supplies its semantic obstacle set; source/target boxes are
// always exempt because paths are expected to terminate on their boundaries.
// Containers, lifelines, and other intentionally pass-through geometry should
// simply not be supplied as obstacles.
export function cleanFlowProblems({
  relations,
  obstacles,
  pathFor,
  diagramType,
  relationCollection,
  obstacleKind,
  clearance = 2,
  routeHint = 'adjust fromSide/toSide, set route/via or channel coordinates, or move the obstacle'
}) {
  // A relationship hidden behind an unrelated opaque node changes the
  // diagram's meaning, so this is a correctness invariant rather than an
  // opt-in composition preference. Keep it active even when the author omits
  // quality_profile; standard/showcase still control stricter visual budgets.
  const problems = [];
  const obstacleList = [...obstacles];
  const obstacleIds = new Set(obstacleList.map((obstacle) => obstacle?.id));
  for (const [relationIndex, relation] of asArray(relations).entries()) {
    if (!relation || typeof relation.from !== 'string' || typeof relation.to !== 'string') continue;
    if (!obstacleIds.has(relation.from) || !obstacleIds.has(relation.to)) continue;
    const points = pathFor(relation)?.points;
    if (!Array.isArray(points) || points.length < 2) continue;
    if (!points.every((point) => Array.isArray(point) && point.length === 2 && isFinitePoint(...point))) continue;

    const endpointIds = new Set([relation.from, relation.to]);
    for (const obstacle of obstacleList) {
      if (!obstacle || endpointIds.has(obstacle.id)) continue;
      if (!isFinitePoint(obstacle.x, obstacle.y, obstacle.width, obstacle.height)) continue;
      let hitSegment = -1;
      for (let segmentIndex = 0; segmentIndex < points.length - 1; segmentIndex += 1) {
        if (segmentIntersectsRect({ start: points[segmentIndex], end: points[segmentIndex + 1] }, obstacle, clearance)) {
          hitSegment = segmentIndex;
          break;
        }
      }
      if (hitSegment === -1) continue;
      const from = points[hitSegment].map(Math.round).join(', ');
      const to = points[hitSegment + 1].map(Math.round).join(', ');
      const relationId = relation.id ? ` id "${relation.id}"` : '';
      const message = `[clean-flow/edge-through-node] ${diagramType} ${relationCollection}[${relationIndex}]${relationId} "${relation.from}" -> "${relation.to}" crosses ${obstacleKind} "${obstacle.id}" (unrelated to this relationship) on segment ${hitSegment} [${from}] -> [${to}] (${clearance}px clearance) — ${routeHint}.`;
      recordDiagnostic({
        code: 'clean-flow/edge-through-node',
        severity: 'error',
        message,
        subject: relationshipSubject(diagramType, relationCollection, relationIndex, relation),
        evidence: {
          obstacleKind,
          obstacleId: obstacle.id,
          segmentIndex: hitSegment,
          from: points[hitSegment],
          to: points[hitSegment + 1],
          clearancePx: clearance,
        },
        supportedFixes: [routeHint],
      });
      problems.push(message);
    }
  }
  return problems;
}

// Build a read-only analysis copy of a polyline with straight-through
// waypoints removed. A waypoint on a forward-collinear run is not a visual
// endpoint, so treating it as one would hide a proper X that lands exactly on
// that waypoint. Reversals and real bends stay split: their shared point can
// still be an authored touch rather than a crossing. The source points are
// retained on every merged segment so diagnostics can name the authored
// segment that contains a hit without changing rendered/receipt geometry.
export function forwardCollinearAnalysisSegments(points) {
  const segments = [];
  for (let segmentIndex = 0; segmentIndex < asArray(points).length - 1; segmentIndex += 1) {
    const authoredStart = points[segmentIndex];
    const authoredEnd = points[segmentIndex + 1];
    const start = Array.isArray(authoredStart) ? [...authoredStart] : authoredStart;
    const end = Array.isArray(authoredEnd) ? [...authoredEnd] : authoredEnd;
    const sourceSegment = { start, end, segmentIndex };
    const previous = segments.at(-1);
    if (previous && segmentsContinueForward(previous.start, previous.end, start, end)) {
      previous.end = end;
      previous.sourceSegments.push(sourceSegment);
      continue;
    }
    segments.push({
      start,
      end,
      segmentIndex,
      sourceSegments: [sourceSegment],
    });
  }
  return segments;
}

export function sourceSegmentIndexAtPoint(segment, point) {
  const source = asArray(segment?.sourceSegments).find(({ start, end }) => (
    pointLiesOnSegment(point, start, end)
  ));
  return source?.segmentIndex ?? segment?.segmentIndex ?? 0;
}

function authoredAnalysisSegments(points) {
  return asArray(points).slice(0, -1).map((start, segmentIndex) => ({
    start,
    end: points[segmentIndex + 1],
    segmentIndex,
    sourceSegments: [{ start, end: points[segmentIndex + 1], segmentIndex }],
  }));
}

function segmentsContinueForward(firstStart, firstEnd, secondStart, secondEnd) {
  if (![firstStart, firstEnd, secondStart, secondEnd].every((point) => (
    Array.isArray(point) && point.length === 2 && isFinitePoint(...point)
  ))) return false;
  const epsilon = 0.0001;
  if (Math.abs(firstEnd[0] - secondStart[0]) > epsilon
    || Math.abs(firstEnd[1] - secondStart[1]) > epsilon) return false;
  const firstVector = [firstEnd[0] - firstStart[0], firstEnd[1] - firstStart[1]];
  const secondVector = [secondEnd[0] - secondStart[0], secondEnd[1] - secondStart[1]];
  const firstLength = Math.hypot(...firstVector);
  const secondLength = Math.hypot(...secondVector);
  if (firstLength <= epsilon || secondLength <= epsilon) return false;
  const cross = firstVector[0] * secondVector[1] - firstVector[1] * secondVector[0];
  if (Math.abs(cross) > epsilon) return false;
  const dot = firstVector[0] * secondVector[0] + firstVector[1] * secondVector[1];
  return dot > epsilon;
}

function pointLiesOnSegment(point, start, end) {
  if (![point, start, end].every((candidate) => (
    Array.isArray(candidate) && candidate.length === 2 && isFinitePoint(...candidate)
  ))) return false;
  const epsilon = 0.0001;
  const length = Math.hypot(end[0] - start[0], end[1] - start[1]);
  if (length <= epsilon) return Math.hypot(point[0] - start[0], point[1] - start[1]) <= epsilon;
  if (Math.abs(crossProduct(start, end, point)) > epsilon * length) return false;
  return point[0] >= Math.min(start[0], end[0]) - epsilon
    && point[0] <= Math.max(start[0], end[0]) + epsilon
    && point[1] >= Math.min(start[1], end[1]) - epsilon
    && point[1] <= Math.max(start[1], end[1]) + epsilon;
}

// Reject only a proper interior X between relationships that share no semantic
// endpoint. Endpoint touches, branch/merge ports, and collinear shared
// corridors are intentionally outside this contract because geometry alone
// cannot tell whether those are authored junctions.
export function cleanCrossingProblems({
  relations,
  endpointIds,
  pathFor,
  diagramType,
  relationCollection,
  profile = 'standard',
  profileIsAuthoritative = false,
  mergeForwardCollinearWaypoints = false,
  routeHint = 'adjust route/via or channel coordinates so the relationships use separate corridors'
}) {
  if (qualityProfileForGate(profile, profileIsAuthoritative) !== 'showcase') return [];
  const routed = asArray(relations).map((relation, index) => {
    if (!relation || !endpointIds.has(relation.from) || !endpointIds.has(relation.to)) return null;
    const points = pathFor(relation)?.points;
    if (!Array.isArray(points) || points.length < 2) return null;
    if (!points.every((point) => Array.isArray(point) && point.length === 2 && isFinitePoint(...point))) return null;
    return {
      relation,
      index,
      points,
      analysisSegments: mergeForwardCollinearWaypoints
        ? forwardCollinearAnalysisSegments(points)
        : authoredAnalysisSegments(points),
    };
  }).filter(Boolean);
  const problems = [];

  for (let leftIndex = 0; leftIndex < routed.length; leftIndex += 1) {
    const left = routed[leftIndex];
    for (let rightIndex = leftIndex + 1; rightIndex < routed.length; rightIndex += 1) {
      const right = routed[rightIndex];
      if ([left.relation.from, left.relation.to].some((id) => id === right.relation.from || id === right.relation.to)) continue;

      let hit = null;
      for (const leftSegment of left.analysisSegments) {
        if (hit) break;
        for (const rightSegment of right.analysisSegments) {
          const point = properSegmentIntersection(
            leftSegment.start,
            leftSegment.end,
            rightSegment.start,
            rightSegment.end
          );
          if (point) {
            hit = {
              point,
              leftSegment: sourceSegmentIndexAtPoint(leftSegment, point),
              rightSegment: sourceSegmentIndexAtPoint(rightSegment, point),
            };
            break;
          }
        }
      }
      if (!hit) continue;

      const describe = ({ relation, index }) => {
        const id = relation.id ? ` id "${relation.id}"` : '';
        return `${relationCollection}[${index}]${id} "${relation.from}" -> "${relation.to}"`;
      };
      const point = hit.point.map((value) => Math.round(value * 10) / 10).join(', ');
      const message = `[composition/proper-crossing] showcase ${diagramType} ${describe(left)} crosses ${describe(right)} at [${point}] (segments ${hit.leftSegment} and ${hit.rightSegment}) — ${routeHint}.`;
      recordDiagnostic({
        code: 'composition/proper-crossing',
        severity: 'error',
        message,
        subject: relationshipSubject(diagramType, relationCollection, left.index, left.relation),
        evidence: {
          otherRelationship: relationshipSubject(diagramType, relationCollection, right.index, right.relation),
          point: hit.point,
          segmentIndex: hit.leftSegment,
          otherSegmentIndex: hit.rightSegment,
        },
        supportedFixes: [routeHint],
      });
      problems.push(message);
    }
  }
  return problems;
}

// Two unrelated relationships that occupy the same visible corridor can read
// as one authored branch or merge even when neither relationship crosses a
// node or forms a proper X. Keep shared semantic endpoints exempt: their
// initial/final fan-out is real topology. Tiny overlaps below the route rhythm
// floor are ignored to avoid turning sub-pixel rounding into a quality debt.
export function collectAmbiguousCorridors({
  routedRelations,
  minOverlapPx = 8,
}) {
  const routed = asArray(routedRelations).map((entry, fallbackIndex) => {
    const relation = entry?.relation;
    if (!relation || typeof relation.from !== 'string' || typeof relation.to !== 'string') return null;
    const points = normalizeRoutePoints(entry?.points);
    if (points.length < 2) return null;
    return {
      relation,
      relationIndex: Number.isInteger(entry.relationIndex) ? entry.relationIndex : fallbackIndex,
      points,
    };
  }).filter(Boolean);
  const hits = [];

  for (let leftIndex = 0; leftIndex < routed.length; leftIndex += 1) {
    const left = routed[leftIndex];
    for (let rightIndex = leftIndex + 1; rightIndex < routed.length; rightIndex += 1) {
      const right = routed[rightIndex];
      if ([left.relation.from, left.relation.to].some((id) => id === right.relation.from || id === right.relation.to)) continue;

      let longest = null;
      for (let leftSegment = 0; leftSegment < left.points.length - 1; leftSegment += 1) {
        for (let rightSegment = 0; rightSegment < right.points.length - 1; rightSegment += 1) {
          const overlap = collinearAxisOverlap(
            left.points[leftSegment],
            left.points[leftSegment + 1],
            right.points[rightSegment],
            right.points[rightSegment + 1],
          );
          if (!overlap || overlap.length + 0.0001 < minOverlapPx) continue;
          if (!longest || overlap.length > longest.overlapLength + 0.0001) {
            longest = {
              left,
              right,
              leftSegment,
              rightSegment,
              overlapLength: overlap.length,
              overlapStart: overlap.start,
              overlapEnd: overlap.end,
            };
          }
        }
      }
      if (longest) hits.push(longest);
    }
  }
  return hits;
}

export function cleanAmbiguousCorridorProblems({
  relations,
  endpointIds,
  pathFor,
  diagramType,
  relationCollection,
  profile = 'standard',
  profileIsAuthoritative = false,
  routeHint = 'adjust route/via or channel coordinates so the relationships use separate corridors',
  minOverlapPx = 8,
}) {
  if (qualityProfileForGate(profile, profileIsAuthoritative) !== 'showcase') return [];
  const routedRelations = collectEligibleRoutedRelations({ relations, endpointIds, pathFor });

  return collectAmbiguousCorridors({ routedRelations, minOverlapPx }).map((hit) => {
    const describe = ({ relation, relationIndex }) => {
      const id = relation.id ? ` id "${relation.id}"` : '';
      return `${relationCollection}[${relationIndex}]${id} "${relation.from}" -> "${relation.to}"`;
    };
    const length = Math.round(hit.overlapLength * 10) / 10;
    const from = hit.overlapStart.map((value) => Math.round(value * 10) / 10).join(', ');
    const to = hit.overlapEnd.map((value) => Math.round(value * 10) / 10).join(', ');
    const message = `[composition/ambiguous-corridor] showcase ${diagramType} ${describe(hit.left)} shares a ${length}px corridor with ${describe(hit.right)} at [${from}] -> [${to}] (segments ${hit.leftSegment} and ${hit.rightSegment}; minimum ${minOverlapPx}px) — ${routeHint}.`;
    recordDiagnostic({
      code: 'composition/ambiguous-corridor',
      severity: 'error',
      message,
      subject: relationshipSubject(diagramType, relationCollection, hit.left.relationIndex, hit.left.relation),
      evidence: {
        otherRelationship: relationshipSubject(diagramType, relationCollection, hit.right.relationIndex, hit.right.relation),
        overlapLengthPx: length,
        minimumPx: minOverlapPx,
        from: hit.overlapStart,
        to: hit.overlapEnd,
        segmentIndex: hit.leftSegment,
        otherSegmentIndex: hit.rightSegment,
      },
      supportedFixes: [routeHint],
    });
    return message;
  });
}

// Relationship paths may cross a structural frame, but they must not borrow a
// frame side as a routing corridor. Rounded rectangle corners are trimmed from
// the modeled straight sides so a short corner touch is not mistaken for a
// border run. Any positive straight overlap beyond the numeric epsilon is a
// hard failure in every quality profile; 16px belongs only to the separate,
// neutral short-segment metric and is not a corridor exemption.
export function collectBorderRuns({ routedRelations, frames }) {
  const hits = [];
  for (const routed of asArray(routedRelations)) {
    const routeSegments = Array.isArray(routed?.segments)
      ? routed.segments
      : asArray(routed?.points).slice(0, -1).map((start, index) => ({ start, end: routed.points[index + 1] }));
    if (!routeSegments.length) continue;
    if (!routeSegments.every((segment) => (
      Array.isArray(segment?.start) && segment.start.length === 2 && isFinitePoint(...segment.start)
      && Array.isArray(segment?.end) && segment.end.length === 2 && isFinitePoint(...segment.end)
    ))) continue;
    for (const [frameIndex, frame] of asArray(frames).entries()) {
      for (const border of frameBorderSegments(frame)) {
        const overlaps = [];
        for (let segmentIndex = 0; segmentIndex < routeSegments.length; segmentIndex += 1) {
          const segment = routeSegments[segmentIndex];
          const overlap = collinearAxisOverlap(
            segment.start,
            segment.end,
            border.start,
            border.end,
          );
          if (!overlap || overlap.length <= 0.0001) continue;
          overlaps.push({ ...overlap, segmentIndex });
        }
        if (!overlaps.length) continue;
        const merged = mergeBorderOverlaps(overlaps, border);
        const longest = [...merged].sort((left, right) => right.length - left.length || left.low - right.low)[0];
        hits.push({
          ...routed,
          frame,
          frameIndex,
          side: border.side,
          segmentIndex: Math.min(...overlaps.map((overlap) => overlap.segmentIndex)),
          overlapLength: merged.reduce((total, overlap) => total + overlap.length, 0),
          overlapStart: longest.start,
          overlapEnd: longest.end,
        });
      }
    }
  }
  return hits;
}

export function cleanBorderRunProblems({
  relations,
  endpointIds,
  frames,
  pathFor,
  diagramType,
  relationCollection,
  profile,
  profileIsAuthoritative = false,
  routeHint = 'adjust route/via or channel coordinates so the relationship crosses the frame perpendicularly through a clear opening'
}) {
  if (!qualityProfileForGate(profile, profileIsAuthoritative)) return [];
  const routedRelations = collectEligibleRoutedRelations({ relations, endpointIds, pathFor });
  return collectBorderRuns({ routedRelations, frames }).map((hit) => {
    const relation = hit.relation || {};
    const relationId = relation.id ? ` id "${relation.id}"` : '';
    const frameKind = hit.frame?.kind || hit.frame?.shape || 'frame';
    const frameIdentity = hit.frame?.label || hit.frame?.id || hit.frameIndex;
    const length = Math.round(hit.overlapLength * 10) / 10;
    const from = hit.overlapStart.map((value) => Math.round(value * 10) / 10).join(', ');
    const to = hit.overlapEnd.map((value) => Math.round(value * 10) / 10).join(', ');
    const message = `[composition/container-border-run] ${diagramType} ${relationCollection}[${hit.relationIndex}]${relationId} "${relation.from}" -> "${relation.to}" follows ${frameKind} "${frameIdentity}" ${hit.side} border for ${length}px on segment ${hit.segmentIndex} [${from}] -> [${to}] — ${routeHint}.`;
    recordDiagnostic({
      code: 'composition/container-border-run',
      severity: 'error',
      message,
      subject: relationshipSubject(diagramType, relationCollection, hit.relationIndex, relation),
      evidence: {
        frameKind,
        frameId: hit.frame?.id,
        frameLabel: hit.frame?.label,
        side: hit.side,
        segmentIndex: hit.segmentIndex,
        overlapLengthPx: length,
        from: hit.overlapStart,
        to: hit.overlapEnd,
      },
      supportedFixes: [routeHint],
    });
    return message;
  });
}

export function routeBudgetMetrics({
  routedRelations,
  bendsPerRelationship = 2,
  stretch = 1.35,
  segmentPx = 16,
  microSegmentPx = 8,
}) {
  let maxBends = 0;
  let routesOverSuggestedBends = 0;
  let maxStretch = null;
  let routesOverSuggestedStretch = 0;
  let minSegmentPx = null;
  let minInteriorSegmentPx = null;
  let shortSegmentCount = 0;
  let shortEndpointSegmentCount = 0;
  let shortInteriorSegmentCount = 0;
  let microSegmentCount = 0;

  for (const routed of asArray(routedRelations)) {
    const points = normalizeRoutePoints(routed?.points);
    if (points.length < 2) continue;
    const bends = Math.max(0, points.length - 2);
    maxBends = Math.max(maxBends, bends);
    if (bends > bendsPerRelationship) routesOverSuggestedBends += 1;

    let routeLength = 0;
    for (let index = 0; index < points.length - 1; index += 1) {
      const length = Math.abs(points[index + 1][0] - points[index][0]) + Math.abs(points[index + 1][1] - points[index][1]);
      if (length <= 0.0001) continue;
      const position = segmentPosition(index, points.length - 1);
      routeLength += length;
      minSegmentPx = minSegmentPx == null ? length : Math.min(minSegmentPx, length);
      if (position === 'interior') {
        minInteriorSegmentPx = minInteriorSegmentPx == null ? length : Math.min(minInteriorSegmentPx, length);
      }
      if (length < segmentPx) {
        shortSegmentCount += 1;
        if (position === 'interior') shortInteriorSegmentCount += 1;
        else shortEndpointSegmentCount += 1;
      }
      if (length < microSegmentPx) microSegmentCount += 1;
    }
    const direct = Math.abs(points.at(-1)[0] - points[0][0]) + Math.abs(points.at(-1)[1] - points[0][1]);
    if (direct > 0.0001) {
      const routeStretch = routeLength / direct;
      maxStretch = maxStretch == null ? routeStretch : Math.max(maxStretch, routeStretch);
      if (routeStretch > stretch + 0.0001) routesOverSuggestedStretch += 1;
    }
  }

  return {
    maxBends,
    routesOverSuggestedBends,
    maxStretch,
    routesOverSuggestedStretch,
    minSegmentPx,
    minInteriorSegmentPx,
    shortSegmentCount,
    shortEndpointSegmentCount,
    shortInteriorSegmentCount,
    microSegmentCount,
  };
}

export function collectRouteRhythmIssues({
  routedRelations,
  interiorSegmentPx = 16,
  microSegmentPx = 8,
}) {
  const issues = [];
  for (const [fallbackIndex, routed] of asArray(routedRelations).entries()) {
    const points = normalizeRoutePoints(routed?.points);
    if (points.length < 2) continue;
    for (let segmentIndex = 0; segmentIndex < points.length - 1; segmentIndex += 1) {
      const start = points[segmentIndex];
      const end = points[segmentIndex + 1];
      const length = Math.abs(end[0] - start[0]) + Math.abs(end[1] - start[1]);
      if (length <= 0.0001) continue;
      const position = segmentPosition(segmentIndex, points.length - 1);
      const code = length < microSegmentPx - 0.0001
        ? 'composition/micro-segment'
        : position === 'interior' && length < interiorSegmentPx - 0.0001
          ? 'composition/short-interior-segment'
          : null;
      if (!code) continue;
      issues.push({
        code,
        relation: routed.relation,
        relationIndex: Number.isInteger(routed.relationIndex) ? routed.relationIndex : fallbackIndex,
        segmentIndex,
        position,
        length,
        start,
        end,
      });
    }
  }
  return issues;
}

export function cleanRouteRhythmProblems({
  relations,
  endpointIds,
  pathFor,
  diagramType,
  relationCollection,
  profile,
  profileIsAuthoritative = false,
  routeHint = 'move the channel/via point to remove the cramped turn or give the route more corridor space',
  interiorSegmentPx = 16,
  microSegmentPx = 8,
}) {
  if (qualityProfileForGate(profile, profileIsAuthoritative) !== 'showcase') return [];
  const routedRelations = collectEligibleRoutedRelations({ relations, endpointIds, pathFor });
  return collectRouteRhythmIssues({ routedRelations, interiorSegmentPx, microSegmentPx }).map((hit) => {
    const relation = hit.relation || {};
    const relationId = relation.id ? ` id "${relation.id}"` : '';
    const length = Math.round(hit.length * 10) / 10;
    const from = hit.start.map((value) => Math.round(value * 10) / 10).join(', ');
    const to = hit.end.map((value) => Math.round(value * 10) / 10).join(', ');
    const rule = hit.code === 'composition/micro-segment'
      ? `is below the ${microSegmentPx}px micro-segment floor`
      : `is below the ${interiorSegmentPx}px interior-segment floor`;
    const message = `[${hit.code}] showcase ${diagramType} ${relationCollection}[${hit.relationIndex}]${relationId} "${relation.from}" -> "${relation.to}" has a ${length}px ${hit.position} segment ${hit.segmentIndex} [${from}] -> [${to}] that ${rule} — ${routeHint}.`;
    recordDiagnostic({
      code: hit.code,
      severity: 'error',
      message,
      subject: relationshipSubject(diagramType, relationCollection, hit.relationIndex, relation),
      evidence: {
        segmentIndex: hit.segmentIndex,
        position: hit.position,
        lengthPx: length,
        minimumPx: hit.code === 'composition/micro-segment' ? microSegmentPx : interiorSegmentPx,
        from: hit.start,
        to: hit.end,
      },
      supportedFixes: [routeHint],
    });
    return message;
  });
}

export function cleanLabelRouteClearanceProblems({
  relations,
  labels,
  endpointIds,
  pathFor,
  diagramType,
  relationCollection,
  profile,
  profileIsAuthoritative = false,
  threshold = 4,
  routeHint = 'adjust labelAt, labelDx, labelDy, or labelSegment; otherwise adjust the other relationship route/via/channel',
}) {
  if (qualityProfileForGate(profile, profileIsAuthoritative) !== 'showcase') return [];
  const routedRelations = collectEligibleRoutedRelations({ relations, endpointIds, pathFor });
  return collectLabelRouteClearance({ labels, routedRelations, threshold }).map((hit) => {
    const describe = (relation, relationIndex) => {
      const relationId = relation?.id ? ` id "${relation.id}"` : '';
      const relationLabel = relation?.label ? ` label "${relation.label}"` : '';
      return `${relationCollection}[${relationIndex}]${relationId} "${relation?.from}" -> "${relation?.to}"${relationLabel}`;
    };
    const clearance = Math.round(hit.clearance * 10) / 10;
    const from = hit.start.map((value) => Math.round(value * 10) / 10).join(', ');
    const to = hit.end.map((value) => Math.round(value * 10) / 10).join(', ');
    const message = `[composition/label-route-clearance] showcase ${diagramType} label "${hit.label?.label || hit.labelRelation?.label || ''}" on ${describe(hit.labelRelation, hit.labelRelationIndex)} is ${clearance}px from ${describe(hit.otherRelation, hit.otherRelationIndex)} segment ${hit.segmentIndex} [${from}] -> [${to}] (label rect ${formatRect(hit.rect)}; minimum ${threshold}px) — ${routeHint}.`;
    recordDiagnostic({
      code: 'composition/label-route-clearance',
      severity: 'error',
      message,
      subject: relationshipSubject(diagramType, relationCollection, hit.labelRelationIndex, hit.labelRelation),
      evidence: {
        label: hit.label?.label || hit.labelRelation?.label || '',
        otherRelationship: relationshipSubject(diagramType, relationCollection, hit.otherRelationIndex, hit.otherRelation),
        segmentIndex: hit.segmentIndex,
        clearancePx: clearance,
        minimumPx: threshold,
        labelRect: hit.rect,
        from: hit.start,
        to: hit.end,
      },
      supportedFixes: [routeHint],
    });
    return message;
  });
}

function qualityProfileForGate(profile, profileIsAuthoritative) {
  return profileIsAuthoritative
    ? profile
    : process.env.ARCHIFY_QUALITY_PROFILE || profile;
}

function collectEligibleRoutedRelations({ relations, endpointIds, pathFor }) {
  return asArray(relations).map((relation, relationIndex) => {
    if (!relation || typeof relation.from !== 'string' || typeof relation.to !== 'string') return null;
    if (endpointIds && (!endpointIds.has(relation.from) || !endpointIds.has(relation.to))) return null;
    return { relation, relationIndex, points: pathFor(relation)?.points };
  }).filter(Boolean);
}

function segmentPosition(index, segmentCount) {
  if (index === 0) return 'source-stub';
  if (index === segmentCount - 1) return 'target-stub';
  return 'interior';
}

export function normalizeRoutePoints(points) {
  const finite = asArray(points).filter((point) => Array.isArray(point) && point.length === 2 && isFinitePoint(...point));
  const deduped = [];
  for (const point of finite) {
    const previous = deduped.at(-1);
    if (!previous || Math.abs(point[0] - previous[0]) > 0.0001 || Math.abs(point[1] - previous[1]) > 0.0001) deduped.push(point);
  }
  const normalized = [];
  for (const point of deduped) {
    while (normalized.length >= 2 && collinearForward(normalized.at(-2), normalized.at(-1), point)) normalized.pop();
    normalized.push(point);
  }
  return normalized;
}

function pointRectDistance(point, rect) {
  const dx = Math.max(rect.x - point[0], 0, point[0] - (rect.x + rect.width));
  const dy = Math.max(rect.y - point[1], 0, point[1] - (rect.y + rect.height));
  return Math.hypot(dx, dy);
}

function pointSegmentDistance(point, start, end) {
  const dx = end[0] - start[0];
  const dy = end[1] - start[1];
  const lengthSquared = dx * dx + dy * dy;
  if (lengthSquared <= 0.0000001) return Math.hypot(point[0] - start[0], point[1] - start[1]);
  const projection = Math.max(0, Math.min(1, ((point[0] - start[0]) * dx + (point[1] - start[1]) * dy) / lengthSquared));
  return Math.hypot(point[0] - (start[0] + projection * dx), point[1] - (start[1] + projection * dy));
}

function collinearForward(a, b, c) {
  if (Math.abs(crossProduct(a, b, c)) > 0.0001) return false;
  return (b[0] - a[0]) * (c[0] - b[0]) + (b[1] - a[1]) * (c[1] - b[1]) >= -0.0001;
}

function frameBorderSegments(frame) {
  if (!frame || typeof frame !== 'object') return [];
  if (frame.shape === 'line') {
    const start = frame.start || [frame.x1, frame.y1];
    const end = frame.end || [frame.x2, frame.y2];
    return isFinitePoint(...start, ...end) ? [{ side: 'line', start, end }] : [];
  }
  if (!isFinitePoint(frame.x, frame.y, frame.width, frame.height) || frame.width <= 0 || frame.height <= 0) return [];
  const radius = Math.max(0, Math.min(Number(frame.radius) || 0, frame.width / 2, frame.height / 2));
  const left = frame.x;
  const right = frame.x + frame.width;
  const top = frame.y;
  const bottom = frame.y + frame.height;
  return [
    { side: 'top', start: [left + radius, top], end: [right - radius, top] },
    { side: 'right', start: [right, top + radius], end: [right, bottom - radius] },
    { side: 'bottom', start: [right - radius, bottom], end: [left + radius, bottom] },
    { side: 'left', start: [left, bottom - radius], end: [left, top + radius] },
  ].filter(({ start, end }) => Math.hypot(end[0] - start[0], end[1] - start[1]) > 0.0001);
}

function mergeBorderOverlaps(overlaps, border) {
  const horizontal = Math.abs(border.start[1] - border.end[1]) <= 0.0001;
  const axis = horizontal ? 0 : 1;
  const fixed = horizontal ? border.start[1] : border.start[0];
  const sorted = overlaps.map((overlap) => ({
    low: Math.min(overlap.start[axis], overlap.end[axis]),
    high: Math.max(overlap.start[axis], overlap.end[axis]),
  })).sort((left, right) => left.low - right.low || left.high - right.high);
  const merged = [];
  for (const interval of sorted) {
    const previous = merged.at(-1);
    if (previous && interval.low <= previous.high + 0.0001) previous.high = Math.max(previous.high, interval.high);
    else merged.push({ ...interval });
  }
  return merged.map((interval) => ({
    ...interval,
    length: interval.high - interval.low,
    start: horizontal ? [interval.low, fixed] : [fixed, interval.low],
    end: horizontal ? [interval.high, fixed] : [fixed, interval.high],
  }));
}

function collinearAxisOverlap(a, b, c, d) {
  const epsilon = 0.0001;
  const horizontal = Math.abs(a[1] - b[1]) <= epsilon
    && Math.abs(c[1] - d[1]) <= epsilon
    && Math.abs(a[1] - c[1]) <= epsilon;
  const vertical = Math.abs(a[0] - b[0]) <= epsilon
    && Math.abs(c[0] - d[0]) <= epsilon
    && Math.abs(a[0] - c[0]) <= epsilon;
  if (!horizontal && !vertical) return null;
  const axis = horizontal ? 0 : 1;
  const low = Math.max(Math.min(a[axis], b[axis]), Math.min(c[axis], d[axis]));
  const high = Math.min(Math.max(a[axis], b[axis]), Math.max(c[axis], d[axis]));
  if (high - low <= epsilon) return null;
  const fixed = horizontal ? a[1] : a[0];
  return {
    length: high - low,
    start: horizontal ? [low, fixed] : [fixed, low],
    end: horizontal ? [high, fixed] : [fixed, high],
  };
}

function properSegmentIntersection(a, b, c, d) {
  const abC = crossProduct(a, b, c);
  const abD = crossProduct(a, b, d);
  const cdA = crossProduct(c, d, a);
  const cdB = crossProduct(c, d, b);
  const epsilon = 0.0001;
  const opposite = (left, right) => (left > epsilon && right < -epsilon) || (left < -epsilon && right > epsilon);
  if (!opposite(abC, abD) || !opposite(cdA, cdB)) return null;

  const denominator = (a[0] - b[0]) * (c[1] - d[1]) - (a[1] - b[1]) * (c[0] - d[0]);
  if (Math.abs(denominator) < epsilon) return null;
  const ab = a[0] * b[1] - a[1] * b[0];
  const cd = c[0] * d[1] - c[1] * d[0];
  return [
    (ab * (c[0] - d[0]) - (a[0] - b[0]) * cd) / denominator,
    (ab * (c[1] - d[1]) - (a[1] - b[1]) * cd) / denominator
  ];
}

function crossProduct(a, b, c) {
  return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
}

function pointInBox(point, box) {
  return point[0] >= box.x1 && point[0] <= box.x2 && point[1] >= box.y1 && point[1] <= box.y2;
}

function segmentsIntersect(a, b, c, d) {
  const o1 = orientation(a, b, c);
  const o2 = orientation(a, b, d);
  const o3 = orientation(c, d, a);
  const o4 = orientation(c, d, b);

  if (o1 === 0 && onSegment(a, c, b)) return true;
  if (o2 === 0 && onSegment(a, d, b)) return true;
  if (o3 === 0 && onSegment(c, a, d)) return true;
  if (o4 === 0 && onSegment(c, b, d)) return true;

  return o1 !== o2 && o3 !== o4;
}

function orientation(a, b, c) {
  const value = (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1]);
  if (Math.abs(value) < 0.0001) return 0;
  return value > 0 ? 1 : 2;
}

function onSegment(a, b, c) {
  return (
    b[0] <= Math.max(a[0], c[0]) &&
    b[0] >= Math.min(a[0], c[0]) &&
    b[1] <= Math.max(a[1], c[1]) &&
    b[1] >= Math.min(a[1], c[1])
  );
}

export function anchor(rect, side) {
  switch (side) {
    case 'left': return [rect.x, rect.cy];
    case 'right': return [rect.x + rect.width, rect.cy];
    case 'top': return [rect.cx, rect.y];
    case 'bottom': return [rect.cx, rect.y + rect.height];
    default:
      return [rect.x + rect.width, rect.cy];
  }
}

const PORT_OUTWARD_VECTOR = {
  left: [-1, 0],
  right: [1, 0],
  top: [0, -1],
  bottom: [0, 1],
};

// Automatic port spreading can put otherwise parallel anchors only a few
// pixels apart. A conventional midpoint dogleg then violates the renderer's
// own 8px/16px route-rhythm floors. Return a full outside-channel route when
// that happens, or null when the normal automatic route remains appropriate.
export function automaticPortRhythmBridge(
  start,
  end,
  fromSide,
  toSide,
  { endpointStubPx = 24, interiorSegmentPx = 16, accept } = {},
) {
  if (!Array.isArray(start) || !Array.isArray(end)
      || start.length !== 2 || end.length !== 2
      || !isFinitePoint(...start, ...end)) return null;
  const fromVector = PORT_OUTWARD_VECTOR[fromSide];
  const toVector = PORT_OUTWARD_VECTOR[toSide];
  if (!fromVector || !toVector) return null;

  const startStub = [
    start[0] + fromVector[0] * endpointStubPx,
    start[1] + fromVector[1] * endpointStubPx,
  ];
  const endStub = [
    end[0] + toVector[0] * endpointStubPx,
    end[1] + toVector[1] * endpointStubPx,
  ];
  const candidates = [];
  const verticalSides = new Set(['top', 'bottom']);
  const horizontalSides = new Set(['left', 'right']);

  if (verticalSides.has(fromSide) && verticalSides.has(toSide)
      && Math.abs(start[0] - end[0]) < interiorSegmentPx) {
    for (const channelX of [
      Math.max(start[0], end[0]) + interiorSegmentPx,
      Math.min(start[0], end[0]) - interiorSegmentPx,
    ]) {
      candidates.push([
        start,
        startStub,
        [channelX, startStub[1]],
        [channelX, endStub[1]],
        endStub,
        end,
      ]);
    }
  }
  if (horizontalSides.has(fromSide) && horizontalSides.has(toSide)
      && Math.abs(start[1] - end[1]) < interiorSegmentPx) {
    for (const channelY of [
      Math.max(start[1], end[1]) + interiorSegmentPx,
      Math.min(start[1], end[1]) - interiorSegmentPx,
    ]) {
      candidates.push([
        start,
        startStub,
        [startStub[0], channelY],
        [endStub[0], channelY],
        endStub,
        end,
      ]);
    }
  }

  return candidates
    .map((points) => normalizeRoutePoints(points))
    .find((points) => (
      routeHonorsEndpointSides(points, fromSide, toSide)
      && collectRouteRhythmIssues({ routedRelations: [{ points }], interiorSegmentPx }).length === 0
      && (typeof accept !== 'function' || accept(points))
    )) || null;
}

// Keep conservative auto-routed fan-out/fan-in relationships visually
// distinct without changing authored route controls. The returned map only
// contains endpoints that belong to a shared automatic midpoint anchor.
export function automaticPortSpread(relations, boxes, { gutter = 16, maxSpacing = 14, sideFor } = {}) {
  const groups = new Map();
  const spread = new Map();

  const add = (relation, endpoint, rect, side, counterpart) => {
    const key = `${rect.id}\u0000${side}`;
    const items = groups.get(key) || [];
    items.push({ relation, endpoint, rect, side, counterpart });
    groups.set(key, items);
  };

  for (const relation of asArray(relations)) {
    if (!relation || (relation.route && relation.route !== 'auto')) continue;
    if (relation.via || relation.channelX !== undefined || relation.channelY !== undefined || relation.labelAt) continue;
    const from = boxes.get(relation.from);
    const to = boxes.get(relation.to);
    if (!from || !to) continue;
    const fromSide = chosenSide(
      relation.fromSide,
      sideFor?.(relation, 'source') || defaultFromSide(from, to),
    );
    const toSide = chosenSide(
      relation.toSide,
      sideFor?.(relation, 'target') || defaultToSide(from, to),
    );
    add(relation, 'from', from, fromSide, to);
    add(relation, 'to', to, toSide, from);
  }

  for (const items of groups.values()) {
    if (items.length < 2) continue;
    const verticalSide = items[0].side === 'left' || items[0].side === 'right';
    items.sort((a, b) => {
      const aCoordinate = verticalSide ? a.counterpart.cy : a.counterpart.cx;
      const bCoordinate = verticalSide ? b.counterpart.cy : b.counterpart.cx;
      if (aCoordinate !== bCoordinate) return aCoordinate - bCoordinate;
      const aKey = `${a.relation.id || ''}\u0000${a.relation.from}\u0000${a.relation.to}\u0000${a.relation.label || ''}`;
      const bKey = `${b.relation.id || ''}\u0000${b.relation.from}\u0000${b.relation.to}\u0000${b.relation.label || ''}`;
      return aKey < bKey ? -1 : aKey > bKey ? 1 : 0;
    });

    const extent = verticalSide ? items[0].rect.height : items[0].rect.width;
    const usable = Math.max(0, extent - gutter * 2);
    const spacing = Math.min(maxSpacing, usable / (items.length - 1));
    if (!(spacing > 0)) continue;

    for (const [index, item] of items.entries()) {
      const offset = (index - (items.length - 1) / 2) * spacing;
      const point = anchor(item.rect, item.side);
      if (verticalSide) point[1] += offset;
      else point[0] += offset;
      const endpoints = spread.get(item.relation) || {};
      endpoints[item.endpoint] = point;
      spread.set(item.relation, endpoints);
    }
  }

  return spread;
}

export function defaultFromSide(from, to) {
  if (to.cx < from.cx) return 'left';
  if (to.cx > from.cx) return 'right';
  if (to.cy > from.cy) return 'bottom';
  return 'top';
}

export function defaultToSide(from, to) {
  if (to.cx < from.cx) return 'right';
  if (to.cx > from.cx) return 'left';
  if (to.cy > from.cy) return 'top';
  return 'bottom';
}

export function chosenSide(side, fallback) {
  return side && side !== 'auto' ? side : fallback;
}

export function polylinePath(points) {
  return points.map(([x, y], index) => `${index === 0 ? 'M' : 'L'} ${x} ${y}`).join(' ');
}

export function routePointsValue(points) {
  return asArray(points)
    .filter((point) => Array.isArray(point) && point.length === 2 && isFinitePoint(...point))
    .map(([x, y]) => `${x},${y}`)
    .join(';');
}

export function roundedPath(points, radius) {
  if (points.length < 3 || radius <= 0) {
    return polylinePath(points);
  }

  const commands = [`M ${points[0][0]} ${points[0][1]}`];
  for (let i = 1; i < points.length - 1; i += 1) {
    const [px, py] = points[i - 1];
    const [cx, cy] = points[i];
    const [nx, ny] = points[i + 1];
    const prevLen = Math.hypot(cx - px, cy - py);
    const nextLen = Math.hypot(nx - cx, ny - cy);
    const r = Math.min(radius, prevLen / 2, nextLen / 2);
    if (r < 1) {
      commands.push(`L ${cx} ${cy}`);
      continue;
    }
    const before = [cx - ((cx - px) / prevLen) * r, cy - ((cy - py) / prevLen) * r];
    const after = [cx + ((nx - cx) / nextLen) * r, cy + ((ny - cy) / nextLen) * r];
    commands.push(`L ${before[0]} ${before[1]}`);
    commands.push(`Q ${cx} ${cy} ${after[0]} ${after[1]}`);
  }
  const [endX, endY] = points[points.length - 1];
  commands.push(`L ${endX} ${endY}`);
  return commands.join(' ');
}

// Shared by edges/flows/transitions: all carry the same optional
// labelAt/labelDx/labelDy/labelSegment knobs.
export function labelPoint(item, points) {
  if (item.labelAt) return item.labelAt;
  if (points.length === 2) {
    return [
      (points[0][0] + points[1][0]) / 2 + (item.labelDx || 0),
      points[0][1] - 10 + (item.labelDy || 0)
    ];
  }
  const segmentIndex = Math.min(points.length - 2, Math.max(0, item.labelSegment ?? 1));
  const a = points[segmentIndex];
  const b = points[segmentIndex + 1];
  return [(a[0] + b[0]) / 2 + (item.labelDx || 0), (a[1] + b[1]) / 2 - 10 + (item.labelDy || 0)];
}

export const componentFill = {
  frontend: 'c-frontend',
  backend: 'c-backend',
  database: 'c-database',
  cloud: 'c-cloud',
  security: 'c-security',
  messagebus: 'c-messagebus',
  external: 'c-external'
};

export const componentText = {
  frontend: 't-frontend',
  backend: 't-backend',
  database: 't-database',
  cloud: 't-cloud',
  security: 't-security',
  messagebus: 't-messagebus',
  external: 't-external'
};

export const arrowClassMap = {
  default: ['a-default', 'arrowhead'],
  emphasis: ['a-emphasis', 'arrowhead-emphasis'],
  security: ['a-security', 'arrowhead-security'],
  dashed: ['a-dashed', 'arrowhead-dashed']
};

// Label accent per edge variant. Workflow colors dashed (async trace) labels
// like the trace store it points at; the other renderers use the bus color.
export function variantAccent(variant, { dashed = 't-messagebus' } = {}) {
  return variant === 'security'
    ? 't-security'
    : variant === 'emphasis'
      ? 't-backend'
      : variant === 'dashed'
        ? dashed
        : 't-muted';
}

export function formatRect(r) {
  return `[${Math.round(r.x)}, ${Math.round(r.y)}, ${Math.round(r.width)}, ${Math.round(r.height)}]`;
}

function formatDelta(n) {
  const v = Math.round(n);
  return v >= 0 ? `+${v}` : String(v);
}

/** Actionable hint when an edge label rect hits a node/component box (#7). */
export function suggestLabelObstacleFix(labelRect, lx, ly, obstacle, obstacleKind = 'component') {
  const lxR = Math.round(lx);
  const lyR = Math.round(ly);
  const belowY = Math.round(obstacle.y + obstacle.height + 14);
  const aboveY = Math.round(obstacle.y - 4);
  return [
    `  label rect: ${formatRect(labelRect)}`,
    `  ${obstacleKind} "${obstacle.id}" rect: ${formatRect(obstacle)}`,
    `  Suggested fix: labelAt [${lxR}, ${belowY}] or labelDy ${formatDelta(belowY - lyR)} (below); or labelAt [${lxR}, ${aboveY}] or labelDy ${formatDelta(aboveY - lyR)} (above)`,
  ].join('\n');
}

/** Hint when two edge labels collide. */
export function suggestLabelPairFix(a, b) {
  return [
    `  "${a.label}" ${formatRect(a)}; "${b.label}" ${formatRect(b)}`,
    '  Suggested fix: adjust labelDx/labelDy/labelSegment, or route one relationship through a separate corridor',
  ].join('\n');
}

/** Hint when two components/nodes are too close. */
export function suggestComponentSeparation(a, b, minGap = 8) {
  const rightX = Math.round(a.x + a.width + minGap);
  const belowY = Math.round(a.y + a.height + minGap);
  return [
    `  "${a.id}" ${formatRect(a)}; "${b.id}" ${formatRect(b)}`,
    `  Suggested fix: move "${b.id}" pos to [${rightX}, ${Math.round(b.y)}] (right of "${a.id}") or [${Math.round(b.x)}, ${belowY}] (below)`,
  ].join('\n');
}
```

## renderers/shared/i18n.mjs

```js
export const SUPPORTED_LOCALES = ['en', 'zh-CN'];
export const DEFAULT_LOCALE = 'en';

const ESCAPE_MAP = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };

export function escapeHtml(value) {
  return String(value ?? '').replace(/[&<>"']/g, (character) => ESCAPE_MAP[character]);
}

// One catalog feeds renderer-time SVG/HTML copy and the selected runtime
// catalog embedded in each standalone artifact. Keeping every locale in one
// tuple makes missing translations impossible to hide behind an English
// fallback during development.
const MESSAGE_PAIRS = {
  'page.title': ['{title} Diagram', '{title}'],
  'diagram.description.architecture': ['An architecture diagram generated by Archify.', '由 Archify 生成的架构图。'],
  'diagram.description.workflow': ['A workflow diagram generated by Archify.', '由 Archify 生成的工作流图。'],
  'diagram.description.sequence': ['A sequence diagram generated by Archify.', '由 Archify 生成的时序图。'],
  'diagram.description.dataflow': ['A data-flow diagram generated by Archify.', '由 Archify 生成的数据流图。'],
  'diagram.description.lifecycle': ['A lifecycle diagram generated by Archify.', '由 Archify 生成的生命周期图。'],
  'node.focus': ['Focus {label}', '聚焦{label}'],
  'node.focus.detail': ['Focus {label}, {detail}', '聚焦{label}，{detail}'],
  'node.context.architecture': ['Architecture component', '架构组件'],
  'node.context.workflow': ['Workflow node', '工作流节点'],
  'node.context.sequence': ['Sequence participant', '时序参与者'],
  'node.context.dataflow': ['Data-flow node', '数据流节点'],
  'node.context.lifecycle': ['Lifecycle state', '生命周期状态'],
  'legend.title': ['Legend', '图例'],

  'legend.architecture.frontend': ['Frontend', '前端'],
  'legend.architecture.backend': ['Backend', '后端'],
  'legend.architecture.database': ['Database', '数据库'],
  'legend.architecture.cloud': ['Cloud', '云服务'],
  'legend.architecture.security': ['Security', '安全'],
  'legend.architecture.messagebus': ['Message bus', '消息总线'],
  'legend.architecture.external': ['External', '外部系统'],
  'legend.workflow.frontend': ['User UI', '用户界面'],
  'legend.workflow.backend': ['Agent logic', 'Agent 逻辑'],
  'legend.workflow.security': ['Policy', '策略'],
  'legend.workflow.messagebus': ['Tool action', '工具操作'],
  'legend.workflow.database': ['Context / trace', '上下文 / 追踪'],
  'legend.workflow.cloud': ['Cloud service', '云服务'],
  'legend.workflow.external': ['External system', '外部系统'],
  'legend.sequence.emphasis': ['request', '请求'],
  'legend.sequence.return': ['return', '返回'],
  'legend.sequence.security': ['security', '安全'],
  'legend.sequence.dashed': ['async trace', '异步追踪'],
  'legend.sequence.default': ['default message', '默认消息'],
  'legend.dataflow.emphasis': ['primary data', '主要数据'],
  'legend.dataflow.security': ['policy / PII', '策略 / PII'],
  'legend.dataflow.dashed': ['async batch', '异步批处理'],
  'legend.dataflow.database': ['data store', '数据存储'],
  'legend.dataflow.default': ['data flow', '数据流'],
  'legend.lifecycle.start': ['start', '开始'],
  'legend.lifecycle.active': ['active state', '活动状态'],
  'legend.lifecycle.waiting': ['waiting', '等待'],
  'legend.lifecycle.decision': ['decision', '决策'],
  'legend.lifecycle.success': ['terminal success', '成功终态'],
  'legend.lifecycle.failure': ['failure / exit', '失败 / 退出'],
  'legend.lifecycle.neutral': ['neutral', '中性状态'],
  'legend.lifecycle.external': ['external', '外部状态'],

  'viewer.kind.frontend': ['Frontend', '前端'],
  'viewer.kind.backend': ['Backend', '后端'],
  'viewer.kind.database': ['Database', '数据库'],
  'viewer.kind.cloud': ['Cloud', '云服务'],
  'viewer.kind.security': ['Security', '安全'],
  'viewer.kind.messagebus': ['Message bus', '消息总线'],
  'viewer.kind.external': ['External', '外部系统'],
  'viewer.kind.neutral': ['Neutral', '中性'],
  'viewer.kind.node': ['Node', '节点'],
  'viewer.kind.start': ['Start', '开始'],
  'viewer.kind.active': ['Active', '活动'],
  'viewer.kind.waiting': ['Waiting', '等待'],
  'viewer.kind.decision': ['Decision', '决策'],
  'viewer.kind.success': ['Success', '成功'],
  'viewer.kind.failure': ['Failure', '失败'],

  'viewer.toolbar.actions': ['Diagram actions', '图表操作'],
  'viewer.theme.toggle.title': ['Toggle theme (T)', '切换主题（T）'],
  'viewer.theme.toggle': ['Toggle color theme', '切换颜色主题'],
  'viewer.theme.dark': ['Dark', '深色'],
  'viewer.theme.light': ['Light', '浅色'],
  'viewer.preset.choose.title': ['Choose visual style (S cycles)', '选择视觉风格（S 循环切换）'],
  'viewer.preset.choose': ['Choose visual style', '选择视觉风格'],
  'viewer.preset.style': ['Style', '风格'],
  'viewer.preset.menu': ['Visual style', '视觉风格'],
  'viewer.preset.identity': ['Visual identity', '视觉表达'],
  'viewer.preset.cycles': ['S cycles', 'S 循环切换'],
  'viewer.preset.classic': ['Classic', '经典'],
  'viewer.preset.classic.short': ['Classic', '经典'],
  'viewer.preset.classic.hint': ['Stable technical default', '稳定的技术默认风格'],
  'viewer.preset.flow': ['Signal Flow', '信号流'],
  'viewer.preset.flow.short': ['Flow', '流动'],
  'viewer.preset.flow.hint': ['Motion-forward presentation', '突出动态流向'],
  'viewer.preset.blueprint': ['Blueprint', '蓝图'],
  'viewer.preset.blueprint.hint': ['Engineering review', '工程评审'],
  'viewer.preset.editorial': ['Editorial', '编辑风格'],
  'viewer.preset.editorial.hint': ['Publication and launch notes', '适合发布与上线说明'],
  'viewer.preset.badge.signalFlow': ['SIGNAL FLOW', '信号流'],
  'viewer.preset.badge.blueprint': ['BLUEPRINT / REV 01', '蓝图 / 修订 01'],
  'viewer.preset.badge.editorial': ['EDITORIAL / FIELD NOTE', '编辑风格 / 现场笔记'],
  'viewer.preset.badge.editorialPlate': ['ARCHIFY / PLATE 04', 'ARCHIFY / 图版 04'],
  'viewer.preset.current': ['Visual style: {style}. Choose visual style', '当前视觉风格：{style}。选择视觉风格'],
  'viewer.motion.live': ['Live', '动态'],
  'viewer.motion.still': ['Still', '静态'],
  'viewer.motion.pause': ['Pause motion', '暂停动效'],
  'viewer.motion.resume': ['Resume motion', '恢复动效'],
  'viewer.motion.reduced': ['Motion paused by reduced-motion preference', '已根据减少动态效果偏好暂停动效'],
  'viewer.motion.hidden': ['Motion paused while this page is hidden', '页面不可见时已暂停动效'],
  'viewer.motion.yielding': ['Pause motion; currently yielding to {owner}', '暂停动效；当前让位于{owner}'],
  'viewer.motion.yielding.title': ['Live preview enabled · yielding to {owner}', '动态预览已启用 · 正在让位于{owner}'],
  'viewer.owner.story': ['the guided story', '引导故事'],
  'viewer.owner.chapter': ['the active chapter', '当前章节'],
  'viewer.owner.chapterPreview': ['the chapter delta preview', '章节差异预览'],
  'viewer.owner.handoff': ['the chapter handoff', '章节交接'],
  'viewer.owner.route': ['Route Probe', '路径探测'],
  'viewer.owner.lens': ['Semantic Lens', '语义透镜'],
  'viewer.owner.relationship': ['Relationship Preview', '关系预览'],
  'viewer.owner.intent': ['Intent Trace', '意图追踪'],
  'viewer.owner.focus': ['semantic focus', '语义聚焦'],
  'viewer.owner.legend': ['legend preview', '图例预览'],
  'viewer.owner.reader': ['reader interaction', '读者交互'],
  'viewer.present.enter': ['Enter presentation stage', '进入演示模式'],
  'viewer.present.enter.title': ['Presentation stage (F)', '演示模式（F）'],
  'viewer.present.exit': ['Exit presentation stage', '退出演示模式'],
  'viewer.present.exit.title': ['Exit presentation stage (F or Escape)', '退出演示模式（F 或 Escape）'],
  'viewer.present.present': ['Present', '演示'],
  'viewer.present.exit.label': ['Exit', '退出'],

  'viewer.export.button': ['Export', '导出'],
  'viewer.export.button.title': ['Export diagram (E)', '导出图表（E）'],
  'viewer.export.diagram': ['Export diagram', '导出图表'],
  'viewer.export.menu': ['Export', '导出'],
  'viewer.export.subtitle': ['Portable, clean outputs', '便携、整洁的输出'],
  'viewer.export.share': ['Share', '分享'],
  'viewer.export.shareCard': ['Share Card', '分享卡片'],
  'viewer.export.routeShareCard': ['Route Share Card', '路径分享卡片'],
  'viewer.export.reachShareCard': ['Reach Share Card', '可达范围分享卡片'],
  'viewer.export.copyShareCard': ['Copy Share Card', '复制分享卡片'],
  'viewer.export.copyDiagram': ['Copy diagram', '复制图表'],
  'viewer.export.clipboardPng': ['PNG to clipboard', '复制 PNG 到剪贴板'],
  'viewer.export.raster': ['Raster images', '位图'],
  'viewer.export.image': ['Image', '图像'],
  'viewer.export.lossless': ['Lossless image', '无损图像'],
  'viewer.export.compact': ['Compact image', '紧凑图像'],
  'viewer.export.modern': ['Modern image', '现代图像格式'],
  'viewer.export.vectorMotion': ['Vector and motion', '矢量与动效'],
  'viewer.export.vectorMotion.heading': ['Vector & motion', '矢量与动效'],
  'viewer.export.editable': ['Editable vector', '可编辑矢量图'],
  'viewer.export.motion6s': ['6s motion', '6 秒动效'],
  'viewer.export.unsupported': ['Not supported by this browser', '当前浏览器不支持'],
  'viewer.export.clipboardUnsupported': ['Clipboard image write not supported by this browser', '当前浏览器不支持写入图片剪贴板'],
  'viewer.export.clipboardUnsupported.period': ['Clipboard image write not supported by this browser.', '当前浏览器不支持写入图片剪贴板。'],
  'viewer.export.clipboardUnsupported.short': ['Clipboard image write not supported in this browser.', '此浏览器不支持写入图片剪贴板。'],
  'viewer.export.motionUnavailable': ['Motion capture unavailable in this browser', '当前浏览器无法录制动效'],
  'viewer.export.webmUnavailable': ['WebM unavailable in this browser', '当前浏览器不支持 WebM'],
  'viewer.export.failed': ['Export failed: {message}', '导出失败：{message}'],
  'viewer.export.unknownVariant': ['Unknown Share Card variant: {variant}', '未知的分享卡片类型：{variant}'],
  'viewer.export.routeRequired': ['Trace a route before exporting a Route Share Card', '请先追踪路径，再导出路径分享卡片'],
  'viewer.export.reachRequired': ['Trace authored reach before exporting a Reach Share Card', '请先追踪编写可达范围，再导出可达范围分享卡片'],
  'viewer.export.unknown': ['unknown', '未知错误'],
  'viewer.export.routeFailed': ['Route Share Card export failed: {message}', '路径分享卡片导出失败：{message}'],
  'viewer.export.reachFailed': ['Reach Share Card export failed: {message}', '可达范围分享卡片导出失败：{message}'],
  'viewer.export.copyFailed': ['Copy failed: {message}', '复制失败：{message}'],
  'viewer.export.copiedPng': ['Copied PNG to clipboard', '已将 PNG 复制到剪贴板'],
  'viewer.export.copiedShare': ['Copied Share Card', '已复制分享卡片'],
  'viewer.export.downloadedShare': ['Downloaded Share Card', '已下载分享卡片'],
  'viewer.export.downloadedRoute': ['Downloaded Route Share Card', '已下载路径分享卡片'],
  'viewer.export.downloadedReach': ['Downloaded Reach Share Card', '已下载可达范围分享卡片'],
  'viewer.export.downloadedWebm': ['Downloaded WebM', '已下载 WebM'],
  'viewer.export.recording': ['Recording 6 seconds of motion…', '正在录制 6 秒动效…'],
  'viewer.export.card.routeSummary.one': ['Route: {source} → {target} · {count} directed hop', '路径：{source} → {target} · {count} 个有向跳转'],
  'viewer.export.card.routeSummary.other': ['Route: {source} → {target} · {count} directed hops', '路径：{source} → {target} · {count} 个有向跳转'],
  'viewer.export.card.reachSummary': ['Authored {direction} from {origin} · {nodes} · {links} · max {hops}', '从{origin}开始的编写{direction} · {nodes} · {links} · 最深 {hops}'],
  'viewer.export.card.node.one': ['{count} node', '{count} 个节点'],
  'viewer.export.card.node.other': ['{count} nodes', '{count} 个节点'],
  'viewer.export.card.link.one': ['{count} link', '{count} 条连接'],
  'viewer.export.card.link.other': ['{count} links', '{count} 条连接'],
  'viewer.export.card.hop.one': ['{count} hop', '{count} 跳'],
  'viewer.export.card.hop.other': ['{count} hops', '{count} 跳'],
  'viewer.export.card.routeBadge': ['ARCHIFY · ROUTE · {hops}', 'ARCHIFY · 路径 · {hops}'],
  'viewer.export.card.reachBadge': ['ARCHIFY · {direction} REACH', 'ARCHIFY · {direction}可达范围'],
  'viewer.export.card.defaultBadge': ['ARCHIFY · {preset} · {theme}', 'ARCHIFY · {preset} · {theme}'],
  'viewer.export.direction.upstream': ['Upstream', '上游'],
  'viewer.export.direction.downstream': ['Downstream', '下游'],
  'viewer.export.error.canvasUnavailable': ['Canvas unavailable for {label}', '无法为{label}使用画布'],
  'viewer.export.error.contextUnavailable': ['2D canvas context unavailable for {label}', '无法为{label}创建二维画布上下文'],
  'viewer.export.error.toBlobUnavailable': ['canvas.toBlob unavailable for {label}', '{label}无法使用 canvas.toBlob'],
  'viewer.export.error.toBlobNull': ['canvas.toBlob returned no data for {label}', '{label}的 canvas.toBlob 未返回数据'],
  'viewer.export.error.variantsCombined': ['Share Card variants cannot be combined', '无法同时组合多种分享卡片类型'],
  'viewer.export.error.viewerState': ['Share Card export could not remove temporary viewer state', '分享卡片导出无法移除临时 Viewer 状态'],
  'viewer.export.error.routeState': ['Route Card export could not preserve the resolved route safely', '路径卡片导出无法安全保留已解析路径'],
  'viewer.export.error.reachState': ['Reach Card export could not preserve authored reach safely', '可达范围卡片导出无法安全保留编写的可达范围'],
  'viewer.export.error.webmRequirements': ['WebM motion export requires a trace animation and browser MediaRecorder support', 'WebM 动效导出需要追踪动画及浏览器 MediaRecorder 支持'],
  'viewer.export.error.mediaRecorder': ['MediaRecorder failed', 'MediaRecorder 录制失败'],
  'viewer.export.error.emptyWebm': ['MediaRecorder produced an empty WebM', 'MediaRecorder 生成了空的 WebM'],
  'viewer.export.error.webmBackground': ['SVG background could not be loaded for WebM export', '无法为 WebM 导出加载 SVG 背景'],

  'viewer.guided.region': ['Guided diagram views', '图表引导视图'],
  'viewer.guided.previous': ['Previous guided view', '上一个引导视图'],
  'viewer.guided.previous.title': ['Previous guided view ([)', '上一个引导视图（[）'],
  'viewer.guided.next': ['Next guided view', '下一个引导视图'],
  'viewer.guided.next.title': ['Next guided view (])', '下一个引导视图（]）'],
  'viewer.guided.views': ['Guided views', '引导视图'],
  'viewer.guided.explore': ['Explore this system', '探索此系统'],
  'viewer.guided.intro': ['Step through curated paths without changing the source diagram.', '沿精选路径逐步查看，而不改变源图表。'],
  'viewer.guided.trail': ['Story trail', '故事轨迹'],
  'viewer.guided.beat': ['Beat', '节点'],
  'viewer.guided.nextBeat': ['Next', '下一步'],
  'viewer.guided.play': ['Play guided story', '播放引导故事'],
  'viewer.guided.play.title': ['Play guided story (P)', '播放引导故事（P）'],
  'viewer.guided.pause': ['Pause guided story', '暂停引导故事'],
  'viewer.guided.pause.title': ['Pause guided story (P)', '暂停引导故事（P）'],
  'viewer.guided.replay': ['Replay guided story', '重播引导故事'],
  'viewer.guided.replay.title': ['Replay guided story (P)', '重播引导故事（P）'],
  'viewer.guided.playStory': ['Play story', '播放故事'],
  'viewer.guided.pauseStory': ['Pause', '暂停'],
  'viewer.guided.replayStory': ['Replay story', '重播故事'],
  'viewer.guided.motionUnavailable': ['Story playback unavailable while motion is Still', '静态模式下无法播放故事'],
  'viewer.guided.enableMotion': ['Switch motion to Live to play the guided story', '切换为动态模式以播放引导故事'],
  'viewer.guided.selectBeatLink': ['Select a Story Beat to copy its exact link', '选择故事节点以复制其精确链接'],
  'viewer.guided.copyMoment': ['Copy moment', '复制此刻'],
  'viewer.guided.momentCopied': ['Moment link copied', '已复制时刻链接'],
  'viewer.guided.momentCopyFailed': ['Could not copy story moment link', '无法复制故事时刻链接'],
  'viewer.guided.copied': ['Copied', '已复制'],
  'viewer.guided.copyFailed': ['Copy failed', '复制失败'],
  'viewer.guided.showAll': ['Show all', '显示全部'],
  'viewer.guided.showAll.aria': ['Show entire diagram', '显示完整图表'],
  'viewer.guided.chapters': ['Story chapters', '故事章节'],
  'viewer.guided.storyTrail': ['Story trail for {label}: {count} beats', '{label}的故事轨迹：{count} 个节点'],
  'viewer.guided.chapter.open': ['Open chapter {index} of {total}: {label}, {count} stops', '打开第 {index}/{total} 章：{label}，{count} 个停靠点'],
  'viewer.guided.chapter.current': ['Current chapter {index} of {total}: {label}, {count} stops', '当前第 {index}/{total} 章：{label}，{count} 个停靠点'],
  'viewer.guided.chapter.selectedNodes': ['{count} selected nodes', '已选择 {count} 个节点'],
  'viewer.guided.chapter.stops': ['{count} stops', '{count} 个停靠点'],
  'viewer.guided.chapter.stop.one': ['{count} stop', '{count} 个停靠点'],
  'viewer.guided.chapter.stop.other': ['{count} stops', '{count} 个停靠点'],
  'viewer.guided.chapter.current.title': ['{label} — current chapter, {count} stops', '{label} — 当前章节，{count} 个停靠点'],
  'viewer.guided.chapter.delta.expanded': ['{stay} stay, {enter} enter, {leave} leave', '{stay} 个保留，{enter} 个进入，{leave} 个离开'],
  'viewer.guided.chapter.delta.aria': ['Open chapter {index} of {total}: {label}. Chapter focus delta: {delta}', '打开第 {index}/{total} 章：{label}。章节聚焦差异：{delta}'],
  'viewer.guided.chapter.delta.title': ['{label} — {delta} chapter focus', '{label} — 章节聚焦 {delta}'],
  'viewer.guided.handoff': ['{from} → {to} · via {label}', '{from} → {to} · 经由{label}'],
  'viewer.guided.share.chapter': ['Chapter {index} / {total}', '章节 {index} / {total}'],
  'viewer.guided.share.initial': ['Chapter 01 / 01', '章节 01 / 01'],
  'viewer.guided.share.default': ['Guided chapter', '引导章节'],
  'viewer.guided.state.ready': ['Ready', '就绪'],
  'viewer.guided.state.playing': ['Playing', '播放中'],
  'viewer.guided.state.settled': ['Settled', '已完成'],
  'viewer.guided.state.paused': ['Paused', '已暂停'],
  'viewer.guided.state.pinned': ['Pinned', '已固定'],
  'viewer.guided.state.still': ['Still', '静态'],
  'viewer.guided.share.step': ['Step {index} / {total} · {label}', '步骤 {index} / {total} · {label}'],
  'viewer.guided.share.staticMoment': ['{step} · Static moment', '{step} · 静态时刻'],
  'viewer.guided.share.complete': ['{count} steps complete · {note}', '{count} 个步骤已完成 · {note}'],
  'viewer.guided.share.settled': ['Path settled for reading.', '路径已稳定，可供阅读。'],
  'viewer.guided.share.staticPath': ['{count} steps · Static path', '{count} 个步骤 · 静态路径'],
  'viewer.guided.share.ready': ['{count} steps · Ready', '{count} 个步骤 · 就绪'],
  'viewer.guided.share.aria': ['{state} chapter {index} of {total}: {label}. {beat}. {route}', '{state}，第 {index}/{total} 章：{label}。{beat}。{route}'],
  'viewer.guided.beat.start': ['Beat {index} / {total} · {label} · starting point', '节点 {index} / {total} · {label} · 起点'],
  'viewer.guided.beat.forward': ['Beat {index} / {total} · {from} → {to}', '节点 {index} / {total} · {from} → {to}'],
  'viewer.guided.beat.reverse': ['Beat {index} / {total} · {from} → {to} · reverse authored link', '节点 {index} / {total} · {from} → {to} · 反向编写连接'],
  'viewer.guided.beat.multiple': ['Beat {index} / {total} · {from} ⇄ {to} · {count} authored links', '节点 {index} / {total} · {from} ⇄ {to} · {count} 条编写连接'],
  'viewer.guided.beat.group': ['Beat {index} / {total} · {from} · {to} · grouped · no direct link', '节点 {index} / {total} · {from} · {to} · 分组 · 无直接连接'],
  'viewer.guided.beat.aria.prefix': ['Story beat {index} of {total}: {label}. ', '故事节点 {index}/{total}：{label}。'],
  'viewer.guided.beat.aria.start': ['Starting point.', '起点。'],
  'viewer.guided.beat.aria.forward': ['From {from} through one authored forward relationship.', '从{from}经一条正向编写关系到达。'],
  'viewer.guided.beat.aria.reverse': ['From {from}; the authored relationship points from {to} to {from}.', '从{from}出发；编写关系实际由{to}指向{from}。'],
  'viewer.guided.beat.aria.multiple': ['From {from} through {count} authored relationships; shown without arbitrary motion.', '从{from}经 {count} 条编写关系到达；不使用任意动效。'],
  'viewer.guided.beat.aria.group': ['Grouped from {from} with no direct authored relationship.', '与{from}分组展示，没有直接编写关系。'],
  'viewer.guided.caption.start': ['Starting point', '起点'],
  'viewer.guided.caption.grouped': ['Grouped transition · no direct authored link', '分组过渡 · 无直接编写连接'],
  'viewer.guided.caption.more': [' +{count} more', ' +另外 {count} 条'],
  'viewer.guided.caption.reverse': ['Reverse authored relationship', '反向编写关系'],
  'viewer.guided.caption.relationships': ['{count} authored relationships', '{count} 条编写关系'],
  'viewer.guided.caption.relationship': ['Authored relationship', '编写关系'],
  'viewer.guided.caption.direction': ['authored direction: {from} → {to}', '编写方向：{from} → {to}'],
  'viewer.guided.caption.starting': ['Authored starting point', '编写起点'],
  'viewer.guided.beatLink': ['Copy link to current story moment: Beat {index} of {total}: {label}', '复制当前故事时刻链接：第 {index}/{total} 个节点：{label}'],
  'viewer.guided.noStory': ['This diagram has no authored guided story.', '此图表没有编写引导故事。'],

  'viewer.guide.eyebrow': ['Diagram guide', '图表指南'],
  'viewer.guide.close': ['Close diagram guide', '关闭图表指南'],
  'viewer.guide.inspecting': ['Inspecting compiled semantics', '正在检查已编译语义'],
  'viewer.guide.actions': ['Diagram exploration actions', '图表探索操作'],
  'viewer.guide.find': ['Find any node', '查找任意节点'],
  'viewer.guide.find.hint': ['Search labels, responsibilities, kinds, and stable IDs.', '搜索标签、职责、类型和稳定 ID。'],
  'viewer.guide.route': ['Trace a route', '追踪路径'],
  'viewer.guide.route.aria': ['Trace a directed route', '追踪有向路径'],
  'viewer.guide.route.hint': ['Ask how two semantic nodes connect in authored direction.', '查看两个语义节点如何按编写方向连接。'],
  'viewer.guide.map': ['See the whole system', '查看完整系统'],
  'viewer.guide.map.hint': ['Open Semantic Radar with a live viewport and stable nodes.', '打开带实时视口和稳定节点的语义雷达。'],
  'viewer.guide.lens': ['Compare semantic kinds', '比较语义类型'],
  'viewer.guide.lens.hint': ['Count roles, reveal their traffic, and compare direct authored links.', '统计角色、显示流量并比较直接编写的连接。'],
  'viewer.guide.story': ['Play the guided story', '播放引导故事'],
  'viewer.guide.story.hint': ['Walk the authored chapters and real relationships.', '浏览已编写的章节和真实关系。'],
  'viewer.guide.present': ['Enter Presentation Stage', '进入演示模式'],
  'viewer.guide.present.hint': ['Give the live diagram the viewport without changing export.', '让实时图表占满视口，同时不改变导出。'],
  'viewer.guide.shortcuts': ['Additional keyboard shortcuts', '其他键盘快捷键'],
  'viewer.guide.shortcut.export': ['Export', '导出'],
  'viewer.guide.shortcut.theme': ['Theme', '主题'],
  'viewer.guide.shortcut.style': ['Style', '风格'],
  'viewer.guide.shortcut.reset': ['Reset', '重置'],
  'viewer.guide.shortcut.zoomIn': ['Zoom in', '放大'],
  'viewer.guide.shortcut.zoomOut': ['Zoom out', '缩小'],
  'viewer.guide.shortcut.close': ['Close', '关闭'],
  'viewer.guide.facts': ['{nodes} · {relationships} · {views}', '{nodes} · {relationships} · {views}'],
  'viewer.guide.fact.node.one': ['{count} semantic node', '{count} 个语义节点'],
  'viewer.guide.fact.node.other': ['{count} semantic nodes', '{count} 个语义节点'],
  'viewer.guide.fact.relationship.one': ['{count} relationship', '{count} 条关系'],
  'viewer.guide.fact.relationship.other': ['{count} relationships', '{count} 条关系'],
  'viewer.guide.fact.view.one': ['{count} guided view', '{count} 个引导视图'],
  'viewer.guide.fact.view.other': ['{count} guided views', '{count} 个引导视图'],
  'viewer.guide.story.available.one': ['Walk {count} authored chapter and its real relationships.', '浏览 {count} 个已编写章节及其真实关系。'],
  'viewer.guide.story.available.other': ['Walk {count} authored chapters and their real relationships.', '浏览 {count} 个已编写章节及其真实关系。'],
  'viewer.guide.story.unavailable': ['No authored guided story in this diagram.', '此图表没有编写引导故事。'],
  'viewer.guide.open': ['Open diagram guide', '打开图表指南'],
  'viewer.guide.noStory': ['This diagram has no authored guided story.', '此图表没有编写引导故事。'],

  'viewer.finder.title': ['Find a node', '查找节点'],
  'viewer.finder.close': ['Close node finder', '关闭节点查找器'],
  'viewer.finder.placeholder': ['Search labels or IDs', '搜索标签或 ID'],
  'viewer.finder.search': ['Search diagram nodes', '搜索图表节点'],
  'viewer.finder.results': ['Diagram nodes', '图表节点'],
  'viewer.finder.empty': ['No matching nodes', '没有匹配的节点'],
  'viewer.finder.result.focus': ['Focus {label}', '聚焦{label}'],
  'viewer.finder.result.routeStart': ['Choose {label} as route start', '选择{label}作为路径起点'],
  'viewer.finder.result.routeTarget': ['Choose {label} as route destination, {links}', '选择{label}作为路径终点，{links}'],
  'viewer.finder.status.empty': ['No matching nodes', '没有匹配的节点'],
  'viewer.finder.status.count.one': ['{count} matching node', '{count} 个匹配节点'],
  'viewer.finder.status.count.other': ['{count} matching nodes', '{count} 个匹配节点'],
  'viewer.finder.noun.nodes': ['nodes', '个节点'],
  'viewer.finder.link.one': ['{count} link', '{count} 条连接'],
  'viewer.finder.link.other': ['{count} links', '{count} 条连接'],
  'viewer.finder.result.focus.one': ['Focus {label}, {count} related connection', '聚焦{label}，{count} 条相关连接'],
  'viewer.finder.result.focus.other': ['Focus {label}, {count} related connections', '聚焦{label}，{count} 条相关连接'],
  'viewer.finder.status.filtered': ['{visible} of {available} {noun}', '{visible}/{available} {noun}'],
  'viewer.finder.status.all': ['{available} {noun}', '{available} {noun}'],

  'viewer.passport.eyebrow': ['Semantic passport', '语义护照'],
  'viewer.passport.metadata': ['Node metadata', '节点元数据'],
  'viewer.passport.evidence': ['Verified source evidence', '已验证的源代码证据'],
  'viewer.passport.verified': ['Verified source', '已验证来源'],
  'viewer.passport.verificationScope': ['Verified against local Git at the pinned revision. Remote access has not been checked.', '已按固定修订版本验证本地 Git 证据，未检查远程访问权限。'],
  'viewer.passport.reach': ['Authored reach', '编写可达范围'],
  'viewer.passport.reach.trace': ['Trace authored reachability', '追踪编写的可达性'],
  'viewer.passport.upstream': ['Upstream', '上游'],
  'viewer.passport.downstream': ['Downstream', '下游'],
  'viewer.passport.upstream.trace': ['Trace upstream authored reachability', '追踪上游编写可达性'],
  'viewer.passport.downstream.trace': ['Trace downstream authored reachability', '追踪下游编写可达性'],
  'viewer.passport.close': ['Close semantic passport', '关闭语义护照'],
  'viewer.passport.copy': ['Copy link', '复制链接'],
  'viewer.passport.copy.focus': ['Copy link to focused node', '复制聚焦节点的链接'],
  'viewer.passport.relations': ['Relations', '关系'],
  'viewer.passport.relations.show': ['Show connected relationships', '显示关联关系'],
  'viewer.passport.relations.hide': ['Hide connected relationships', '隐藏关联关系'],
  'viewer.passport.relations.list': ['Connected relationships', '关联关系'],
  'viewer.passport.copyRelation': ['Copy relation', '复制关系'],
  'viewer.passport.copyNode': ['Copy node', '复制节点'],
  'viewer.passport.copyPinned': ['Copy link to pinned relationship', '复制固定关系的链接'],
  'viewer.passport.copySource': ['Copy link to source node', '复制来源节点的链接'],
  'viewer.passport.copy.focused.success': ['Focused node link copied', '已复制聚焦节点链接'],
  'viewer.passport.copy.pinned.success': ['Pinned relationship link copied', '已复制固定关系链接'],
  'viewer.passport.copy.focused.failed': ['Could not copy focused node link', '无法复制聚焦节点链接'],
  'viewer.passport.copy.pinned.failed': ['Could not copy pinned relationship link', '无法复制固定关系链接'],
  'viewer.passport.relationship.none': ['No connected relationships', '没有关联关系'],
  'viewer.passport.relationship.count.one': ['{count} relation', '{count} 条关系'],
  'viewer.passport.relationship.count.other': ['{count} relations', '{count} 条关系'],
  'viewer.passport.relationship.show.one': ['Show {count} connected relationship', '显示 {count} 条关联关系'],
  'viewer.passport.relationship.show.other': ['Show {count} connected relationships', '显示 {count} 条关联关系'],
  'viewer.passport.relationship.summary': ['{out} outgoing · {in} incoming{loops}', '{out} 条出向 · {in} 条入向{loops}'],
  'viewer.passport.relationship.loops': [' · {count} loop', ' · {count} 条自环'],
  'viewer.passport.relationship.explorer': ['Direct relationship explorer', '直接关系浏览器'],
  'viewer.passport.relationship.help': ['Use arrow keys to explore relationships. Press Enter or Space to pin details; Escape clears.', '使用方向键浏览关系。按 Enter 或空格键固定详情；按 Escape 清除。'],
  'viewer.passport.relationship.loopsBack': ['loops back', '回环'],
  'viewer.passport.relationship.connectsTo': ['connects to', '连接到'],
  'viewer.passport.relationship.connectsFrom': ['connects from', '连接自'],
  'viewer.passport.relationship.pinned': ['Pinned relationship · {from} → {to} · {label}', '已固定关系 · {from} → {to} · {label}'],
  'viewer.passport.relationship.inspect': ['Inspect relationship {index} of {total}: {from} to {to}, {label}. Press Enter for details.', '检查第 {index}/{total} 条关系：{from} 到 {to}，{label}。按 Enter 查看详情。'],
  'viewer.passport.relationship.group.out': ['Outgoing', '出向'],
  'viewer.passport.relationship.group.in': ['Incoming', '入向'],
  'viewer.passport.relationship.group.loop': ['Self loops', '自环'],
  'viewer.passport.relationship.row': ['{group}: {relationship}, {neighbor}', '{group}：{relationship}，{neighbor}'],
  'viewer.passport.relationship.direction.out': ['OUT →', '出 →'],
  'viewer.passport.relationship.direction.in': ['← IN', '← 入'],
  'viewer.passport.relationship.direction.loop': ['LOOP', '自环'],
  'viewer.passport.sourceCount.one': ['{count} verified source reference', '{count} 个已验证来源引用'],
  'viewer.passport.sourceCount.other': ['{count} verified source references', '{count} 个已验证来源引用'],
  'viewer.passport.sourceMarker': ['SRC', '来源'],
  'viewer.passport.beacon.one': ['{count} verified source; focus this node to inspect', '{count} 个已验证来源；聚焦此节点以检查'],
  'viewer.passport.beacon.other': ['{count} verified sources; focus this node to inspect', '{count} 个已验证来源；聚焦此节点以检查'],
  'viewer.passport.repository.open': ['Open verified repository revision {revision}', '打开已验证的仓库修订版本 {revision}'],
  'viewer.passport.source.open': ['Open verified source {path} at revision {revision}', '打开修订版本 {revision} 中已验证的来源 {path}'],
  'viewer.passport.source.openLink': ['Open ↗', '打开 ↗'],
  'viewer.passport.reach.upstream.one': ['Trace {count} upstream authored node', '追踪 {count} 个上游编写节点'],
  'viewer.passport.reach.upstream.other': ['Trace {count} upstream authored nodes', '追踪 {count} 个上游编写节点'],
  'viewer.passport.reach.downstream.one': ['Trace {count} downstream authored node', '追踪 {count} 个下游编写节点'],
  'viewer.passport.reach.downstream.other': ['Trace {count} downstream authored nodes', '追踪 {count} 个下游编写节点'],
  'viewer.passport.reach.noUpstream': ['No upstream authored nodes', '没有上游编写节点'],
  'viewer.passport.reach.noDownstream': ['No downstream authored nodes', '没有下游编写节点'],
  'viewer.passport.reach.status': ['{direction} · {nodes} nodes · {links} links · max {hops} hops', '{direction} · {nodes} 个节点 · {links} 条连接 · 最深 {hops} 跳'],

  'viewer.route.eyebrow': ['Route probe', '路径探测'],
  'viewer.route.start': ['Choose a start node', '选择起点节点'],
  'viewer.route.start.find': ['Find start', '查找起点'],
  'viewer.route.start.find.aria': ['Find a route start', '查找路径起点'],
  'viewer.route.copy': ['Copy link', '复制链接'],
  'viewer.route.copy.aria': ['Copy link to traced route', '复制已追踪路径的链接'],
  'viewer.route.clear': ['Clear', '清除'],
  'viewer.route.clear.aria': ['Clear route probe', '清除路径探测'],
  'viewer.route.traced': ['Traced route', '已追踪路径'],
  'viewer.route.pickTwo': ['Pick two semantic nodes on the diagram', '在图表中选择两个语义节点'],
  'viewer.route.pickOne': ['Pick a semantic node on the diagram', '在图表中选择一个语义节点'],
  'viewer.route.controls': ['Route journey controls', '路径旅程控制'],
  'viewer.route.previous': ['Previous route position', '上一个路径位置'],
  'viewer.route.play': ['Play route journey', '播放路径旅程'],
  'viewer.route.pause': ['Pause route journey', '暂停路径旅程'],
  'viewer.route.replay': ['Replay route journey', '重播路径旅程'],
  'viewer.route.next': ['Next route position', '下一个路径位置'],
  'viewer.route.journey': ['Journey', '旅程'],
  'viewer.route.pause.label': ['Pause', '暂停'],
  'viewer.route.replay.label': ['Replay', '重播'],
  'viewer.route.overview': ['Overview', '总览'],
  'viewer.route.overview.aria': ['Show complete route overview', '显示完整路径总览'],
  'viewer.route.instructions': ['Choose the source, then the destination. Direction matters.', '先选择来源，再选择目标；方向很重要。'],
  'viewer.route.destination': ['Choose a destination from {label}', '选择从{label}出发的目标'],
  'viewer.route.destination.find': ['Find target', '查找目标'],
  'viewer.route.destination.find.aria': ['Find a reachable route destination', '查找可达的路径目标'],
  'viewer.route.differentDestination': ['Choose a different destination', '选择其他目标'],
  'viewer.route.distinct': ['A route needs two distinct semantic nodes.', '一条路径需要两个不同的语义节点。'],
  'viewer.route.unreachable': ['No directed route to {label}', '没有通往{label}的有向路径'],
  'viewer.route.unreachable.detail': ['{target} is not reachable from {source}. Pick a highlighted destination.', '从{source}无法到达{target}。请选择高亮的目标。'],
  'viewer.route.start.instructions': ['Select the source. The next step will reveal only directed destinations.', '选择来源。下一步只会显示有向可达的目标。'],
  'viewer.route.copy.success': ['Traced route link copied', '已复制路径链接'],
  'viewer.route.copy.failed': ['Could not copy traced route link', '无法复制路径链接'],
  'viewer.route.position': ['Route position {index} of {total}: {label}', '路径位置 {index}/{total}：{label}'],
  'viewer.route.step': ['Step {index} of {total} · {phase} · {label}', '第 {index}/{total} 步 · {phase} · {label}'],
  'viewer.route.motionRequired': ['Automatic journey requires Live motion', '自动旅程需要动态模式'],
  'viewer.route.trigger.clear': ['Clear traced route', '清除已追踪路径'],
  'viewer.route.overview.status': ['{nodes} · {hops} · shortest authored route', '{nodes} · {hops} · 最短编写路径'],
  'viewer.route.overview.node.one': ['{count} node', '{count} 个节点'],
  'viewer.route.overview.node.other': ['{count} nodes', '{count} 个节点'],
  'viewer.route.overview.hop.one': ['{count} directed hop', '{count} 个有向跳转'],
  'viewer.route.overview.hop.other': ['{count} directed hops', '{count} 个有向跳转'],
  'viewer.route.phase.playing': ['Playing', '播放中'],
  'viewer.route.phase.complete': ['Complete', '已完成'],
  'viewer.route.phase.inspecting': ['Inspecting', '检查中'],
  'viewer.route.destination.count.one': ['{count} directed destination available. Pick a highlighted node.', '有 {count} 个有向目标可用。请选择高亮节点。'],
  'viewer.route.destination.count.other': ['{count} directed destinations available. Pick a highlighted node.', '有 {count} 个有向目标可用。请选择高亮节点。'],
  'viewer.route.noOutgoing': ['No outgoing route starts here. Clear and choose another source.', '此处没有可用的出向路径。请清除后选择其他来源。'],
  'viewer.route.result.title': ['{source} to {target}', '{source} 到 {target}'],
  'viewer.route.finder.source.title': ['Choose route start', '选择路径起点'],
  'viewer.route.finder.source.placeholder': ['Search route sources', '搜索路径来源'],
  'viewer.route.finder.source.empty': ['No matching route sources', '没有匹配的路径来源'],
  'viewer.route.finder.source.results': ['Nodes that can start a route', '可作为路径起点的节点'],
  'viewer.route.finder.source.noun': ['route sources', '个路径来源'],
  'viewer.route.finder.source.badge': ['start', '起点'],
  'viewer.route.finder.target.title': ['Destination from {label}', '从{label}出发的目标'],
  'viewer.route.finder.target.placeholder': ['Search reachable destinations', '搜索可达目标'],
  'viewer.route.finder.target.empty': ['No matching reachable destinations', '没有匹配的可达目标'],
  'viewer.route.finder.target.results': ['Reachable route destinations', '可达路径目标'],
  'viewer.route.finder.target.noun': ['reachable destinations', '个可达目标'],
  'viewer.route.hop.one': ['{count} hop', '{count} 跳'],
  'viewer.route.hop.other': ['{count} hops', '{count} 跳'],

  'viewer.lens.eyebrow': ['Semantic lens', '语义透镜'],
  'viewer.lens.title': ['Compare system roles', '比较系统角色'],
  'viewer.lens.close': ['Close semantic lens', '关闭语义透镜'],
  'viewer.lens.instruction': ['Choose up to two semantic kinds. One reveals its real traffic; two compare only direct authored relationships.', '最多选择两种语义类型。选择一种可显示其真实流量；选择两种只比较直接编写的关系。'],
  'viewer.lens.kinds': ['Semantic kinds', '语义类型'],
  'viewer.lens.choose': ['Choose a kind to inspect its nodes and touching relationships.', '选择一种类型以检查其节点和相连关系。'],
  'viewer.lens.copy': ['Copy link to semantic lens', '复制语义透镜链接'],
  'viewer.lens.clear': ['Clear semantic lens', '清除语义透镜'],
  'viewer.lens.open': ['Open semantic lens', '打开语义透镜'],
  'viewer.lens.openActive': ['Open active semantic lens', '打开当前语义透镜'],
  'viewer.lens.legend': ['Semantic legend', '语义图例'],
  'viewer.lens.legend.inspect.one': ['Inspect {label}, {count} node', '检查{label}，{count} 个节点'],
  'viewer.lens.legend.inspect.other': ['Inspect {label}, {count} nodes', '检查{label}，{count} 个节点'],
  'viewer.lens.kind.count.one': ['{label}, {count} node', '{label}，{count} 个节点'],
  'viewer.lens.kind.count.other': ['{label}, {count} nodes', '{label}，{count} 个节点'],
  'viewer.lens.compare.one': ['{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationship', '{first} → {second}：{forward} · {second} → {first}：{reverse} · 共 {count} 条直接关系'],
  'viewer.lens.compare.other': ['{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationships', '{first} → {second}：{forward} · {second} → {first}：{reverse} · 共 {count} 条直接关系'],
  'viewer.lens.single': ['{nodes} · {relationships} · connected peers remain visible', '{nodes} · {relationships} · 已连接节点保持可见'],
  'viewer.lens.node.one': ['{count} {label} node', '{count} 个{label}节点'],
  'viewer.lens.node.other': ['{count} {label} nodes', '{count} 个{label}节点'],
  'viewer.lens.relationship.one': ['{count} touching relationship', '{count} 条相连关系'],
  'viewer.lens.relationship.other': ['{count} touching relationships', '{count} 条相连关系'],

  'viewer.radar.title': ['Semantic radar', '语义雷达'],
  'viewer.radar.building': ['Building overview', '正在构建总览'],
  'viewer.radar.openFull': ['Open full semantic radar', '打开完整语义雷达'],
  'viewer.radar.open': ['Open radar', '打开雷达'],
  'viewer.radar.close': ['Close semantic radar', '关闭语义雷达'],
  'viewer.radar.surface': ['Diagram overview. Click a node to focus it, or use arrow keys to pan.', '图表总览。点击节点进行聚焦，或使用方向键平移。'],
  'viewer.radar.click': ['Click node', '点击节点'],
  'viewer.radar.drag': ['Drag to pan', '拖动平移'],
  'viewer.radar.space': ['Semantic radar needs more MAP space.', '语义雷达需要更多地图可见空间。'],
  'viewer.radar.nodes': ['Semantic diagram radar nodes', '语义图表雷达节点'],
  'viewer.radar.focus': ['Focus {label} from Semantic Radar', '从语义雷达聚焦{label}'],
  'viewer.radar.status': ['{count} nodes · {viewport}', '{count} 个节点 · {viewport}'],
  'viewer.radar.fullMap': ['{count} nodes · full map', '{count} 个节点 · 完整地图'],
  'viewer.radar.compacted': ['Radar compacted to avoid covering the Semantic Passport or MAP controls.', '已收紧雷达，避免遮挡语义护照或地图控件。'],
  'viewer.radar.cancelWaiting': ['Cancel semantic radar waiting for more MAP space', '取消等待更多地图空间的语义雷达'],
  'viewer.radar.needsSpace': ['Semantic radar needs more visible MAP space', '语义雷达需要更多可见地图空间'],
  'viewer.radar.viewport.full': ['full map', '完整地图'],
  'viewer.radar.viewport.width': ['{percent}% width', '宽度 {percent}%'],
  'viewer.radar.viewport.scale': ['{percent}% viewport', '视口 {percent}%'],

  'viewer.nav.controls': ['Diagram view controls', '图表视图控制'],
  'viewer.nav.route': ['Trace a directed route', '追踪有向路径'],
  'viewer.nav.route.title': ['Trace route (R)', '追踪路径（R）'],
  'viewer.nav.route.short': ['PATH', '路径'],
  'viewer.nav.radar': ['Open semantic radar', '打开语义雷达'],
  'viewer.nav.radar.title': ['Semantic radar (M)', '语义雷达（M）'],
  'viewer.nav.radar.short': ['MAP', '地图'],
  'viewer.nav.lens': ['Open semantic lens', '打开语义透镜'],
  'viewer.nav.lens.title': ['Semantic lens (L)', '语义透镜（L）'],
  'viewer.nav.lens.short': ['LENS', '透镜'],
  'viewer.nav.find': ['Find a node', '查找节点'],
  'viewer.nav.find.title': ['Find a node (/)', '查找节点（/）'],
  'viewer.nav.guide': ['Open diagram guide', '打开图表指南'],
  'viewer.nav.guide.title': ['Diagram guide (?)', '图表指南（?）'],
  'viewer.nav.zoomOut': ['Zoom out', '缩小'],
  'viewer.nav.zoomOut.title': ['Zoom out (-)', '缩小（-）'],
  'viewer.nav.reset': ['Reset diagram view', '重置图表视图'],
  'viewer.nav.reset.title': ['Reset view (0)', '重置视图（0）'],
  'viewer.nav.read': ['READ', '阅读'],
  'viewer.nav.zoomIn': ['Zoom in', '放大'],
  'viewer.nav.zoomIn.title': ['Zoom in (+)', '放大（+）'],
  'viewer.nav.camera': ['{hint}. Reset diagram view', '{hint}。重置图表视图'],
  'viewer.nav.camera.title': ['{semantic}{hint} · reset view (0)', '{semantic}{hint} · 重置视图（0）'],
  'viewer.nav.camera.semantic': ['Semantic camera active · ', '语义相机已启用 · '],
  'viewer.nav.level.map': ['MAP', '概览'],
  'viewer.nav.level.read': ['READ', '阅读'],
  'viewer.nav.level.full': ['FULL', '完整'],
  'viewer.nav.level.auto': ['AUTO', '自动'],
  'viewer.nav.detail.map': ['Zoom in to reveal relationship labels and node context', '放大以显示关系标签和节点上下文'],
  'viewer.nav.detail.read': ['Zoom in again to reveal tags and annotations', '再次放大以显示标签和注释'],
  'viewer.nav.detail.full': ['Full diagram detail', '完整图表详情'],

  'viewer.intent.summary': ['{label}. {out} outgoing, {in} incoming{loops}. {total} connections. Press Enter for details.', '{label}。{out} 条出向，{in} 条入向{loops}。共 {total} 条连接。按 Enter 查看详情。'],
  'viewer.intent.loops': [', {count} self loop', '，{count} 条自环'],

  'viewer.common.copied': ['Copied', '已复制'],
  'viewer.common.copyFailed': ['Copy failed', '复制失败'],
  'viewer.common.copyLink': ['Copy link', '复制链接'],
  'viewer.common.clear': ['Clear', '清除'],
  'viewer.common.close': ['Close', '关闭'],
};

for (const [key, messages] of Object.entries(MESSAGE_PAIRS)) {
  if (messages.length !== SUPPORTED_LOCALES.length || messages.some((message) => typeof message !== 'string')) {
    throw new Error(`Incomplete Archify i18n tuple ${JSON.stringify(key)}`);
  }
}

const CATALOGS = Object.fromEntries(SUPPORTED_LOCALES.map((locale, index) => [
  locale,
  Object.fromEntries(Object.entries(MESSAGE_PAIRS).map(([key, pair]) => [key, pair[index]])),
]));

export function resolveLocale(locale) {
  return SUPPORTED_LOCALES.includes(locale) ? locale : DEFAULT_LOCALE;
}

export function formatMessage(template, values = {}) {
  return String(template).replace(/\{([a-zA-Z0-9_]+)\}/g, (match, key) => (
    Object.hasOwn(values, key) ? String(values[key]) : match
  ));
}

export function translateMessage(locale, key, values = {}) {
  const resolved = resolveLocale(locale);
  if (!Object.hasOwn(CATALOGS[resolved], key)) {
    throw new Error(`Missing Archify i18n message ${JSON.stringify(key)} for ${resolved}`);
  }
  return formatMessage(CATALOGS[resolved][key], values);
}

export function translateCount(locale, key, count, values = {}) {
  const suffix = count === 1 ? 'one' : 'other';
  return translateMessage(locale, `${key}.${suffix}`, { ...values, count });
}

export function viewerCatalog(locale) {
  const resolved = resolveLocale(locale);
  return Object.fromEntries(Object.entries(CATALOGS[resolved]).filter(([key]) => key.startsWith('viewer.')));
}

export function localizeTemplate(template, locale) {
  return template.replace(/\{\{i18n:([a-zA-Z0-9_.-]+)\}\}/g, (_match, key) => escapeHtml(translateMessage(locale, key)));
}

export function catalogKeys() {
  return Object.keys(MESSAGE_PAIRS);
}
```

## renderers/shared/layout-report.mjs

```js
/** Serialize computed layout for dry-run / inspect (#9). */

export function componentBox(c) {
  return {
    id: c.id,
    type: c.type,
    label: c.label,
    x: Math.round(c.x),
    y: Math.round(c.y),
    width: c.width,
    height: c.height,
    ...(Number.isInteger(c.row) ? { row: c.row } : {}),
    ...(Number.isInteger(c.col) ? { col: c.col } : {}),
    ...(Array.isArray(c.pos) ? { pos: c.pos.map(Math.round) } : {}),
  };
}

export function boundaryBox(b) {
  return {
    kind: b.kind,
    label: b.label,
    x: Math.round(b.x),
    y: Math.round(b.y),
    width: Math.round(b.width),
    height: Math.round(b.height),
    wraps: b.wraps,
  };
}

export function connectionPath(conn, routed, labelAt) {
  return {
    from: conn.from,
    to: conn.to,
    label: conn.label ?? null,
    variant: conn.variant ?? 'default',
    route: conn.route ?? 'auto',
    points: routed.points.map(([x, y]) => [Math.round(x), Math.round(y)]),
    ...(labelAt ? { labelAt: labelAt.map(Math.round) } : {}),
  };
}
```

## renderers/shared/legend.mjs

```js
import { throwDiagnosticError } from './diagnostics.mjs';
import { rectsOverlap, segmentIntersectsRect } from './geometry.mjs';
import { esc, textUnits } from './utils.mjs';
import { translateMessage } from './i18n.mjs';

const DEFAULT_FONT_SIZE = 8;
const DEFAULT_ITEM_GAP = 22;
const DEFAULT_LINE_GAP = 22;
const DEFAULT_SWATCH_GAP = 8;
const TEXT_ADVANCE_EM = 0.62;
const INTERACTIVE_BADGE_ALLOWANCE = 21;

export function relationshipLegendObstacles(relations, { pointsFor, labelRectFor } = {}) {
  const obstacles = [];
  for (const [index, relation] of (Array.isArray(relations) ? relations : []).entries()) {
    const points = typeof pointsFor === 'function' ? pointsFor(relation, index) : [];
    const finitePoints = (Array.isArray(points) ? points : []).filter((point) => (
      Array.isArray(point) && point.length === 2 && point.every(Number.isFinite)
    ));
    for (let pointIndex = 0; pointIndex < finitePoints.length - 1; pointIndex += 1) {
      obstacles.push({
        kind: 'relationship-segment',
        start: finitePoints[pointIndex],
        end: finitePoints[pointIndex + 1],
      });
    }
    const labelRect = typeof labelRectFor === 'function' ? labelRectFor(relation, index) : null;
    if (labelRect && [labelRect.x, labelRect.y, labelRect.width, labelRect.height].every(Number.isFinite)) {
      obstacles.push({ kind: 'relationship-label', ...labelRect });
    }
  }
  return obstacles;
}

export function resolveLegend(config, catalog, presentKinds) {
  const mode = config?.mode || 'auto';
  if (mode === 'hidden') return [];
  const present = presentKinds instanceof Set ? presentKinds : new Set(presentKinds || []);
  const overrides = config?.entries || {};

  return catalog.flatMap((catalogEntry) => {
    const override = overrides[catalogEntry.kind] || {};
    const selectedByMode = mode === 'all' || present.has(catalogEntry.kind);
    const visible = override.visible === true || (selectedByMode && override.visible !== false);
    if (!visible) return [];
    return [{
      ...catalogEntry,
      label: override.label || catalogEntry.label,
      present: present.has(catalogEntry.kind),
      interactive: catalogEntry.interactive !== false && present.has(catalogEntry.kind),
    }];
  });
}

function measuredEntryWidth(entry, fontSize, swatchGap) {
  const swatchWidth = entry.swatchWidth ?? 14;
  return Math.ceil(
    swatchWidth
    + swatchGap
    + textUnits(entry.label) * fontSize * TEXT_ADVANCE_EM
    + (entry.interactive ? INTERACTIVE_BADGE_ALLOWANCE : 0),
  );
}

// One pure footprint calculation owns both auto-viewBox sizing and final SVG
// placement. Callers must not maintain a second approximation of legend width
// or row count; that would make generated geometry disagree with validation.
export function legendFootprint(entries, {
  width,
  fontSize = DEFAULT_FONT_SIZE,
  itemGap = DEFAULT_ITEM_GAP,
  lineGap = DEFAULT_LINE_GAP,
  swatchGap = DEFAULT_SWATCH_GAP,
} = {}) {
  if (!entries.length) {
    return { measured: [], rows: [], rowCount: 0, minWidth: 0, extraHeight: 0 };
  }
  const measured = entries.map((entry) => ({
    ...entry,
    width: measuredEntryWidth(entry, fontSize, entry.swatchGap ?? swatchGap),
  }));
  const rows = [[]];
  let cursor = 0;
  for (const entry of measured) {
    const row = rows.at(-1);
    const required = (row.length ? itemGap : 0) + entry.width;
    if (row.length && cursor + required > width) {
      rows.push([entry]);
      cursor = entry.width;
    } else {
      row.push(entry);
      cursor += required;
    }
  }
  return {
    measured,
    rows,
    rowCount: rows.length,
    minWidth: Math.max(...measured.map((entry) => entry.width)),
    extraHeight: (rows.length - 1) * lineGap,
  };
}

export function measureLegend(entries, {
  x,
  baselineY,
  width,
  fontSize = DEFAULT_FONT_SIZE,
  itemGap = DEFAULT_ITEM_GAP,
  lineGap = DEFAULT_LINE_GAP,
  swatchGap = DEFAULT_SWATCH_GAP,
  minTitleY = 0,
  obstacles = [],
  unfit = 'error',
  diagramType = 'diagram',
} = {}) {
  if (!entries.length) return { entries: [], rowCount: 0, titleY: null };
  const footprint = legendFootprint(entries, { width, fontSize, itemGap, lineGap, swatchGap });
  const tooWide = footprint.measured.find((entry) => entry.width > width);
  if (tooWide) {
    if (unfit === 'hide') return null;
    const message = `[legend/label-too-wide] ${diagramType} legend label for "${tooWide.kind}" needs ${tooWide.width}px but only ${width}px is available.`;
    throwDiagnosticError(message, [{
      code: 'legend/label-too-wide',
      severity: 'error',
      message,
      subject: { diagramType, path: `/meta/legend/entries/${tooWide.kind}/label` },
      evidence: { kind: tooWide.kind, measuredWidthPx: tooWide.width, availableWidthPx: width },
      supportedFixes: ['shorten the legend label or use a wider viewBox'],
    }]);
  }

  const titleY = baselineY - footprint.extraHeight - 20;
  const legendTopY = titleY - 10;
  if (legendTopY < minTitleY) {
    if (unfit === 'hide') return null;
    const message = `[legend/vertical-overflow] ${diagramType} legend needs ${footprint.rowCount} rows, which would start at y=${legendTopY} above the available legend band at y=${minTitleY}.`;
    throwDiagnosticError(message, [{
      code: 'legend/vertical-overflow',
      severity: 'error',
      message,
      subject: { diagramType, path: '/meta/legend' },
      evidence: { rowCount: footprint.rowCount, requiredTopY: legendTopY, availableTopY: minTitleY },
      supportedFixes: ['shorten legend labels, hide nonessential entries, or use a wider viewBox'],
    }]);
  }

  const positioned = [];
  footprint.rows.forEach((row, rowIndex) => {
    let entryX = x;
    const baseline = baselineY - (footprint.rowCount - rowIndex - 1) * lineGap;
    for (const entry of row) {
      positioned.push({ ...entry, x: entryX, baseline, row: rowIndex });
      entryX += entry.width + itemGap;
    }
  });

  const legendRects = [
    { kind: 'title', x, y: legendTopY, width: 48, height: 14 },
    ...positioned.map((entry) => ({
      kind: entry.kind,
      x: entry.x,
      y: entry.baseline - 10,
      width: entry.width,
      height: 14,
    })),
  ];
  const collision = legendRects.find((legendRect) => obstacles.some((obstacle) => (
    Array.isArray(obstacle.start) && Array.isArray(obstacle.end)
      ? segmentIntersectsRect({ start: obstacle.start, end: obstacle.end }, legendRect)
      : rectsOverlap(obstacle, legendRect)
  )));
  if (collision) {
    if (unfit === 'hide') return null;
    const message = `[legend/content-overlap] ${diagramType} legend entry "${collision.kind}" overlaps authored relationship geometry.`;
    throwDiagnosticError(message, [{
      code: 'legend/content-overlap',
      severity: 'error',
      message,
      subject: { diagramType, path: '/meta/legend' },
      evidence: { legendKind: collision.kind, legendRect: collision },
      supportedFixes: ['shorten or hide legend entries, use a wider viewBox, or move the authored relationship route/label out of the legend band'],
    }]);
  }

  return {
    entries: positioned,
    rowCount: footprint.rowCount,
    titleY,
    fontSize,
  };
}

export function renderLegend({ entries, layout, renderSwatch, locale }) {
  if (!entries.length) return '';
  const measured = measureLegend(entries, layout);
  if (!measured) return '';
  const hasInteractiveEntries = measured.entries.some((entry) => entry.interactive);
  const renderedFontSize = measured.fontSize < 8 ? measured.fontSize + 0.5 : measured.fontSize + 2;
  const rootAttributes = hasInteractiveEntries ? ' data-legend="" data-legend-bridge=""' : ' data-legend=""';
  const parts = [
    `        <g${rootAttributes}>`,
    `          <text x="${layout.x}" y="${measured.titleY}" class="t-primary" font-size="12" font-weight="650">${esc(translateMessage(locale, 'legend.title'))}</text>`,
  ];

  for (const entry of measured.entries) {
    const interactive = entry.interactive
      ? ` data-legend-kind="${esc(entry.kind)}" data-legend-label="${esc(entry.label)}"`
      : '';
    parts.push(`          <g data-legend-semantic-kind="${esc(entry.kind)}"${interactive} data-legend-x="${entry.x}" data-legend-baseline="${entry.baseline}" data-legend-width="${entry.width}">`);
    parts.push(`            ${renderSwatch(entry)}`);
    parts.push(`            <text x="${entry.x + (entry.swatchWidth ?? 14) + (entry.swatchGap ?? DEFAULT_SWATCH_GAP)}" y="${entry.baseline}" class="t-muted" font-size="${renderedFontSize}" font-weight="500">${esc(entry.label)}</text>`);
    parts.push('          </g>');
  }
  parts.push('        </g>');
  return parts.join('\n');
}
```

## renderers/shared/output-path.mjs

```js
import fs from 'node:fs';
import path from 'node:path';

const MAX_SYMLINK_DEPTH = 64;
const directorySemanticsCache = new Map();
let semanticsProbeSequence = 0;

function splitAbsolute(absolutePath) {
  const root = path.parse(absolutePath).root;
  return {
    root,
    segments: absolutePath.slice(root.length).split(path.sep).filter(Boolean),
  };
}

function canonicalize(targetPath, depth) {
  const absolutePath = path.resolve(targetPath);
  const { root, segments } = splitAbsolute(absolutePath);
  let current = root;

  for (let index = 0; index < segments.length; index += 1) {
    const candidate = path.join(current, segments[index]);
    let stat;
    try {
      stat = fs.lstatSync(candidate);
    } catch (error) {
      if (error.code === 'ENOENT' || error.code === 'ENOTDIR') {
        return path.resolve(current, ...segments.slice(index));
      }
      throw error;
    }

    if (stat.isSymbolicLink()) {
      if (depth >= MAX_SYMLINK_DEPTH) {
        const error = new Error(`Could not resolve path because a symbolic-link cycle includes "${candidate}".`);
        error.code = 'ELOOP';
        error.path = candidate;
        throw error;
      }
      const link = fs.readlinkSync(candidate);
      const linkTarget = path.isAbsolute(link) ? link : path.resolve(path.dirname(candidate), link);
      return canonicalize(path.join(linkTarget, ...segments.slice(index + 1)), depth + 1);
    }

    current = fs.realpathSync.native(candidate);
  }

  return path.normalize(current);
}

export function canonicalFuturePath(targetPath) {
  try {
    return canonicalize(targetPath, 0);
  } catch (error) {
    if (error?.code !== 'ELOOP') throw error;
    const output = path.resolve(targetPath);
    throw new OutputPathError(`Output path contains a symbolic-link cycle: "${output}".`, {
      code: 'output/symlink-cycle',
      message: 'Output path could not be resolved because it contains a symbolic-link cycle.',
      subject: { output },
      evidence: {
        systemCode: 'ELOOP',
        ...(error.path ? { cycleAt: path.resolve(error.path) } : {}),
      },
      supportedFixes: ['remove the symbolic-link cycle or choose an output path outside it'],
    });
  }
}

function hasFileIdentity(stat) {
  return stat.ino !== 0 && stat.ino !== 0n;
}

function sameFileIdentity(left, right) {
  return hasFileIdentity(left)
    && hasFileIdentity(right)
    && left.dev === right.dev
    && left.ino === right.ino;
}

function nearestExistingDirectory(targetPath) {
  let directory = path.dirname(targetPath);
  while (true) {
    try {
      const stat = fs.statSync(directory);
      if (stat.isDirectory()) {
        return {
          path: fs.realpathSync.native(directory),
          stat,
        };
      }
    } catch (error) {
      if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') return null;
    }
    const parent = path.dirname(directory);
    if (parent === directory) return null;
    directory = parent;
  }
}

function directoryIdentityKey(directory) {
  if (!hasFileIdentity(directory.stat)) return null;
  return `${directory.stat.dev}:${directory.stat.ino}`;
}

function probeNamesAlias(directoryPath, authoredName, lookupName) {
  let fileDescriptor;
  let created = false;
  let result = null;
  let cleaned = true;
  const authoredPath = path.join(directoryPath, authoredName);
  const lookupPath = path.join(directoryPath, lookupName);
  try {
    fileDescriptor = fs.openSync(authoredPath, 'wx', 0o600);
    created = true;
    fs.closeSync(fileDescriptor);
    fileDescriptor = undefined;

    let authored;
    let lookup;
    try {
      authored = fs.statSync(authoredPath);
      lookup = fs.statSync(lookupPath);
    } catch (error) {
      if (error.code === 'ENOENT') result = false;
    }
    if (authored && lookup) {
      if (sameFileIdentity(authored, lookup)) {
        result = true;
      } else {
        try {
          result = fs.realpathSync.native(authoredPath) === fs.realpathSync.native(lookupPath);
        } catch {
          result = null;
        }
      }
    }
  } catch {
    result = null;
  } finally {
    if (fileDescriptor !== undefined) {
      try {
        fs.closeSync(fileDescriptor);
      } catch {
        cleaned = false;
      }
    }
    if (created) {
      try {
        fs.unlinkSync(authoredPath);
      } catch {
        cleaned = false;
      }
    }
  }
  return cleaned ? result : null;
}

function probeDirectorySemantics(directory) {
  const cacheKey = directoryIdentityKey(directory);
  if (cacheKey && directorySemanticsCache.has(cacheKey)) {
    return directorySemanticsCache.get(cacheKey);
  }

  semanticsProbeSequence += 1;
  const suffix = `${process.pid}-${Date.now().toString(36)}-${semanticsProbeSequence}`;
  const caseAuthored = `.archify-Case-Probe-${suffix}`;
  const normalizationAuthored = `.archify-norm-\u00e9-probe-${suffix}`;
  const semantics = {
    caseInsensitive: probeNamesAlias(
      directory.path,
      caseAuthored,
      caseAuthored.toLowerCase(),
    ),
    normalizationInsensitive: probeNamesAlias(
      directory.path,
      normalizationAuthored,
      normalizationAuthored.normalize('NFD'),
    ),
  };
  if (
    cacheKey
    && semantics.caseInsensitive !== null
    && semantics.normalizationInsensitive !== null
  ) {
    directorySemanticsCache.set(cacheKey, semantics);
  }
  return semantics;
}

function sameDirectory(left, right) {
  return left.path === right.path || sameFileIdentity(left.stat, right.stat);
}

function futurePathsAlias(leftPath, rightPath) {
  const left = canonicalFuturePath(leftPath);
  const right = canonicalFuturePath(rightPath);
  if (left === right) return true;

  const leftDirectory = nearestExistingDirectory(left);
  const rightDirectory = nearestExistingDirectory(right);
  if (!leftDirectory || !rightDirectory || !sameDirectory(leftDirectory, rightDirectory)) {
    return false;
  }

  const semantics = probeDirectorySemantics(leftDirectory);
  let comparableLeft = path.relative(leftDirectory.path, left);
  let comparableRight = path.relative(rightDirectory.path, right);
  if (semantics.normalizationInsensitive !== false) {
    comparableLeft = comparableLeft.normalize('NFC');
    comparableRight = comparableRight.normalize('NFC');
  }
  if (semantics.caseInsensitive !== false) {
    comparableLeft = comparableLeft.toLowerCase();
    comparableRight = comparableRight.toLowerCase();
  }
  return comparableLeft === comparableRight;
}

export function pathsAlias(leftPath, rightPath) {
  if (futurePathsAlias(leftPath, rightPath)) return true;
  try {
    const left = fs.statSync(leftPath);
    const right = fs.statSync(rightPath);
    return sameFileIdentity(left, right);
  } catch {
    return false;
  }
}

function pathIsInside(directoryPath, targetPath) {
  const relative = path.relative(canonicalFuturePath(directoryPath), canonicalFuturePath(targetPath));
  return relative === '' || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`));
}

export class OutputPathError extends Error {
  constructor(message, diagnostic) {
    super(message);
    this.name = 'OutputPathError';
    this.archifyDiagnostics = [{
      severity: 'error',
      subject: {},
      evidence: {},
      supportedFixes: [],
      ...diagnostic,
    }];
  }
}

export function resolveOutputPath({
  requestedOutput,
  authoredOutput,
  defaultOutput,
  inputPaths = [],
  inputDescription = 'an input',
  otherOutputPaths = [],
  cwd = process.cwd(),
  requiredExtension = '.html',
}) {
  const rawOutput = requestedOutput || authoredOutput || defaultOutput;
  const source = requestedOutput ? 'cli' : (authoredOutput ? 'meta' : 'default');
  if (
    source === 'meta'
    && (path.isAbsolute(rawOutput) || path.posix.isAbsolute(rawOutput) || path.win32.isAbsolute(rawOutput))
  ) {
    throw new OutputPathError('meta.output must be a relative path.', {
      code: 'output/meta-absolute',
      message: 'meta.output must be a relative path resolved from the current working directory.',
      subject: { output: rawOutput },
      supportedFixes: ['set meta.output to a relative .html path inside the current working directory'],
    });
  }
  if (source === 'meta' && path.extname(rawOutput).toLowerCase() !== '.html') {
    throw new OutputPathError('meta.output must target an .html file.', {
      code: 'output/meta-extension',
      message: 'meta.output must target an .html file.',
      subject: { output: rawOutput },
      supportedFixes: ['change meta.output to a path ending in .html'],
    });
  }
  const outputPath = path.resolve(cwd, rawOutput);
  if (source === 'meta' && path.extname(canonicalFuturePath(outputPath)).toLowerCase() !== '.html') {
    throw new OutputPathError('meta.output must resolve to an .html file.', {
      code: 'output/meta-resolved-extension',
      message: 'meta.output must resolve to an .html file after symbolic links are followed.',
      subject: { output: rawOutput },
      supportedFixes: ['remove the symbolic-link alias or point it to an .html target inside the current working directory'],
    });
  }
  if (source === 'meta' && !pathIsInside(cwd, outputPath)) {
    throw new OutputPathError('meta.output must stay inside the current working directory.', {
      code: 'output/meta-outside-cwd',
      message: 'meta.output must stay inside the current working directory after symbolic links are resolved.',
      subject: { output: rawOutput, cwd: path.resolve(cwd) },
      supportedFixes: ['set meta.output to a relative .html path inside the current working directory'],
    });
  }

  for (const inputPath of inputPaths) {
    if (!pathsAlias(outputPath, inputPath)) continue;
    throw new OutputPathError(`Output must not replace ${inputDescription}.`, {
      code: 'output/input-alias',
      message: `Output must not replace ${inputDescription}, including through a symbolic-link or future-path alias.`,
      subject: { output: outputPath, input: path.resolve(inputPath) },
      supportedFixes: ['choose an output path that is distinct from every input path'],
    });
  }
  for (const otherOutputPath of otherOutputPaths) {
    if (!pathsAlias(outputPath, otherOutputPath)) continue;
    throw new OutputPathError('Output targets must use distinct paths.', {
      code: 'output/target-alias',
      message: 'Output targets must use distinct paths, including symbolic-link and future-path aliases.',
      subject: { output: outputPath, conflictingOutput: path.resolve(otherOutputPath) },
      supportedFixes: ['choose distinct paths for every generated output'],
    });
  }

  // Keep explicit CLI directories unrestricted, but reject mistaken file types.
  // Alias checks above retain priority when a target would overwrite an input.
  if (source === 'cli') {
    const resolvedOutput = canonicalFuturePath(outputPath);
    const authoredMatches = path.extname(rawOutput).toLowerCase() === requiredExtension;
    const resolvedMatches = path.extname(resolvedOutput).toLowerCase() === requiredExtension;
    if (!authoredMatches || !resolvedMatches) {
      const message = `CLI output must ${authoredMatches ? 'resolve to' : 'target'} a ${requiredExtension} file.`;
      throw new OutputPathError(message, {
        code: authoredMatches ? 'output/cli-resolved-extension' : 'output/cli-extension',
        message,
        subject: { output: rawOutput },
        evidence: { resolvedOutput, requiredExtension },
        supportedFixes: [`choose a path ending in ${requiredExtension} whose symbolic-link target also ends in ${requiredExtension}`],
      });
    }
  }

  return {
    outputPath,
    source,
  };
}
```

## renderers/shared/repository-evidence.mjs

```js
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { throwDiagnosticError } from './diagnostics.mjs';
import { parseRepositoryRemote, redactRepositoryRemote, repositorySourceHref } from './repository-location.mjs';

const FULL_SHA_RE = /^[a-f0-9]{40}$/i;
const CONTROL_CHARACTER_RE = /[\u0000-\u001f\u007f]/;

function evidenceFailure(code, message, { subject = {}, evidence = {}, supportedFixes = [] } = {}) {
  throwDiagnosticError(message, [{
    code,
    severity: 'error',
    message,
    subject: { surface: 'repository-evidence', ...subject },
    evidence,
    supportedFixes,
  }]);
}

function runGit(repoRoot, args) {
  const result = spawnSync('git', ['-C', repoRoot, ...args], {
    encoding: 'utf8',
    maxBuffer: 16 * 1024 * 1024,
  });
  if (result.error) evidenceFailure('repository-evidence/git-unavailable', `Could not run Git: ${result.error.message}`, {
    evidence: { reason: result.error.message },
    supportedFixes: ['install Git and ensure it is available on PATH'],
  });
  return result;
}

function gitValue(repoRoot, args, failure) {
  const result = runGit(repoRoot, args);
  if (result.status !== 0) evidenceFailure('repository-evidence/git-command', failure, {
    evidence: { gitArgs: args, exitCode: result.status },
    supportedFixes: ['use the intended local Git repository and verify its origin and revision'],
  });
  return result.stdout.trim();
}

function verifiedSourcePath(value, where) {
  const sourcePath = String(value || '');
  if (!sourcePath || sourcePath.startsWith('/') || sourcePath.includes('\\') || CONTROL_CHARACTER_RE.test(sourcePath)) {
    evidenceFailure('repository-evidence/path-invalid', `${where} must be a repo-relative POSIX path.`, {
      subject: { path: where },
      evidence: { authoredPath: sourcePath },
      supportedFixes: ['use a repository-relative path with forward slashes'],
    });
  }
  const segments = sourcePath.split('/');
  if (segments.some((segment) => !segment || segment === '.' || segment === '..') || segments[0] === '.git') {
    evidenceFailure('repository-evidence/path-escape', `${where} must stay inside the repository and may not address .git.`, {
      subject: { path: where },
      evidence: { authoredPath: sourcePath },
      supportedFixes: ['remove empty, dot, parent, or .git path segments'],
    });
  }
  return segments.join('/');
}

function sourceLineCount(content) {
  if (!content.length) return 0;
  const lines = content.split(/\r\n|\n|\r/);
  return lines.length - (/(?:\r\n|\n|\r)$/.test(content) ? 1 : 0);
}

export function hasRepositoryEvidence(diagramType, diagram) {
  if (diagramType !== 'architecture') return false;
  const components = Array.isArray(diagram?.components) ? diagram.components : [];
  return Boolean(diagram?.meta?.repository) || components.some((component) => Array.isArray(component?.sources) && component.sources.length);
}

export function verifyRepositoryEvidence(diagramType, diagram, repoRootInput) {
  if (!hasRepositoryEvidence(diagramType, diagram)) return null;
  if (diagramType !== 'architecture') evidenceFailure('repository-evidence/type-unsupported', 'Repository evidence is currently supported for architecture diagrams only.', {
    subject: { diagramType },
    supportedFixes: ['use architecture mode or remove repository evidence'],
  });

  const repository = diagram.meta?.repository;
  if (!repository) evidenceFailure('repository-evidence/repository-required', 'Repository evidence requires /meta/repository.', {
    subject: { path: '/meta/repository' },
    supportedFixes: ['add the pinned repository metadata or remove component sources'],
  });
  if (!FULL_SHA_RE.test(repository.revision || '')) {
    evidenceFailure('repository-evidence/revision-invalid', '/meta/repository/revision must be a full 40-character commit SHA.', {
      subject: { path: '/meta/repository/revision' },
      evidence: { revision: repository.revision },
      supportedFixes: ['pin one full 40-character commit SHA'],
    });
  }
  const location = parseRepositoryRemote(repository.url, { authored: true });
  if (!location) {
    evidenceFailure('repository-evidence/url-invalid', '/meta/repository/url must be a credential-free HTTP(S) or Git SSH repository address without query, fragment, or dot segments.', {
      subject: { path: '/meta/repository/url' },
      supportedFixes: ['declare the matching repository address without credentials; use link_mode: local-only for internal repositories'],
    });
  }
  const linkMode = repository.link_mode ?? 'web';
  if (!['web', 'local-only'].includes(linkMode)) evidenceFailure('repository-evidence/link-mode-invalid', 'Repository link_mode must be web or local-only.');
  if (repository.provider !== undefined && (!['github', 'gitee'].includes(repository.provider) || repository.provider !== location.provider)) {
    evidenceFailure('repository-evidence/provider-invalid', 'Repository provider must match its supported public host (github.com or gitee.com).', {
      subject: { path: '/meta/repository/provider' },
      supportedFixes: ['use the matching provider or omit provider and select link_mode: local-only'],
    });
  }
  if (linkMode === 'web' && (!location.provider || location.protocol !== 'https:' || location.endpoint !== 'standard' || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(location.path))) {
    evidenceFailure('repository-evidence/links-unsupported', 'Web source links require a canonical GitHub or Gitee HTTPS owner/repository URL.', {
      subject: { path: '/meta/repository/url' },
      supportedFixes: ['use a canonical GitHub or Gitee URL, or select link_mode: local-only to retain local verification without web links'],
    });
  }
  if (!repoRootInput) {
    evidenceFailure('repository-evidence/root-required', 'This diagram declares source evidence. Pass --repo-root <repository> so Archify can verify it before rendering.', {
      subject: { path: '/meta/repository' },
      supportedFixes: ['pass --repo-root with the matching local Git checkout'],
    });
  }

  const requestedRoot = path.resolve(repoRootInput);
  let realRoot;
  try {
    realRoot = fs.realpathSync(requestedRoot);
  } catch (error) {
    evidenceFailure('repository-evidence/root-unreadable', `Could not resolve evidence repository root "${requestedRoot}": ${error.message}`, {
      subject: { repoRoot: requestedRoot },
      evidence: { reason: error.message },
      supportedFixes: ['pass one readable local repository directory'],
    });
  }
  const gitRoot = gitValue(realRoot, ['rev-parse', '--show-toplevel'], `Evidence root "${realRoot}" is not a Git repository.`);
  if (fs.realpathSync(gitRoot) !== realRoot) {
    evidenceFailure('repository-evidence/root-not-top-level', `Evidence root must be the Git top-level directory: ${gitRoot}`, {
      subject: { repoRoot: realRoot },
      evidence: { gitTopLevel: gitRoot },
      supportedFixes: [`pass --repo-root ${gitRoot}`],
    });
  }
  const origin = gitValue(realRoot, ['remote', 'get-url', 'origin'], 'Evidence repository must have an origin remote.');
  if (parseRepositoryRemote(origin)?.identity !== location.identity) {
    const safeOrigin = redactRepositoryRemote(origin);
    evidenceFailure('repository-evidence/origin-mismatch', `Evidence repository origin ${JSON.stringify(safeOrigin)} does not match ${JSON.stringify(repository.url)}.`, {
      subject: { repoRoot: realRoot },
      evidence: { localOrigin: safeOrigin, authoredRepository: repository.url },
      supportedFixes: ['use the matching local checkout or correct the authored repository URL'],
    });
  }

  const revision = repository.revision.toLowerCase();
  const commit = runGit(realRoot, ['cat-file', '-e', `${revision}^{commit}`]);
  if (commit.status !== 0) {
    evidenceFailure('repository-evidence/revision-unavailable', `Evidence revision ${revision} is not available in the local repository.`, {
      subject: { repoRoot: realRoot },
      evidence: { revision },
      supportedFixes: ['fetch the pinned commit or pin an available full commit SHA'],
    });
  }

  const nodes = Object.create(null);
  let referenceCount = 0;
  const components = Array.isArray(diagram.components) ? diagram.components : [];
  for (const [componentIndex, component] of components.entries()) {
    if (!Array.isArray(component.sources) || component.sources.length === 0) continue;
    const verified = [];
    for (const [sourceIndex, authored] of component.sources.entries()) {
      const where = `/components/${componentIndex}/sources/${sourceIndex}/path`;
      const source = {
        path: verifiedSourcePath(authored.path, where),
        ...(authored.line ? { line: authored.line } : {}),
        ...(authored.end_line ? { endLine: authored.end_line } : {}),
        ...(authored.label ? { label: authored.label } : {}),
      };
      if (source.endLine && !source.line) {
        evidenceFailure('repository-evidence/line-required', `/components/${componentIndex}/sources/${sourceIndex}/end_line requires line.`, {
          subject: { path: `/components/${componentIndex}/sources/${sourceIndex}/end_line`, componentId: component.id },
          supportedFixes: ['add line or remove end_line'],
        });
      }
      if (source.endLine && source.endLine < source.line) {
        evidenceFailure('repository-evidence/line-range-invalid', `/components/${componentIndex}/sources/${sourceIndex}/end_line must be greater than or equal to line.`, {
          subject: { path: `/components/${componentIndex}/sources/${sourceIndex}`, componentId: component.id },
          evidence: { line: source.line, endLine: source.endLine },
          supportedFixes: ['use an end_line greater than or equal to line'],
        });
      }
      const object = `${revision}:${source.path}`;
      const type = runGit(realRoot, ['cat-file', '-t', object]);
      if (type.status !== 0 || type.stdout.trim() !== 'blob') {
        evidenceFailure('repository-evidence/file-missing', `${where} does not identify a file at revision ${revision}.`, {
          subject: { path: where, componentId: component.id },
          evidence: { sourcePath: source.path, revision },
          supportedFixes: ['use a file path that exists at the pinned revision'],
        });
      }
      if (source.line) {
        const content = runGit(realRoot, ['show', object]);
        if (content.status !== 0) evidenceFailure('repository-evidence/file-unreadable', `${where} could not be read at revision ${revision}.`, {
          subject: { path: where, componentId: component.id },
          evidence: { sourcePath: source.path, revision },
          supportedFixes: ['verify the pinned blob is readable in the local checkout'],
        });
        const lineCount = sourceLineCount(content.stdout);
        const requestedLine = source.endLine || source.line;
        if (requestedLine > lineCount) {
          evidenceFailure('repository-evidence/line-out-of-range', `/components/${componentIndex}/sources/${sourceIndex} requests line ${requestedLine}, but ${source.path} has ${lineCount} lines at revision ${revision}.`, {
            subject: { path: `/components/${componentIndex}/sources/${sourceIndex}`, componentId: component.id },
            evidence: { sourcePath: source.path, requestedLine, lineCount, revision },
            supportedFixes: ['use a line range that exists at the pinned revision'],
          });
        }
      }
      verified.push({ ...source, ...(linkMode === 'web' ? { href: repositorySourceHref(location.provider, location.url, revision, source) } : {}) });
      referenceCount += 1;
    }
    nodes[component.id] = verified;
  }
  if (referenceCount === 0) {
    evidenceFailure('repository-evidence/source-required', '/meta/repository requires at least one component source reference.', {
      subject: { path: '/meta/repository' },
      supportedFixes: ['add at least one verified component source or remove repository metadata'],
    });
  }

  return {
    schemaVersion: 1,
    verified: true,
    repository: {
      url: location.url,
      revision,
      shortRevision: revision.slice(0, 7),
      label: location.provider === 'github' ? location.path : location.url.replace(/^(?:https?:\/\/|ssh:\/\/git@|git@)/, ''),
      ...(linkMode === 'web' ? { href: `${location.url}/tree/${revision}` } : { linkMode }),
    },
    referenceCount,
    nodes,
  };
}
```

## renderers/shared/repository-location.mjs

```js
// Repository identity and forge links are independent of local Git object checks.
// This module never contacts a remote server or reads the user's SSH config.
export function parseRepositoryRemote(value, { authored = false } = {}) {
  if (typeof value !== 'string') return null;
  const raw = authored ? value : value.trim();
  if (!raw || /[\s\\\u0000-\u001f\u007f?#]/.test(raw)) return null;
  const scp = raw.match(/^git@([^/:]+):(.+)$/);
  const scpAbsolute = Boolean(scp && scp[2].startsWith('/'));
  const expanded = scp ? `ssh://git@${scp[1]}/${scp[2].replace(/^\//, '')}` : raw;
  const match = expanded.match(/^(https?|ssh):\/\/([^/]+)\/(.+)$/i);
  if (!match) return null;
  // Validate the original path before URL parsing can collapse dot segments.
  let segments;
  try {
    segments = match[3].replace(/\/$/, '').split('/');
    // Git passes SCP paths literally; percent escapes are decoded only in URIs.
    if (!scp) segments = segments.map(decodeURIComponent);
  }
  catch { return null; }
  if (segments.some((part) => !part || part === '.' || part === '..' || /[/\\\s\u0000-\u001f\u007f?#]/.test(part))) return null;
  let url;
  try { url = new URL(expanded); } catch { return null; }
  const protocol = url.protocol;
  if (protocol === 'ssh:' && (url.username !== 'git' || url.password)) return null;
  if (authored && protocol !== 'ssh:' && (url.username || url.password)) return null;
  const hostname = url.hostname.toLowerCase();
  if (!hostname) return null;
  const provider = hostname === 'github.com' ? 'github' : hostname === 'gitee.com' ? 'gitee' : null;
  const last = segments.length - 1;
  if (provider) segments[last] = segments[last].replace(provider === 'github' ? /\.git$/i : /\.git$/, '');
  if (!segments[last] || segments[last] === '.' || segments[last] === '..') return null;
  const repositoryPath = segments.join('/');
  // Only known forges map HTTPS and SSH to one repository namespace. Other
  // hosts retain transport, port and remote-relative/absolute path semantics.
  const port = url.port || (protocol === 'ssh:' ? '22' : protocol === 'https:' ? '443' : '80');
  const endpoint = provider && ((protocol === 'https:' && port === '443') || (protocol === 'ssh:' && port === '22'))
    ? 'standard' : `${protocol}${port}`;
  const pathKind = provider ? 'repository' : scp && !scpAbsolute ? 'relative' : 'absolute';
  const identityPath = provider === 'github' ? repositoryPath.toLowerCase() : repositoryPath;
  const encodedPath = segments.map(encodeURIComponent).join('/');
  const canonicalUrl = scp ? `git@${hostname}:${scpAbsolute ? '/' : ''}${repositoryPath}`
    : `${protocol}//${protocol === 'ssh:' ? 'git@' : ''}${url.host}/${encodedPath}`;
  return { identity: JSON.stringify([hostname, endpoint, pathKind, identityPath]), url: canonicalUrl, provider, protocol, path: repositoryPath, endpoint };
}

export function redactRepositoryRemote(value) {
  return String(value || '')
    .replace(/^((?:https?|ssh):\/\/)[^/]*@/i, '$1REDACTED@')
    .replace(/[?#].*$/s, '?REDACTED');
}

export function repositorySourceHref(provider, url, revision, source) {
  const encodedPath = source.path.split('/').map(encodeURIComponent).join('/');
  const end = source.endLine && source.endLine !== source.line
    ? `-${provider === 'github' ? 'L' : ''}${source.endLine}` : '';
  const fragment = source.line ? `#L${source.line}${end}` : '';
  return `${url}/blob/${revision}/${encodedPath}${fragment}`;
}
```

## renderers/shared/text-fit.mjs

```js
// Single-line node text fitting, shared by every renderer.
//
// Node text (`label`, `sublabel`, `tag`) renders as one <text> element with
// text-anchor="middle" and is never wrapped. Left unmeasured, an over-long
// value silently spills across its neighbours while validation still reports
// a clean receipt — the failure mode this module exists to close.
//
// Two halves, always used together:
//   - fittedNodeFontSize shrinks the text toward a legible minimum at render
//     time, so ordinary overruns simply get smaller instead of overlapping.
//   - minimumNodeTextWidth reports the width the text still needs once it has
//     shrunk as far as it may, so validation can reject what shrinking cannot
//     save.
//
// The geometry constants below are shared; the per-field `preferred` and
// `minimum` font sizes are not, because renderers set node text at different
// sizes (architecture sublabels are 9px, the rest are 7px).

import { textUnits } from './utils.mjs';

// widthFactor: px of advance width per text unit, per px of font size.
// horizontalPadding: total px reserved inside the box so text never touches
// the border.
export const nodeTextFit = {
  widthFactor: 0.6,
  horizontalPadding: 8,
};

// Largest font size at or below `preferred` that fits `text` inside `width`,
// floored at `minimum` — below that the text is no longer legible and the
// caller should be reporting a problem instead.
export function fittedNodeFontSize(text, width, preferred, minimum) {
  const units = Math.max(1, textUnits(text));
  const available = Math.max(1, width - nodeTextFit.horizontalPadding);
  const fitted = Math.min(preferred, available / (units * nodeTextFit.widthFactor));
  return Math.max(minimum, Math.floor(fitted * 10) / 10);
}

// Width `text` occupies at its legible minimum. Compare against
// `width - nodeTextFit.horizontalPadding` to decide whether shrink-to-fit can
// rescue it.
export function minimumNodeTextWidth(text, minimum) {
  return textUnits(text) * minimum * nodeTextFit.widthFactor;
}

// Available text width inside a box of `width`.
export function availableNodeTextWidth(width) {
  return width - nodeTextFit.horizontalPadding;
}
```

## renderers/shared/utils.mjs

```js
import {
  escapeHtml as esc,
  localizeTemplate,
  resolveLocale,
  translateMessage,
  viewerCatalog,
} from './i18n.mjs';

export { esc };

export function renderDefinitions() {
  return `        <!-- Definitions -->
        <defs>
          <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
            <polygon points="0 0, 10 3.5, 0 7" class="m-default" />
          </marker>
          <marker id="arrowhead-emphasis" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
            <polygon points="0 0, 10 3.5, 0 7" class="m-emphasis" />
          </marker>
          <marker id="arrowhead-security" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
            <polygon points="0 0, 10 3.5, 0 7" class="m-security" />
          </marker>
          <marker id="arrowhead-dashed" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
            <polygon points="0 0, 10 3.5, 0 7" class="m-dashed" />
          </marker>
          <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
            <path d="M 40 0 L 0 0 0 40" class="c-grid" stroke-width="0.5"/>
          </pattern>
        </defs>`;
}

const SIGIL_TONE = {
  frontend: 'frontend',
  start: 'frontend',
  backend: 'backend',
  active: 'backend',
  database: 'database',
  success: 'database',
  cloud: 'cloud',
  waiting: 'cloud',
  security: 'security',
  failure: 'security',
  messagebus: 'messagebus',
  external: 'external',
  neutral: 'external',
};

const SIGIL_SHAPE = {
  frontend: `<rect x="2" y="3" width="12" height="10" rx="2"/>
            <path d="M2 6.5h12"/>
            <circle cx="4.1" cy="4.8" r=".7" class="sigil-fill"/>
            <circle cx="6.3" cy="4.8" r=".7" class="sigil-fill"/>`,
  backend: `<path d="M6 3 3 8l3 5M10 3l3 5-3 5"/>`,
  database: `<ellipse cx="8" cy="4" rx="5" ry="2"/>
            <path d="M3 4v8c0 1.1 2.2 2 5 2s5-.9 5-2V4M3 8c0 1.1 2.2 2 5 2s5-.9 5-2"/>`,
  cloud: `<path d="M4.3 12.5h7.3a2.4 2.4 0 0 0 .2-4.8 4 4 0 0 0-7.5-1.3A3.1 3.1 0 0 0 4.3 12.5Z"/>`,
  security: `<path d="M8 2.2 13 4v3.5c0 3.1-1.8 5.4-5 6.5-3.2-1.1-5-3.4-5-6.5V4Z"/>
            <path d="m5.8 8 1.5 1.5 3-3"/>`,
  messagebus: `<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>
            <circle cx="5" cy="4.5" r="1" class="sigil-fill"/>
            <circle cx="10.5" cy="8" r="1" class="sigil-fill"/>
            <circle cx="7" cy="11.5" r="1" class="sigil-fill"/>`,
  external: `<rect x="2.5" y="5" width="8.5" height="8" rx="1.5"/>
            <path d="M8 2.5h5.5V8M13.5 2.5 7.5 8.5"/>`,
  start: `<circle cx="8" cy="8" r="5"/>
            <path d="m7 5.4 3.6 2.6L7 10.6Z" class="sigil-fill"/>`,
  active: `<path d="M2 8h3l1.5-3.5L9 12l1.6-4H14"/>`,
  waiting: `<path d="M4 2.5h8M4 13.5h8M5 3c0 2.8 2 3.2 3 5-1 1.8-3 2.2-3 5M11 3c0 2.8-2 3.2-3 5 1 1.8 3 2.2 3 5"/>`,
  success: `<circle cx="8" cy="8" r="5.3"/>
            <path d="m5.2 8 1.8 1.8 3.8-4"/>`,
  failure: `<circle cx="8" cy="8" r="5.3"/>
            <path d="m5.7 5.7 4.6 4.6m0-4.6-4.6 4.6"/>`,
  neutral: `<rect x="3" y="3" width="10" height="10" rx="2"/>
            <circle cx="8" cy="8" r="1.2" class="sigil-fill"/>`,
};

// A quiet, renderer-owned role stamp. It is authored SVG content rather than a
// viewer overlay, so it survives canonical export while adding no focus target,
// accessible name, layout box, or interaction state of its own.
export function renderSemanticSigil(kind, { x, y, size = 11 } = {}) {
  const normalized = Object.hasOwn(SIGIL_SHAPE, kind) ? kind : 'neutral';
  const tone = SIGIL_TONE[normalized] || 'external';
  const scale = size / 16;
  return `<g aria-hidden="true" data-semantic-sigil="${esc(normalized)}" class="semantic-sigil s-${tone}" transform="translate(${x} ${y}) scale(${scale})">
            ${SIGIL_SHAPE[normalized]}
          </g>`;
}

export function renderCards(cards) {
  const list = Array.isArray(cards) ? cards : [];
  return `    <!-- Info Cards -->
    <div class="cards">
${list.map((card) => `      <div class="card">
        <div class="card-header">
          <div class="card-dot ${esc(card.dot)}"></div>
          <h3>${esc(card.title)}</h3>
        </div>
        <ul>
${card.items.map((item) => `          <li>&bull; ${esc(item)}</li>`).join('\n')}
        </ul>
      </div>`).join('\n\n')}
    </div>`;
}

const SVG_SLOT_RE = /      <!-- ARCHIFY:SVG_SLOT_START -->[\s\S]*?      <!-- ARCHIFY:SVG_SLOT_END -->/;
const CARDS_SLOT_RE = /    <!-- ARCHIFY:CARDS_SLOT_START -->[\s\S]*?    <!-- ARCHIFY:CARDS_SLOT_END -->/;
const SUBTITLE_SLOT_RE = /^([ \t]*)<p class="subtitle">\[Subtitle description\]<\/p>[ \t]*(\r?\n)?/m;
const GUIDED_VIEWS_PLACEHOLDER = '<!-- ARCHIFY:GUIDED_VIEWS_DATA -->';
const SOURCE_EVIDENCE_PLACEHOLDER = '    <!-- ARCHIFY:SOURCE_EVIDENCE_DATA -->';
const I18N_PLACEHOLDER = '    <!-- ARCHIFY:I18N_DATA -->';

function serializeScriptJson(value) {
  return JSON.stringify(value)
    .replaceAll('<', '\\u003c')
    .replaceAll('>', '\\u003e')
    .replaceAll('&', '\\u0026');
}

const TEMPLATE_PLACEHOLDERS = [
  '<html lang="en" data-theme="dark" data-preset="[VISUAL PRESET]">',
  '<title>[PROJECT NAME] Architecture Diagram</title>',
  '<h1>[PROJECT NAME] Architecture</h1>',
  GUIDED_VIEWS_PLACEHOLDER,
];

export function applyTemplate(template, {
  title,
  subtitle,
  svg,
  cards,
  locale,
  visualPreset = 'classic',
  guidedViews = [],
  sourceEvidence = null,
}) {
  if (!SVG_SLOT_RE.test(template)) {
    throw new Error('applyTemplate: template missing ARCHIFY:SVG_SLOT sentinel');
  }
  if (!CARDS_SLOT_RE.test(template)) {
    throw new Error('applyTemplate: template missing ARCHIFY:CARDS_SLOT sentinel');
  }
  if (!SUBTITLE_SLOT_RE.test(template)) {
    throw new Error('applyTemplate: template missing subtitle placeholder');
  }
  for (const ph of TEMPLATE_PLACEHOLDERS) {
    if (!template.includes(ph)) {
      throw new Error(`applyTemplate: template missing placeholder ${JSON.stringify(ph)}`);
    }
  }
  // Keep existing custom templates compatible when evidence is not requested.
  // Silently dropping verified evidence would be misleading, so the new slot
  // becomes mandatory only for the opt-in evidence path.
  if (sourceEvidence && !template.includes(SOURCE_EVIDENCE_PLACEHOLDER)) {
    throw new Error(`applyTemplate: repository evidence requires placeholder ${JSON.stringify(SOURCE_EVIDENCE_PLACEHOLDER)}`);
  }
  // Function replacers: a literal `$&`, `$'`, `$\`` or `$$` in titles, labels,
  // or rendered SVG must not be interpreted as a replacement pattern.
  const guidedViewsJson = serializeScriptJson(guidedViews);
  const sourceEvidenceJson = serializeScriptJson(sourceEvidence);
  const resolvedLocale = resolveLocale(locale);
  const i18nJson = serializeScriptJson({ locale: resolvedLocale, messages: viewerCatalog(resolvedLocale) });
  const renderedSubtitle = typeof subtitle === 'string' && subtitle.trim()
    ? `<p class="subtitle">${esc(subtitle)}</p>`
    : '';
  const i18nData = `    <script id="archify-i18n-data" type="application/json">${i18nJson}</script>`;
  const localizedTemplate = localizeTemplate(template, resolvedLocale);
  const templateWithI18n = localizedTemplate.includes(I18N_PLACEHOLDER)
    ? localizedTemplate.replace(I18N_PLACEHOLDER, () => i18nData)
    : localizedTemplate.replace(GUIDED_VIEWS_PLACEHOLDER, () => `${i18nData}\n    ${GUIDED_VIEWS_PLACEHOLDER}`);
  return templateWithI18n
    .replace(TEMPLATE_PLACEHOLDERS[0], () => `<html lang="${esc(resolvedLocale)}" data-theme="dark" data-preset="${esc(visualPreset)}">`)
    .replace(TEMPLATE_PLACEHOLDERS[1], () => `<title>${esc(translateMessage(resolvedLocale, 'page.title', { title }))}</title>`)
    .replace(TEMPLATE_PLACEHOLDERS[2], () => `<h1>${esc(title)}</h1>`)
    .replace(SUBTITLE_SLOT_RE, (_match, indent, newline = '') => renderedSubtitle
      ? `${indent}${renderedSubtitle}${newline}`
      : '')
    .replace(SVG_SLOT_RE, () => svg)
    .replace(CARDS_SLOT_RE, () => cards)
    .replace(GUIDED_VIEWS_PLACEHOLDER, () => `<script id="archify-guided-views-data" type="application/json">${guidedViewsJson}</script>`)
    .replace(SOURCE_EVIDENCE_PLACEHOLDER, () => sourceEvidence
      ? `    <script id="archify-source-evidence-data" type="application/json">${sourceEvidenceJson}</script>`
      : '');
}

// CJK and other wide/fullwidth glyphs render at roughly twice the advance
// width of ASCII in the monospace stacks the template uses. Keep halfwidth
// forms (notably U+FF61–U+FF9F Katakana) out of this set. The explicit ranges
// also cover vertical punctuation and supplementary East Asian scripts that
// literal glyph ranges made difficult to audit.
// Code points that take two columns of advance width: East Asian Wide and
// Fullwidth per UAX #11, tracking Unicode 17.0. That takes in the BMP symbols
// carrying emoji presentation (U+2705, U+2B50, U+26A1, U+231B, ...), which
// render at the same square advance as the supplementary-plane emoji already
// listed here, and Hangul Jamo Extended-A. Two boundary calls worth naming:
// Unicode 16.0 reclassified the trigrams (U+2630-U+2637) and the monogram /
// digram symbols (U+268A-U+268F) from Neutral to Wide, so both are in; and
// Hangul Jamo Extended-A stops at U+A97C, its last assigned jamo, because
// U+A97D-U+A97F are unassigned, and unassigned code points outside the CJK
// ranges UAX #11 names default to Neutral rather than Wide. Spelled out as
// ranges because V8 has no \p{East_Asian_Width=W} property escape.
const FULLWIDTH_RE = /[\u1100-\u115F\u231A-\u231B\u2329-\u232A\u23E9-\u23EC\u23F0\u23F3\u25FD-\u25FE\u2614-\u2615\u2630-\u2637\u2648-\u2653\u267F\u268A-\u268F\u2693\u26A1\u26AA-\u26AB\u26BD-\u26BE\u26C4-\u26C5\u26CE\u26D4\u26EA\u26F2-\u26F3\u26F5\u26FA\u26FD\u2705\u270A-\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B-\u2B1C\u2B50\u2B55\u2E80-\uA4CF\uA960-\uA97C\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE6F\uFF01-\uFF60\uFFE0-\uFFE6\u{16FE0}-\u{18DFF}\u{1AFF0}-\u{1AFFF}\u{1B000}-\u{1B2FF}\u{1F000}-\u{1FAFF}\u{20000}-\u{3FFFD}]/u;

// A variation selector (U+FE00-U+FE0F) carries no advance of its own: it
// re-presents the character before it. VS15 (U+FE0E) asks for text
// presentation, which renders narrow; VS16 (U+FE0F) asks for emoji
// presentation, which renders at the square emoji advance. So a base plus a
// selector is measured from the selector, not from the base -- otherwise
// widening the emoji-presentation bases above turns U+2B50 U+FE0F from two
// units into three while the glyph on screen stays one square, and leaves
// U+2708 U+FE0F at two only because its base happens to be narrow.
//
// A selector following a base that cannot take emoji presentation is
// malformed input; measuring it wide is the safe direction here, since
// over-measuring pads a box while under-measuring spills the label out of it.
const VARIATION_SELECTOR_FIRST = 0xfe00;
const VARIATION_SELECTOR_LAST = 0xfe0f;
const VARIATION_SELECTOR_TEXT = 0xfe0e;
const VARIATION_SELECTOR_EMOJI = 0xfe0f;

export function textUnits(text) {
  const chars = Array.from(String(text ?? ''));
  let units = 0;
  for (let i = 0; i < chars.length; i += 1) {
    const codePoint = chars[i].codePointAt(0);
    if (codePoint >= VARIATION_SELECTOR_FIRST && codePoint <= VARIATION_SELECTOR_LAST) continue;
    const next = i + 1 < chars.length ? chars[i + 1].codePointAt(0) : -1;
    if (next === VARIATION_SELECTOR_EMOJI) units += 2;
    else if (next === VARIATION_SELECTOR_TEXT) units += 1;
    else units += FULLWIDTH_RE.test(chars[i]) ? 2 : 1;
  }
  return units;
}
```

## renderers/shared/validator.mjs

```js
import * as validators from './generated-validators.mjs';
import { throwDiagnosticError } from './diagnostics.mjs';

// "/nodes/3/label" reads much better as "/nodes/3 (id: "router") /label" for the
// LLM fixing the JSON; resolve the nearest enclosing element's id or label.
function annotatedPath(instancePath, data) {
  if (!instancePath) return { path: '/', identity: null };
  let node = data;
  let hint = null;
  for (const seg of instancePath.split('/').slice(1)) {
    if (node == null || typeof node !== 'object') break;
    node = node[/^\d+$/.test(seg) ? Number(seg) : seg];
    if (node && typeof node === 'object' && !Array.isArray(node)) {
      const tag = node.id ?? node.label;
      if (tag != null) hint = String(tag);
    }
  }
  return { path: instancePath, identity: hint };
}

function annotatePath(instancePath, data) {
  const annotated = annotatedPath(instancePath, data);
  return annotated.identity != null
    ? `${annotated.path} (id/label: ${JSON.stringify(annotated.identity)})`
    : annotated.path;
}

function formatErrors(errors, data) {
  return errors.map((e) => {
    const where = annotatePath(e.instancePath, data);
    const detail = e.params && Object.keys(e.params).length
      ? ' ' + JSON.stringify(e.params)
      : '';
    return `  ${where} ${e.message}${detail}`;
  }).join('\n');
}

export function validateSchema(diagramType, data) {
  const validate = validators[diagramType];
  if (!validate) {
    throw new Error(`validateSchema: unknown diagram type "${diagramType}"`);
  }
  if (!validate(data)) {
    const diagnostics = validate.errors.map((error) => {
      const annotated = annotatedPath(error.instancePath, data);
      const subject = {
        diagramType,
        path: annotated.path,
        ...(annotated.identity != null ? { identity: String(annotated.identity) } : {}),
      };
      const evidence = {
        keyword: error.keyword,
        expected: error.schema,
        ...error.params,
      };
      const supportedFixes = {
        additionalProperties: [`remove unsupported property ${JSON.stringify(error.params?.additionalProperty)}`],
        required: [`add required property ${JSON.stringify(error.params?.missingProperty)}`],
        type: [`use ${JSON.stringify(error.params?.type)} at ${annotated.path}`],
        enum: [`choose one of ${JSON.stringify(error.params?.allowedValues || [])}`],
        pattern: [`match the required pattern ${JSON.stringify(error.params?.pattern)}`],
        minimum: [`use a value ${error.params?.comparison || '>='} ${error.params?.limit}`],
        maximum: [`use a value ${error.params?.comparison || '<='} ${error.params?.limit}`],
        minItems: [`provide at least ${error.params?.limit} item(s)`],
        maxItems: [`provide at most ${error.params?.limit} item(s)`],
        minLength: [`provide at least ${error.params?.limit} character(s)`],
        maxLength: [`provide at most ${error.params?.limit} character(s)`],
      }[error.keyword] || [];
      const detail = error.params && Object.keys(error.params).length
        ? ` ${JSON.stringify(error.params)}`
        : '';
      return {
        code: `schema/${error.keyword}`,
        severity: 'error',
        message: `${annotatePath(error.instancePath, data)} ${error.message}${detail}`,
        subject,
        evidence,
        supportedFixes,
      };
    });
    throwDiagnosticError(
      `${diagramType} schema validation failed:\n${formatErrors(validate.errors, data)}`,
      diagnostics,
    );
  }
}
```

## renderers/workflow

```

```

## renderers/workflow/README.md

# Workflow Renderer

Render `diagram_type: "workflow"` JSON files into the standard Archify HTML
template.

```bash
node archify/renderers/workflow/render-workflow.mjs input.workflow.json output.html
```

The renderer validates input against `archify/schemas/workflow.schema.json`
with the bundled standalone validator. No dependency installation is required.

If `output.html` is omitted, the renderer uses `meta.output` from the JSON file
or falls back to `workflow.html` in the current working directory.

After rendering, run the artifact checker:

```bash
node archify/scripts/check-render-output.mjs output.html
```

It catches final-SVG issues that are easiest to see in a browser: non-finite
SVG values, accidental two-point diagonal arrows, and arrows crossing the
legend.

## Input

Workflow JSON files must set:

```json
{
  "schema_version": 2,
  "diagram_type": "workflow",
  "meta": {
    "title": "Agent Tool Call Workflow"
  },
  "lanes": [],
  "phases": [],
  "groups": [],
  "mainPath": [],
  "nodes": [],
  "edges": [],
  "cards": []
}
```

Use `schema_version: 2` for new workflows. Its readable layout compiler treats
every `col` as a logical rank in `0..5` and derives geometry from the measured
document. `schema_version: 1` remains the fixed legacy contract for existing
sources; valid v1 output is preserved byte-for-byte and never silently
reinterpreted as v2.

Omit `meta.viewBox` for the common v2 case so the compiler can use intrinsic
measured bounds. In v1, the omitted width remains fixed at 720 and height is
derived from lane count. A complete worked example lives at
`archify/examples/agent-tool-call.workflow.json`; its `schema_version` selects
the applicable contract.

The schema lives at:

```text
archify/schemas/workflow.schema.json
```

## Migration and layout receipt

Migrate an existing v1 source into a separate v2 file:

```bash
node archify/bin/archify.mjs migrate workflow old.json new.json --to-schema 2 --json
```

Running the command again with its schema-v2 output as the new source is an
idempotent verification pass: the destination bytes and geometry stay unchanged.

The command never overwrites the source by default. It maps absolute
`via[*][0]`, `labelAt[0]`, and `channelX` values from legacy to solved rank
space, preserves y coordinates unless a reported vertical constraint needs
author input, expands an explicit viewBox only for an unambiguous containment
repair, and writes the destination only after v2 compilation and artifact
checks pass. Ambiguous explicit pins fail without producing the destination.

Inspect the stable author-facing v2 plan with:

```bash
node archify/bin/archify.mjs validate workflow input.workflow.json --layout-json
```

The receipt reports the selected contract, measured `viewBox` and
`requiredViewBox`, solved columns, nodes, edges, labels, and causal diagnostics.
It deliberately omits solver iterations and candidate scores.

## Legend

The default legend derives component kinds from `nodes[].type`. Supported
`meta.legend.entries` keys, in stable order, are `frontend`, `backend`,
`security`, `messagebus`, `database`, `cloud`, and `external`. Labels and
visibility may be overridden through the shared legend contract; only kinds
backed by rendered nodes receive Semantic Legend controls.

## Layout contracts

### Fixed v1

| Constant | Value |
|----------|-------|
| viewBox | default `[720, auto]` — auto height = 52 + lanes×104 + (lanes−1)×20 + 124 |
| Lane frame | x 40, width 640, height 104, gap 20; first lane top at y 52 |
| Lane title strip | top 30px of each lane; node boxes must stay below it |
| Column centers (`col` 0–5) | x = 88, 220, 300, 430, 500, 625 |
| Phase headers | Optional `phases[]` render above the first lane, spanning `fromCol..toCol` |
| Lane groups | Optional `groups[]` frame parallel work or branch work inside one lane |
| Exception lanes | Set `lane.variant: "exception"` for retry, denial, fallback, or failure paths |
| Main path lint | Optional `mainPath[]` checks that happy-path steps have matching edges and do not move backward |
| Default node | 92×52 (height 68 when `tag` is set) |
| Node spacing | ≥8px between nodes in the same lane |
| Edge length | straight segments must span ≥28px |
| Legend row | y = lane bottom + 44; viewBox height must be ≥ legend y + 18 |

Column-center gaps are 132 / 80 / 130 / 70 / 125 px: columns 1↔2 (80px) and
3↔4 (70px) cannot both hold default-width 92px nodes in the same lane. Such an
invalid v1 source receives one causal `workflow/column-capacity` diagnostic and
a verified migration-to-v2 repair; v1 never falls through to adaptive layout.

### Readable v2

| Invariant | Contract |
|----------|----------|
| Logical columns | `col` is an integer in `0..5`; pixel centers are measured output |
| Adjacent-rank baseline | 120px center distance before document-specific constraints |
| Same-lane node clearance | ≥8px when vertical node intervals overlap |
| Facing direct edge | clear gap ≥`max(28px, measured label mask width + 8px)` |
| Automatic route rhythm | direct segment ≥28px; endpoint stub ≥8px; interior turn segment ≥16px |
| Implicit viewBox | intrinsic content bounds plus contract padding |
| Explicit viewBox | containment capacity; too-small input reports exact `requiredViewBox` and contributors |

The compiler applies constraints only to actual related or overlapping
same-lane nodes, so a wide node in an unrelated lane does not expand every
rank. Legacy centers are a soft preference after correctness constraints, not
a geometry promise. Phase and group frames derive from the solved rank bands.
Automatic routes are normalized once and the same final scene drives
validation and SVG serialization. Long automatic labels compare direct-gutter
growth with a legal channel instead of widening every downstream rank. Measured
multi-row legends participate in intrinsic height and explicit viewBox
capacity.

Authored `via`, `labelAt`, `channelX`, and `channelY` are absolute hard pins in
v2; an infeasible pin returns `workflow/explicit-pin-conflict` rather than being
silently moved. `fromSide` and `toSide` remain direction constraints. A route
preset restricts the automatic candidate family but is not itself an absolute
coordinate pin. When either endpoint side is omitted, the v2 compiler chooses
a feasible side; an authored side restricts that endpoint to the named port.

## Design Rules

- Use lanes for ownership or runtime boundaries.
- Use phase headers for high-level story beats such as Intake, Plan, Execute, and Report.
- Use groups for parallel checks, branch handling, or bounded work within a lane; every group must contain at least one node.
- Use `lane.variant: "exception"` for human wait, denial, retry, fallback, and failure lanes instead of mixing those paths into the happy path.
- Set `mainPath` when the diagram has a clear happy path; the renderer validates that consecutive ids have matching edges and move left-to-right.
- Place nodes with lane IDs and `col` indexes in `0..5`, not raw SVG coordinates.
- Preserve semantic edge labels. Readable v2 allocates measured label clearance;
  when a label does not fit, repair the reported capacity or route constraint
  instead of deleting meaning.
- Use labels for decisions, approvals, protocols, async traces, return paths,
  and any other relationship meaning not fully implied by its endpoints.
- Prefer route presets — `drop` (bend between lanes; `bias` 0–1 picks where),
  `outside-right`, `return-left`, `bottom-channel`, and `up-channel` — before
  using raw `via` points. `straight` and the default `auto` cover the rest.
- Keep workflow examples compact enough to render well in narrow chat/browser
  previews.

### Optional semantic checks

Layout validation cannot infer domain truth from labels or cards. When source
evidence establishes roots, terminals, mandatory direct relationships, or
mandatory directed reachability, encode those facts in `semanticChecks`:

```json
"semanticChecks": {
  "allowedRoots": ["request", "resource_catalog"],
  "allowedTerminals": ["reply", "audit_log"],
  "requiredEdges": [
    { "from": "dispatch", "to": "dispatch_ledger" }
  ],
  "requiredPaths": [
    { "from": "event_ledger", "to": "runtime_host" }
  ]
}
```

When `allowedRoots` or `allowedTerminals` is present, it is the complete allow
list for zero-incoming or zero-outgoing nodes respectively. `requiredEdges`
requires one exact authored direction; `requiredPaths` permits intermediate
nodes but follows authored edge direction. These checks run before layout, do
not alter SVG or receipt bytes, and must not be weakened merely to resolve a
route or composition diagnostic. Omit fields whose domain facts are unknown.

Schema violations exit non-zero with path-prefixed messages annotated with the
element's id or label. The renderer additionally fails when it can detect
layout problems, including node overlap, nodes outside their lanes, invalid
phase/group column ranges, empty groups, broken `mainPath` steps, unknown edge
targets, labels colliding with nodes or other labels, labels wider than their
node, legends outside the viewBox, or straight arrows that are too short to
read cleanly. The shared Clean Flow Gate also rejects edges crossing unrelated
nodes with 2px clearance; lanes, phases, and groups remain intentional
pass-through containers. Text width is estimated CJK-aware: fullwidth glyphs
count as two units.

Diagnostics are causal: a rank-capacity failure suppresses derivative short
edge, endpoint-direction, and label-overlap findings. Every
`supportedFixes[]` entry is verified by replanning the proposed edit, and a
diagnostic never proposes removing a semantic label when label presence does
not cause the failed invariant.

Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper
X crossings then fail with `composition/proper-crossing`; default `standard`
keeps them as artifact-receipt warnings. Collinear lane corridors are outside
the proper-X rule, but a separate gate warns in `standard` and fails in
`showcase` when unrelated edges overlap for at least 8px. Shared semantic
endpoints, point touches, and shorter overlaps remain valid. Showcase also
rejects any route segment below 8px and any interior turn segment below 16px;
ordinary 8–15px endpoint stubs remain valid for fixed lane gaps.

## renderers/workflow/render-workflow.mjs

```js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadDiagramWithBrandMarks, writeDiagram } from '../shared/cli.mjs';
import { throwDiagnosticError } from '../shared/diagnostics.mjs';
import { compileWorkflow } from './workflow-compiler.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const { diagram: workflow, template, outPath } = await loadDiagramWithBrandMarks({
  rendererDir: __dirname,
  diagramType: 'workflow',
  defaultExample: 'agent-tool-call.workflow.json'
});

const compiled = compileWorkflow({
  workflow,
  qualityProfile: process.env.ARCHIFY_QUALITY_PROFILE || workflow.meta?.quality_profile,
});

const layoutJson = process.argv.includes('--layout-json');

if (layoutJson) {
  process.stdout.write(`${JSON.stringify(compiled.receipt, null, 2)}\n`);
  if (!compiled.ok) process.exitCode = 1;
} else if (!compiled.ok) {
  throwDiagnosticError(compiled.error || 'Workflow compilation failed.', compiled.diagnostics);
} else {
  writeDiagram({
    outPath,
    template,
    diagramType: 'workflow',
    meta: workflow.meta,
    svg: compiled.svg,
    cards: workflow.cards,
  });
}
```

## renderers/workflow/workflow-compiler.mjs

```js
import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs';
import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs';
import {
  throwDiagnosticError,
  throwDiagnosticProblems,
  withDiagnosticRecordingSuppressed,
} from '../shared/diagnostics.mjs';
import { validateSchema } from '../shared/validator.mjs';
import {
  legendFootprint,
  measureLegend,
  relationshipLegendObstacles,
  resolveLegend,
  renderLegend as renderResolvedLegend,
} from '../shared/legend.mjs';
import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs';
import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs';
import { translateMessage as i18nText } from '../shared/i18n.mjs';
import {
  createMappedWorkflowCandidate,
  intrinsicWorkflow,
  planningWorkflow,
} from './workflow-migration-geometry.mjs';
import {
  asArray,
  isFinitePoint,
  rectsOverlap,
  segmentIntersectsRect,
  segmentRectClearance,
  cleanEndpointSideProblems,
  cleanFlowProblems,
  cleanCrossingProblems,
  cleanAmbiguousCorridorProblems,
  cleanBorderRunProblems,
  cleanRouteRhythmProblems,
  cleanLabelRouteClearanceProblems,
  collectAmbiguousCorridors,
  collectLabelRouteClearance,
  collectBorderRuns,
  forwardCollinearAnalysisSegments,
  sourceSegmentIndexAtPoint,
  suggestLabelObstacleFix,
  suggestLabelPairFix,
  anchor,
  automaticPortSpread,
  defaultFromSide,
  defaultToSide,
  chosenSide,
  normalizeRoutePoints,
  routeHonorsEndpointSides,
  polylinePath,
  routePointsValue,
  labelPoint,
  componentFill,
  componentText,
  arrowClassMap,
  variantAccent
} from '../shared/geometry.mjs';

const LEGACY_COLUMN_CENTERS = Object.freeze([88, 220, 300, 430, 500, 625]);
const READABLE_CANDIDATE_COST_PRIORITY = Object.freeze([
  'automaticForwardReversePx',
  'properCrossingCount',
  'sharedCorridorPx',
  'labelRouteClearanceDeficit',
  'interiorPreferred28Deficit',
  'bendCount',
  'stretchMilli',
  'canvasGrowthPx',
  'portDisplacementMilli',
  'legacyCoordinateDisplacement',
  'stableCandidateOrdinal',
]);
const MAX_READABLE_LAYOUT_FEEDBACK_ROUNDS = 3;
const GROUP_FRAME_TOP_INSET = 8;
const GROUP_FRAME_BOTTOM_INSET = 4;
const GROUP_LABEL_BASELINE_OFFSET = -2;
const GROUP_LABEL_MASK_ASCENT = 10;
const GROUP_LABEL_MASK_H = 14;
const GROUP_NODE_INSET = 4;

class WorkflowLayoutFeedback extends Error {
  constructor(request) {
    super(`Workflow layout requires ${request.kind} feedback.`);
    this.name = 'WorkflowLayoutFeedback';
    this.request = request;
  }
}

function createLegacyLayout() {
  return {
    contract: 'fixed-v1',
    laneX: 40,
    laneY: 52,
    laneW: 640,
    laneH: 104,
    laneGap: 20,
    laneTitleH: 30,
    colXs: [...LEGACY_COLUMN_CENTERS],
    nodeW: 92,
    nodeH: 52,
    defaultViewBoxWidth: 720,
  };
}

function authoredNodeWidth(node) {
  return Number.isFinite(node?.width) ? node.width : 92;
}

function nodeWidthContributor(node) {
  return `node ${node.id} width ${authoredNodeWidth(node)}px`;
}

function authoredNodeHeight(node) {
  if (Number.isFinite(node?.height)) return node.height;
  return node?.tag ? 68 : 52;
}

function workflowLabelWidth(label) {
  return Math.max(30, textUnits(label) * 4.8 + 10);
}

function readableGroupBounds(workflow, group, colXs) {
  if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)
    || group.fromCol < 0 || group.fromCol > group.toCol || group.toCol >= colXs.length) {
    return { x: 0, width: 0, cx: 0 };
  }
  const start = colXs[group.fromCol] - 50;
  const end = colXs[group.toCol] + 50;
  const naturalWidth = end - start;
  const minimumWidth = textUnits(group.label) * 5.6 + 20;
  let width = Math.max(naturalWidth, minimumWidth);
  let left = group.fromCol === group.toCol && width > naturalWidth
    ? start
    : (start + end - width) / 2;
  let right = left + width;
  for (const node of asArray(workflow.nodes)) {
    if (node.lane !== group.lane
      || !Number.isInteger(node.col)
      || node.col < group.fromCol
      || node.col > group.toCol
      || node.col < 0
      || node.col >= colXs.length) continue;
    const halfWidth = authoredNodeWidth(node) / 2;
    left = Math.min(left, colXs[node.col] - halfWidth - GROUP_NODE_INSET);
    right = Math.max(right, colXs[node.col] + halfWidth + GROUP_NODE_INSET);
  }
  width = right - left;
  return { x: left, width, cx: left + width / 2 };
}

function verticalIntervalsOverlap(a, b, clearance = 0) {
  const aCenter = Number(a?.yOffset) || 0;
  const bCenter = Number(b?.yOffset) || 0;
  return Math.abs(aCenter - bCenter)
    < authoredNodeHeight(a) / 2 + authoredNodeHeight(b) / 2 + clearance;
}

function createReadableLayout(workflow, layoutFeedback = {}) {
  const columnCount = 6;
  const baselinePitch = 120;
  const columnStart = 94;
  const maxLayoutIterations = 3;
  const channelDetourBudgetPx = 4 * 28;
  const constraints = [];
  const feedbackConstraints = [];
  const channelLabelEdgeKeys = new Set();
  const widthContributors = new Set();
  const heightContributors = new Set();
  const nodes = asArray(workflow.nodes);
  const nodesById = new Map(nodes.map((node) => [node.id, node]));

  for (let col = 0; col < columnCount - 1; col += 1) {
    constraints.push({ from: col, to: col + 1, minimum: baselinePitch });
  }
  for (const [key, minimum] of Object.entries(layoutFeedback.rankGapMinimums || {}).sort()) {
    const [from, to] = key.split(':').map(Number);
    constraints.push({
      from,
      to,
      minimum,
      contributors: layoutFeedback.rankGapContributors?.[key]
        || [`rank ${from}→${to} route clearance`],
    });
  }

  for (let leftIndex = 0; leftIndex < nodes.length; leftIndex += 1) {
    for (let rightIndex = leftIndex + 1; rightIndex < nodes.length; rightIndex += 1) {
      const leftNode = nodes[leftIndex];
      const rightNode = nodes[rightIndex];
      if (leftNode.lane !== rightNode.lane || leftNode.col === rightNode.col) continue;
      if (!verticalIntervalsOverlap(leftNode, rightNode, 8)) continue;
      const fromNode = leftNode.col < rightNode.col ? leftNode : rightNode;
      const toNode = fromNode === leftNode ? rightNode : leftNode;
      constraints.push({
        from: fromNode.col,
        to: toNode.col,
        minimum: authoredNodeWidth(fromNode) / 2 + 8 + authoredNodeWidth(toNode) / 2,
        contributors: [
          `rank ${fromNode.col}→${toNode.col} node width clearance`,
          nodeWidthContributor(fromNode),
          nodeWidthContributor(toNode),
        ],
      });
    }
  }

  for (const edge of asArray(workflow.edges)) {
    const fromNode = nodesById.get(edge.from);
    const toNode = nodesById.get(edge.to);
    if (!fromNode || !toNode || fromNode.lane !== toNode.lane || fromNode.col === toNode.col) continue;
    if (edge.via || edge.channelX !== undefined || edge.channelY !== undefined
      || !['auto', 'straight'].includes(edge.route || 'auto')) continue;
    if ((Number(fromNode.yOffset) || 0) !== (Number(toNode.yOffset) || 0)) continue;
    const earlier = fromNode.col < toNode.col ? fromNode : toNode;
    const later = earlier === fromNode ? toNode : fromNode;
    const labeledDirectClearance = edge.label && !edge.labelAt
      ? Math.max(28, workflowLabelWidth(edge.label) + 8)
      : 28;
    const directLabelExpansionCost = Math.max(0, labeledDirectClearance - 28);
    const canUseAutomaticLabelChannel = edge.label
      && !edge.labelAt
      && (edge.route || 'auto') === 'auto'
      && !edge.fromSide
      && !edge.toSide
      && edge.channelX === undefined
      && edge.channelY === undefined;
    const preferLabelChannel = canUseAutomaticLabelChannel
      && directLabelExpansionCost > channelDetourBudgetPx;
    if (preferLabelChannel) channelLabelEdgeKeys.add(stableValueKey(edge));
    constraints.push({
      from: earlier.col,
      to: later.col,
      minimum: authoredNodeWidth(earlier) / 2 + 28 + authoredNodeWidth(later) / 2,
      contributors: [
        `rank ${earlier.col}→${later.col} direct clearance`,
        `rank ${earlier.col}→${later.col} node width clearance`,
        nodeWidthContributor(earlier),
        nodeWidthContributor(later),
      ],
    });
    if (!preferLabelChannel && labeledDirectClearance > 28) {
      const labelConstraintMinimum = authoredNodeWidth(earlier) / 2
        + labeledDirectClearance
        + authoredNodeWidth(later) / 2;
      feedbackConstraints.push({
        from: earlier.col,
        to: later.col,
        minimum: labelConstraintMinimum,
        contributors: [
          `rank ${earlier.col}→${later.col} direct clearance`,
          `edge ${workflowEdgeName(edge)} label mask`,
          nodeWidthContributor(earlier),
          nodeWidthContributor(later),
        ],
      });
    }
  }

  for (const phase of asArray(workflow.phases)) {
    if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol)
      || phase.fromCol < 0 || phase.fromCol > phase.toCol || phase.toCol >= columnCount) continue;
    const minimumWidth = textUnits(phase.label) * 5.6 + 8;
    if (phase.fromCol === phase.toCol) {
      if (phase.toCol < columnCount - 1) {
        constraints.push({
          from: phase.toCol,
          to: phase.toCol + 1,
          minimum: baselinePitch + Math.max(0, minimumWidth - 92),
          contributors: [`phase ${phase.id || phase.label} label span`],
        });
      }
      continue;
    }
    constraints.push({
      from: phase.fromCol,
      to: phase.toCol,
      minimum: Math.max(0, minimumWidth - 92),
      contributors: [`phase ${phase.id || phase.label} label span`],
    });
  }

  for (const group of asArray(workflow.groups)) {
    if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)
      || group.fromCol < 0 || group.fromCol > group.toCol || group.toCol >= columnCount) continue;
    const minimumWidth = textUnits(group.label) * 5.6 + 20;
    if (group.fromCol === group.toCol) {
      if (group.toCol < columnCount - 1) {
        constraints.push({
          from: group.toCol,
          to: group.toCol + 1,
          minimum: baselinePitch + Math.max(0, minimumWidth - 100),
          contributors: [`group ${group.id || group.label} label span`],
        });
      }
      continue;
    }
    constraints.push({
      from: group.fromCol,
      to: group.toCol,
      minimum: Math.max(0, minimumWidth - 100),
      contributors: [`group ${group.id || group.label} label span`],
    });
  }

  let activeConstraints = [...constraints];
  let colXs;
  let colProvenance;
  for (let iteration = 0; iteration < maxLayoutIterations; iteration += 1) {
    colXs = Array.from({ length: columnCount }, (_, col) => columnStart + col * baselinePitch);
    colProvenance = Array.from({ length: columnCount }, () => new Set());
    const orderedConstraints = activeConstraints
      .filter(({ from, to, minimum }) => (
        Number.isInteger(from) && Number.isInteger(to)
        && from >= 0 && from < to && to < columnCount
        && Number.isFinite(minimum)
      ))
      .sort((a, b) => a.to - b.to || a.from - b.from || a.minimum - b.minimum);
    for (let to = 1; to < columnCount; to += 1) {
      for (const constraint of orderedConstraints) {
        if (constraint.to !== to) continue;
        const candidate = colXs[constraint.from] + constraint.minimum;
        const candidateProvenance = new Set([
          ...colProvenance[constraint.from],
          ...asArray(constraint.contributors),
        ]);
        if (candidate > colXs[to] + 0.0001) {
          colXs[to] = candidate;
          colProvenance[to] = candidateProvenance;
        } else if (Math.abs(candidate - colXs[to]) <= 0.0001
          && candidate > columnStart + to * baselinePitch + 0.0001) {
          for (const contributor of candidateProvenance) colProvenance[to].add(contributor);
        }
      }
    }
    if (iteration > 0 || !feedbackConstraints.length) break;
    activeConstraints = [...activeConstraints, ...feedbackConstraints];
  }

  const firstRankNodes = nodes.filter((node) => node.col === 0);
  const firstExtent = firstRankNodes.reduce(
    (maximum, node) => Math.max(maximum, authoredNodeWidth(node) / 2),
    46,
  );
  const leftInset = 8;
  const leftShift = Math.max(0, 40 + leftInset + firstExtent - colXs[0]);
  if (leftShift) {
    for (let col = 0; col < colXs.length; col += 1) colXs[col] += leftShift;
    for (const node of firstRankNodes) {
      if (Math.abs(authoredNodeWidth(node) / 2 - firstExtent) > 0.0001) continue;
      for (const provenance of colProvenance) provenance.add(nodeWidthContributor(node));
    }
  }

  const unpinnedTopEndpointIds = new Set();
  for (const edge of asArray(workflow.edges)) {
    const preservesHorizontalPins = Array.isArray(edge.via) || edge.channelX !== undefined;
    if (preservesHorizontalPins) continue;
    if (edge.fromSide === 'top') unpinnedTopEndpointIds.add(edge.from);
    if (edge.toSide === 'top') unpinnedTopEndpointIds.add(edge.to);
  }
  const laneOrder = new Map(asArray(workflow.lanes).map((lane, index) => [lane.id, index]));
  let laneHeaderShift = 0;
  const laneHeaderShiftContributors = new Set();
  for (const nodeId of unpinnedTopEndpointIds) {
    const node = nodesById.get(nodeId);
    if (!node || !Number.isInteger(node.col) || node.col < 0 || node.col >= columnCount) continue;
    const lanePosition = laneOrder.get(node.lane);
    const lane = asArray(workflow.lanes)[lanePosition];
    if (!lane) continue;
    const prefix = lane.variant === 'exception'
      ? 'EX'
      : String(lanePosition + 1).padStart(2, '0');
    const laneHeaderRight = 40 + 14 + textUnits(`${prefix} / ${lane.label}`) * 6.2;
    const requiredShift = laneHeaderRight + 2 - colXs[node.col];
    if (requiredShift > laneHeaderShift + 0.0001) {
      laneHeaderShift = requiredShift;
      laneHeaderShiftContributors.clear();
      laneHeaderShiftContributors.add(`lane ${lane.id} label width`);
    } else if (requiredShift > 0 && Math.abs(requiredShift - laneHeaderShift) <= 0.0001) {
      laneHeaderShiftContributors.add(`lane ${lane.id} label width`);
    }
  }
  if (laneHeaderShift > 0) {
    for (let col = 0; col < colXs.length; col += 1) colXs[col] += laneHeaderShift;
    for (const provenance of colProvenance) {
      for (const contributor of laneHeaderShiftContributors) provenance.add(contributor);
    }
  }

  let measuredContentLeftShift = asArray(workflow.edges).reduce((maximum, edge) => {
    if (!channelLabelEdgeKeys.has(stableValueKey(edge))) return maximum;
    const fromNode = nodesById.get(edge.from);
    const toNode = nodesById.get(edge.to);
    if (!fromNode || !toNode) return maximum;
    const labelCenter = (colXs[fromNode.col] + colXs[toNode.col]) / 2;
    const labelLeft = labelCenter - workflowLabelWidth(edge.label) / 2;
    return Math.max(maximum, 16 - labelLeft);
  }, 0);
  for (const phase of asArray(workflow.phases)) {
    if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol)) continue;
    const width = Math.max(
      colXs[phase.toCol] - colXs[phase.fromCol] + 92,
      textUnits(phase.label) * 5.6 + 8,
    );
    const left = phase.fromCol === phase.toCol
      ? colXs[phase.fromCol] - 46
      : (colXs[phase.fromCol] + colXs[phase.toCol] - width) / 2;
    measuredContentLeftShift = Math.max(measuredContentLeftShift, 16 - left);
  }
  for (const group of asArray(workflow.groups)) {
    if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)) continue;
    const bounds = readableGroupBounds(workflow, group, colXs);
    measuredContentLeftShift = Math.max(measuredContentLeftShift, 44 - bounds.x);
  }
  if (measuredContentLeftShift > 0) {
    for (let col = 0; col < colXs.length; col += 1) colXs[col] += measuredContentLeftShift;
  }

  let rightmost = colXs.at(-1) + 50;
  let rightmostContributors = new Set(colProvenance.at(-1));
  for (const node of nodes) {
    if (!Number.isInteger(node.col) || node.col < 0 || node.col >= columnCount) continue;
    const nodeRight = colXs[node.col] + authoredNodeWidth(node) / 2;
    const nodeContributors = new Set([
      ...colProvenance[node.col],
      nodeWidthContributor(node),
    ]);
    if (nodeRight > rightmost + 0.0001) {
      rightmost = nodeRight;
      rightmostContributors = nodeContributors;
    } else if (Math.abs(nodeRight - rightmost) <= 0.0001) {
      for (const contributor of nodeContributors) rightmostContributors.add(contributor);
    }
  }
  for (const group of asArray(workflow.groups)) {
    if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)) continue;
    const bounds = readableGroupBounds(workflow, group, colXs);
    const groupRight = bounds.x + bounds.width;
    const groupContributors = new Set([
      ...colProvenance[group.fromCol],
      ...colProvenance[group.toCol],
      `group ${group.id || group.label} label span`,
      ...nodes
        .filter((node) => node.lane === group.lane
          && node.col >= group.fromCol && node.col <= group.toCol)
        .map(nodeWidthContributor),
    ]);
    if (groupRight > rightmost + 0.0001) {
      rightmost = groupRight;
      rightmostContributors = groupContributors;
    } else if (Math.abs(groupRight - rightmost) <= 0.0001) {
      for (const contributor of groupContributors) rightmostContributors.add(contributor);
    }
  }
  const widestLaneLabel = asArray(workflow.lanes).reduce((widest, lane, index) => {
    const width = textUnits(`${String(index + 1).padStart(2, '0')} / ${lane.label}`) * 6.2 + 30;
    return width > widest.width ? { width, lane } : widest;
  }, { width: 0, lane: null });
  const laneLabelWidth = widestLaneLabel.width;
  const rightmostLaneWidth = Math.ceil(rightmost - 40 + 8);
  const laneW = Math.max(
    640,
    rightmostLaneWidth,
    Math.ceil(laneLabelWidth),
  );
  if (laneW > 640) {
    if (rightmostLaneWidth === laneW) {
      for (const contributor of rightmostContributors) widthContributors.add(contributor);
    }
    if (Math.ceil(laneLabelWidth) === laneW && widestLaneLabel.lane) {
      widthContributors.add(`lane ${widestLaneLabel.lane.id || widestLaneLabel.lane.label} label width`);
    }
  }
  let maxVerticalExtent = 0;
  const verticalExtentContributors = new Set();
  for (const node of nodes) {
    const yOffset = Number(node.yOffset) || 0;
    const extent = authoredNodeHeight(node) / 2 + Math.abs(yOffset);
    const contributor = `node ${node.id} height ${authoredNodeHeight(node)}px${yOffset ? ` with yOffset ${yOffset}px` : ''}`;
    if (extent > maxVerticalExtent + 0.0001) {
      maxVerticalExtent = extent;
      verticalExtentContributors.clear();
      verticalExtentContributors.add(contributor);
    } else if (Math.abs(extent - maxVerticalExtent) <= 0.0001) {
      verticalExtentContributors.add(contributor);
    }
  }
  const baseContentH = Math.max(74, Math.ceil(maxVerticalExtent * 2 + 8));
  const laneH = 30 + baseContentH;
  const groupsByLane = new Map();
  for (const group of asArray(workflow.groups)) {
    groupsByLane.set(group.lane, [...(groupsByLane.get(group.lane) || []), group]);
  }
  const groupLaneReserves = asArray(workflow.lanes).map((lane) => {
    let header = 0;
    let footer = 0;
    for (const group of groupsByLane.get(lane.id) || []) {
      const bounds = readableGroupBounds(workflow, group, colXs);
      const labelLeft = bounds.x + 10;
      const labelRight = labelLeft + textUnits(group.label) * 5.6;
      for (const node of nodes) {
        if (node.lane !== group.lane
          || !Number.isInteger(node.col)
          || node.col < group.fromCol
          || node.col > group.toCol
          || node.col < 0
          || node.col >= colXs.length) continue;
        const halfWidth = authoredNodeWidth(node) / 2;
        const nodeLeft = colXs[node.col] - halfWidth;
        const nodeRight = colXs[node.col] + halfWidth;
        const overlapsLabel = nodeRight > labelLeft && nodeLeft < labelRight;
        const topOffset = (baseContentH - authoredNodeHeight(node)) / 2
          + (Number(node.yOffset) || 0);
        const minimumTopOffset = overlapsLabel ? 11 : 9;
        header = Math.max(header, Math.ceil(minimumTopOffset - topOffset));
        const bottomMargin = baseContentH - GROUP_FRAME_BOTTOM_INSET
          - topOffset - authoredNodeHeight(node);
        footer = Math.max(footer, Math.ceil(1 - bottomMargin));
      }
    }
    return { header: Math.max(0, header), footer: Math.max(0, footer) };
  });
  const groupHeaderHeights = groupLaneReserves.map(({ header }) => header);
  const groupFooterHeights = groupLaneReserves.map(({ footer }) => footer);
  const laneHeights = groupLaneReserves.map(({ header, footer }) => laneH + header + footer);
  const laneGap = Math.max(20, Math.ceil(layoutFeedback.laneGapMin || 0));
  for (const [index, reserve] of groupHeaderHeights.entries()) {
    if (!reserve) continue;
    const lane = asArray(workflow.lanes)[index];
    heightContributors.add(`lane ${lane.id || lane.label} group label clearance ${reserve}px`);
  }
  for (const [index, reserve] of groupFooterHeights.entries()) {
    if (!reserve) continue;
    const lane = asArray(workflow.lanes)[index];
    heightContributors.add(`lane ${lane.id || lane.label} group frame containment ${reserve}px`);
  }
  if (laneH > 104) {
    for (const contributor of verticalExtentContributors) heightContributors.add(contributor);
  }
  if (laneGap > 20) {
    for (const contributor of asArray(layoutFeedback.laneGapContributors)) {
      heightContributors.add(contributor);
    }
  }
  const requiredWidth = 40 + laneW + 16;

  return {
    contract: 'readable-v2',
    laneX: 40,
    laneY: 52,
    laneW,
    laneH,
    laneHeights,
    laneGap,
    laneTitleH: 30,
    groupHeaderHeights,
    groupFooterHeights,
    colXs,
    nodeW: 92,
    nodeH: 52,
    defaultViewBoxWidth: requiredWidth,
    channelLabelEdgeKeys,
    widthContributors: [...widthContributors].sort(stableCompare),
    heightContributors: [...heightContributors].sort(stableCompare),
  };
}

function compilerFailure(contract, diagnostics, error = diagnostics.map(({ message }) => message).join('\n')) {
  return {
    ok: false,
    error,
    diagnostics,
    receipt: { contract, diagnostics },
  };
}

function workflowEdgeName(edge) {
  return edge.id || `${edge.from}->${edge.to}`;
}

function stableText(value) {
  return value == null ? '' : String(value);
}

function stableCompare(left, right) {
  const a = stableText(left);
  const b = stableText(right);
  return a < b ? -1 : a > b ? 1 : 0;
}

function stableValueKey(value) {
  if (Array.isArray(value)) return `[${value.map(stableValueKey).join(',')}]`;
  if (value && typeof value === 'object') {
    return `{${Object.keys(value).sort(stableCompare).map((key) => `${JSON.stringify(key)}:${stableValueKey(value[key])}`).join(',')}}`;
  }
  return JSON.stringify(value);
}

function cloneWorkflow(value) {
  return JSON.parse(JSON.stringify(value));
}

function canonicalReadableWorkflow(workflow) {
  if (workflow.schema_version !== 2) return workflow;
  const laneOrder = new Map(asArray(workflow.lanes).map((lane, index) => [lane.id, index]));
  const nodes = [...asArray(workflow.nodes)].sort((left, right) => (
    (laneOrder.get(left.lane) ?? Number.MAX_SAFE_INTEGER) - (laneOrder.get(right.lane) ?? Number.MAX_SAFE_INTEGER)
    || left.col - right.col
    || stableCompare(left.id, right.id)
  ));
  const edges = [...asArray(workflow.edges)].sort((left, right) => (
    stableCompare(left.id, right.id)
    || stableCompare(left.from, right.from)
    || stableCompare(left.to, right.to)
    || stableCompare(left.label, right.label)
    || stableCompare(left.route, right.route)
    || stableCompare(stableValueKey(left), stableValueKey(right))
  ));
  const phases = workflow.phases === undefined ? undefined : [...asArray(workflow.phases)].sort((left, right) => (
    left.fromCol - right.fromCol || left.toCol - right.toCol
    || stableCompare(left.id, right.id)
  ));
  const groups = workflow.groups === undefined ? undefined : [...asArray(workflow.groups)].sort((left, right) => (
    (laneOrder.get(left.lane) ?? Number.MAX_SAFE_INTEGER) - (laneOrder.get(right.lane) ?? Number.MAX_SAFE_INTEGER)
    || left.fromCol - right.fromCol || left.toCol - right.toCol
    || stableCompare(left.id, right.id)
  ));
  return {
    ...workflow,
    nodes,
    edges,
    ...(phases ? { phases } : {}),
    ...(groups ? { groups } : {}),
  };
}

function semanticContractDiagnostics(workflow) {
  const checks = workflow.semanticChecks;
  if (!checks) return [];

  const nodeIds = new Set(asArray(workflow.nodes).map((node) => node.id));
  const incoming = new Map([...nodeIds].map((id) => [id, 0]));
  const outgoing = new Map([...nodeIds].map((id) => [id, 0]));
  const adjacency = new Map([...nodeIds].map((id) => [id, new Set()]));
  for (const edge of asArray(workflow.edges)) {
    if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) continue;
    outgoing.set(edge.from, outgoing.get(edge.from) + 1);
    incoming.set(edge.to, incoming.get(edge.to) + 1);
    adjacency.get(edge.from).add(edge.to);
  }

  const diagnostics = [];
  const diagnostic = (code, message, subject, evidence, supportedFixes) => ({
    code,
    severity: 'error',
    message,
    subject: { diagramType: 'workflow', ...subject },
    evidence,
    supportedFixes,
  });
  const referencedNodes = [
    ...asArray(checks.allowedRoots).map((id, index) => ({ id, path: `/semanticChecks/allowedRoots/${index}` })),
    ...asArray(checks.allowedTerminals).map((id, index) => ({ id, path: `/semanticChecks/allowedTerminals/${index}` })),
    ...asArray(checks.requiredEdges).flatMap((relation, index) => [
      { id: relation.from, path: `/semanticChecks/requiredEdges/${index}/from` },
      { id: relation.to, path: `/semanticChecks/requiredEdges/${index}/to` },
    ]),
    ...asArray(checks.requiredPaths).flatMap((relation, index) => [
      { id: relation.from, path: `/semanticChecks/requiredPaths/${index}/from` },
      { id: relation.to, path: `/semanticChecks/requiredPaths/${index}/to` },
    ]),
  ];
  for (const { id, path } of referencedNodes) {
    if (nodeIds.has(id)) continue;
    diagnostics.push(diagnostic(
      'workflow/semantic-node-reference',
      `Workflow semantic contract references unknown node "${id}" at ${path}.`,
      { node: id, path },
      { knownNodes: [...nodeIds] },
      [`replace "${id}" with an existing node id`, 'add the missing node before compiling'],
    ));
  }
  if (diagnostics.length) return diagnostics;

  if (checks.allowedRoots !== undefined) {
    const allowed = new Set(checks.allowedRoots);
    for (const [node, count] of incoming) {
      if (count > 0 || allowed.has(node)) continue;
      diagnostics.push(diagnostic(
        'workflow/unexpected-root',
        `Workflow node "${node}" has no incoming edge and is not declared in semanticChecks.allowedRoots.`,
        { node, path: '/semanticChecks/allowedRoots' },
        { incomingEdges: 0, allowedRoots: [...allowed] },
        [`add the missing incoming edge to "${node}"`, `declare "${node}" in semanticChecks.allowedRoots if it is an intentional source`],
      ));
    }
  }

  if (checks.allowedTerminals !== undefined) {
    const allowed = new Set(checks.allowedTerminals);
    for (const [node, count] of outgoing) {
      if (count > 0 || allowed.has(node)) continue;
      diagnostics.push(diagnostic(
        'workflow/unexpected-terminal',
        `Workflow node "${node}" has no outgoing edge and is not declared in semanticChecks.allowedTerminals.`,
        { node, path: '/semanticChecks/allowedTerminals' },
        { outgoingEdges: 0, allowedTerminals: [...allowed] },
        [`add the missing outgoing edge from "${node}"`, `declare "${node}" in semanticChecks.allowedTerminals if it is an intentional sink`],
      ));
    }
  }

  const authoredEdges = new Set(asArray(workflow.edges).map((edge) => `${edge.from}\u0000${edge.to}`));
  for (const [index, relation] of asArray(checks.requiredEdges).entries()) {
    if (authoredEdges.has(`${relation.from}\u0000${relation.to}`)) continue;
    diagnostics.push(diagnostic(
      'workflow/required-edge',
      `Workflow semantic contract requires edge "${relation.from}" -> "${relation.to}", but no authored edge matches it.`,
      { from: relation.from, to: relation.to, path: `/semanticChecks/requiredEdges/${index}` },
      { authoredEdgeCount: asArray(workflow.edges).length },
      [`add an edge from "${relation.from}" to "${relation.to}" without deleting the semantic requirement`],
    ));
  }

  function reachable(from, to) {
    const visited = new Set([from]);
    const pending = [from];
    while (pending.length) {
      const current = pending.shift();
      if (current === to) return true;
      for (const next of adjacency.get(current) || []) {
        if (visited.has(next)) continue;
        visited.add(next);
        pending.push(next);
      }
    }
    return false;
  }

  for (const [index, relation] of asArray(checks.requiredPaths).entries()) {
    if (reachable(relation.from, relation.to)) continue;
    diagnostics.push(diagnostic(
      'workflow/required-path',
      `Workflow semantic contract requires a directed path from "${relation.from}" to "${relation.to}", but none exists.`,
      { from: relation.from, to: relation.to, path: `/semanticChecks/requiredPaths/${index}` },
      { reachableNodes: [...new Set([relation.from, ...(adjacency.get(relation.from) || [])])] },
      [`restore a directed path from "${relation.from}" to "${relation.to}" without weakening the semantic requirement`],
    ));
  }

  return diagnostics;
}

function compileWorkflowInternal({
  workflow: inputWorkflow,
  qualityProfile,
  discoverFixes = true,
  layoutFeedback = {},
} = {}) {
  if (!inputWorkflow || typeof inputWorkflow !== 'object' || Array.isArray(inputWorkflow)) {
    const diagnostics = [{
      code: 'workflow/input-contract',
      severity: 'error',
      message: 'compileWorkflow requires one parsed workflow document object.',
      subject: { diagramType: 'workflow', path: '/' },
      evidence: {},
      supportedFixes: [],
    }];
    return compilerFailure('fixed-v1', diagnostics, diagnostics[0].message);
  }
  const resolvedQualityProfile = qualityProfile || inputWorkflow.meta?.quality_profile;
  const authoredQualityProfile = inputWorkflow.meta?.quality_profile;
  const qualityResolvedWorkflow = resolvedQualityProfile && resolvedQualityProfile !== inputWorkflow.meta?.quality_profile
    ? { ...inputWorkflow, meta: { ...inputWorkflow.meta, quality_profile: resolvedQualityProfile } }
    : inputWorkflow;
  let inputDiagnostics = [];
  try {
    validateSchema('workflow', qualityResolvedWorkflow);
  } catch (error) {
    inputDiagnostics = Array.isArray(error?.archifyDiagnostics)
      ? error.archifyDiagnostics.map((diagnostic) => ({
          ...diagnostic,
          supportedFixes: [],
        }))
      : [{
        code: 'workflow/input-contract',
        severity: 'error',
        message: 'Workflow schema validation failed unexpectedly.',
        subject: { diagramType: 'workflow', path: '/' },
        evidence: { reason: error?.message || String(error) },
        supportedFixes: [],
      }];
  }
  if (inputDiagnostics.length) {
    return compilerFailure(
      inputWorkflow.schema_version === 2 ? 'readable-v2' : 'fixed-v1',
      inputDiagnostics,
    );
  }
  const workflow = canonicalReadableWorkflow(qualityResolvedWorkflow);
  const semanticDiagnostics = semanticContractDiagnostics(workflow);
  if (semanticDiagnostics.length) {
    return compilerFailure(
      workflow.schema_version === 2 ? 'readable-v2' : 'fixed-v1',
      semanticDiagnostics,
    );
  }
  const sourceIndexes = {
    lanes: new Map(asArray(qualityResolvedWorkflow.lanes).map((lane, index) => [lane, index])),
    nodes: new Map(asArray(qualityResolvedWorkflow.nodes).map((node, index) => [node, index])),
    edges: new Map(asArray(qualityResolvedWorkflow.edges).map((edge, index) => [edge, index])),
  };
  const layout = workflow.schema_version === 2
    ? createReadableLayout(workflow, layoutFeedback)
    : createLegacyLayout();

const LEGEND_CATALOG = [
  'frontend',
  'backend',
  'security',
  'messagebus',
  'database',
  'cloud',
  'external',
].map((kind) => ({ kind, label: i18nText(workflow.meta.locale, `legend.workflow.${kind}`) }));
const presentLegendKinds = new Set(asArray(workflow.nodes).map((node) => node.type));
const workflowLegendEntries = resolveLegend(
  workflow.meta?.legend,
  LEGEND_CATALOG,
  presentLegendKinds,
);
const legendFootprintOptions = { fontSize: 7, itemGap: 7 };
const oneRowLegendFootprint = legendFootprint(workflowLegendEntries, {
  ...legendFootprintOptions,
  width: Number.MAX_SAFE_INTEGER,
});
const minimumCanvasWidth = workflow.schema_version === 2
  ? Math.max(layout.defaultViewBoxWidth, oneRowLegendFootprint.minWidth + 40)
  : layout.defaultViewBoxWidth;
const legendPackingWidth = Math.max(
  1,
  (workflow.schema_version === 2
    ? minimumCanvasWidth
    : (workflow.meta?.viewBox?.[0] ?? minimumCanvasWidth)) - 40,
);
const packedLegendFootprint = legendFootprint(workflowLegendEntries, {
  ...legendFootprintOptions,
  width: legendPackingWidth,
});
const legendExtraHeight = workflow.schema_version === 2
  ? packedLegendFootprint.extraHeight
  : 0;

// Content is 680px wide (laneX + laneW); auto height fits the lanes plus legend.
const autoHeight = layout.laneY
  + (layout.laneHeights?.reduce((total, height) => total + height, 0)
    ?? (workflow.lanes?.length || 1) * layout.laneH)
  + ((workflow.lanes?.length || 1) - 1) * layout.laneGap
  + 124
  + legendExtraHeight;
let viewBox = workflow.meta?.viewBox || [minimumCanvasWidth, autoHeight];
let requiredViewBox = [...viewBox];

const laneIndex = new Map(asArray(workflow.lanes).map((lane, index) => [lane.id, index]));
const laneLabels = new Map(asArray(workflow.lanes).map((lane) => [lane.id, lane.label]));

function nodeContext(node) {
  const group = asArray(workflow.groups).find((candidate) => (
    candidate.lane === node.lane && node.col >= candidate.fromCol && node.col <= candidate.toCol
  ));
  const phase = asArray(workflow.phases).find((candidate) => (
    node.col >= candidate.fromCol && node.col <= candidate.toCol
  ));
  return [laneLabels.get(node.lane), group?.label, phase?.label].filter(Boolean).join(' › ')
    || i18nText(workflow.meta.locale, 'node.context.workflow');
}

function laneHeight(idOrIndex) {
  const index = typeof idOrIndex === 'number' ? idOrIndex : laneIndex.get(idOrIndex);
  return layout.laneHeights?.[index] ?? layout.laneH;
}

function laneGroupHeaderH(idOrIndex) {
  const index = typeof idOrIndex === 'number' ? idOrIndex : laneIndex.get(idOrIndex);
  return layout.groupHeaderHeights?.[index] ?? 0;
}

function laneGroupFooterH(idOrIndex) {
  const index = typeof idOrIndex === 'number' ? idOrIndex : laneIndex.get(idOrIndex);
  return layout.groupFooterHeights?.[index] ?? 0;
}

function laneTop(id) {
  const index = laneIndex.get(id);
  const precedingHeight = asArray(workflow.lanes).slice(0, index)
    .reduce((total, _lane, lanePosition) => total + laneHeight(lanePosition), 0);
  return layout.laneY + precedingHeight + index * layout.laneGap;
}

function lastLaneBottom() {
  return layout.laneY
    + asArray(workflow.lanes).reduce((total, _lane, index) => total + laneHeight(index), 0)
    + (workflow.lanes.length - 1) * layout.laneGap;
}

function legendY() {
  return lastLaneBottom() + 44 + legendExtraHeight;
}

function workflowLegendLayout(obstacles = []) {
  return {
    x: 20,
    baselineY: legendY(),
    width: workflow.schema_version === 2 ? legendPackingWidth : viewBox[0] - 40,
    fontSize: 7,
    itemGap: 7,
    minTitleY: lastLaneBottom() + 8,
    obstacles,
    unfit: workflow.meta?.legend === undefined ? 'hide' : 'error',
    diagramType: 'workflow',
  };
}

function workflowLegendRects() {
  if (!workflowLegendEntries.length) return [];
  const measured = measureLegend(workflowLegendEntries, workflowLegendLayout());
  if (!measured) return [];
  return [
    { kind: 'title', x: 20, y: measured.titleY - 10, width: 48, height: 14 },
    ...measured.entries.map((entry) => ({
      kind: entry.kind,
      x: entry.x,
      y: entry.baseline - 10,
      width: entry.width,
      height: 14,
    })),
  ];
}

function measureNode(node) {
  const width = node.width || layout.nodeW;
  const height = node.height || (node.tag ? 68 : layout.nodeH);
  const cx = layout.colXs[node.col];
  const groupHeaderH = laneGroupHeaderH(node.lane);
  const contentH = laneHeight(node.lane) - layout.laneTitleH
    - groupHeaderH - laneGroupFooterH(node.lane);
  const y = laneTop(node.lane) + layout.laneTitleH + groupHeaderH
    + (contentH - height) / 2 + (node.yOffset || 0);
  return {
    ...node,
    width,
    height,
    x: cx - width / 2,
    y,
    cx,
    cy: y + height / 2
  };
}

// Font sizes for this renderer's node text; the fitting geometry is shared.
const nodeTextFit = {
  labelPreferred: 11,
  labelMinimum: 9,
  sublabelPreferred: 8,
  sublabelMinimum: 6,
  tagPreferred: 7,
  tagMinimum: 6,
};

const nodes = new Map(asArray(workflow.nodes).map((node) => [node.id, measureNode(node)]));

function workflowCompositionFrames() {
  const frames = [];
  for (const [index, lane] of asArray(workflow.lanes).entries()) {
    const y = laneTop(lane.id);
    const height = laneHeight(index);
    frames.push({ id: `lane-${index}`, label: lane.label, kind: 'lane', x: layout.laneX, y, width: layout.laneW, height, radius: 10 });
    if (lane.variant === 'exception') {
      frames.push({ id: `lane-${index}-exception`, label: `${lane.label} exception`, kind: 'exception-lane', x: layout.laneX + 6, y: y + 6, width: layout.laneW - 12, height: height - 12, radius: 8 });
    }
  }
  for (const [index, group] of asArray(workflow.groups).entries()) {
    const span = groupSpan(group);
    frames.push({
      id: `group-${index}`,
      label: group.label,
      kind: 'group',
      x: span.x,
      y: laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET,
      width: span.width,
      height: workflow.schema_version === 2
        ? laneHeight(group.lane) - layout.laneTitleH
          - GROUP_FRAME_TOP_INSET - GROUP_FRAME_BOTTOM_INSET
        : layout.laneH - layout.laneTitleH - 16,
      radius: 9,
    });
  }
  return frames;
}

function workflowSceneLabelObstacles() {
  const obstacles = [];
  for (const [index, lane] of asArray(workflow.lanes).entries()) {
    const prefix = lane.variant === 'exception' ? 'EX' : String(index + 1).padStart(2, '0');
    const label = `${prefix} / ${lane.label}`;
    obstacles.push({
      kind: 'lane-header',
      id: lane.id,
      x: layout.laneX + 14,
      y: laneTop(lane.id) + 12,
      width: textUnits(label) * 6.2,
      height: 14,
    });
  }
  for (const phase of asArray(workflow.phases)) {
    if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol)
      || phase.fromCol < 0 || phase.toCol >= layout.colXs.length || phase.fromCol > phase.toCol) continue;
    const span = phaseSpan(phase);
    obstacles.push({
      kind: 'phase-header',
      id: phase.id ?? null,
      x: span.x,
      y: 27,
      width: span.width,
      height: 16,
    });
  }
  for (const group of asArray(workflow.groups)) {
    if (!laneIndex.has(group.lane)
      || !Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)
      || group.fromCol < 0 || group.toCol >= layout.colXs.length || group.fromCol > group.toCol) continue;
    const span = groupSpan(group);
    const frameY = laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET;
    const labelBaseline = frameY + GROUP_LABEL_BASELINE_OFFSET;
    obstacles.push({
      kind: 'group-label',
      id: group.id ?? null,
      x: span.x + 10,
      y: labelBaseline - GROUP_LABEL_MASK_ASCENT,
      width: textUnits(group.label) * 5.6,
      height: GROUP_LABEL_MASK_H,
    });
  }
  return obstacles;
}

const mainPathSteps = new Map(asArray(workflow.mainPath).map((id, index) => [id, index]));
const edgeSteps = new Map(asArray(workflow.edges).map((edge, index) => {
  const fromStep = mainPathSteps.get(edge.from);
  const toStep = mainPathSteps.get(edge.to);
  const mainStep = Number.isInteger(fromStep) && toStep === fromStep + 1 ? fromStep : null;
  return [edge, mainStep ?? asArray(workflow.mainPath).length + index];
}));

function nodeStep(node) {
  return mainPathSteps.get(node.id) ?? asArray(workflow.mainPath).length + asArray(workflow.nodes).findIndex((item) => item.id === node.id);
}

  function acceptsFix(mutator) {
    if (!discoverFixes) return false;
    const candidate = cloneWorkflow(workflow);
    mutator(candidate);
    return withDiagnosticRecordingSuppressed(() => compileWorkflowWithFeedback({
      workflow: candidate,
      qualityProfile: resolvedQualityProfile,
      discoverFixes: false,
    }).ok);
  }

  function verifiedLegacyAlternative(edge, from, to, requiredClearance) {
    const occupied = [...nodes.values()].filter((node) => node.lane === to.lane && node.id !== to.id);
  const candidates = layout.colXs.map((center, col) => ({ center, col }))
    .filter(({ col }) => col !== to.col)
    .sort((a, b) => Math.abs(a.col - to.col) - Math.abs(b.col - to.col) || a.col - b.col);
  for (const candidate of candidates) {
    const candidateRect = { ...to, col: candidate.col, cx: candidate.center, x: candidate.center - to.width / 2 };
    if (occupied.some((node) => rectsOverlap(candidateRect, node, 8))) continue;
    const centerDistance = Math.abs(candidate.center - from.cx);
    const signedClearance = centerDistance - from.width / 2 - to.width / 2;
      if (signedClearance < requiredClearance) continue;
      if (acceptsFix((document) => {
        document.nodes.find((node) => node.id === to.id).col = candidate.col;
      })) return candidate.col;
    }
    return null;
  }

  function readableMigrationProvidesCapacity(from, to, requiredClearance) {
    const readable = createReadableLayout({ ...workflow, schema_version: 2 });
    const centerDistance = Math.abs(readable.colXs[to.col] - readable.colXs[from.col]);
    if (centerDistance - from.width / 2 - to.width / 2 < requiredClearance) return false;
    if (!discoverFixes) return false;

    return withDiagnosticRecordingSuppressed(() => {
      const migrationQualityProfile = authoredQualityProfile;
      let planned = compileWorkflowWithFeedback({
        workflow: intrinsicWorkflow(workflow),
        qualityProfile: migrationQualityProfile,
        discoverFixes: false,
      });
      if (!planned.ok) {
        planned = compileWorkflowWithFeedback({
          workflow: planningWorkflow(workflow),
          qualityProfile: migrationQualityProfile,
          discoverFixes: false,
        });
      }
      if (!planned.ok || !Array.isArray(planned.receipt?.columns)) return false;

      let candidate;
      try {
        candidate = createMappedWorkflowCandidate(
          workflow,
          LEGACY_COLUMN_CENTERS,
          planned.receipt.columns,
        ).document;
      } catch {
        return false;
      }
      let compiled = compileWorkflowWithFeedback({
        workflow: candidate,
        qualityProfile: migrationQualityProfile,
        discoverFixes: false,
      });
      const requiredViewBox = compiled.diagnostics?.length
        && compiled.diagnostics.every(({ code }) => code === 'workflow/viewbox-capacity')
        ? compiled.diagnostics.find(({ evidence }) => Array.isArray(evidence?.requiredViewBox))
          ?.evidence.requiredViewBox
        : null;
      if (!compiled.ok && Array.isArray(candidate.meta?.viewBox) && requiredViewBox) {
        candidate.meta.viewBox = [
          Math.max(candidate.meta.viewBox[0], requiredViewBox[0]),
          Math.max(candidate.meta.viewBox[1], requiredViewBox[1]),
        ];
        compiled = compileWorkflowWithFeedback({
          workflow: candidate,
          qualityProfile: migrationQualityProfile,
          discoverFixes: false,
        });
      }
      return compiled.ok;
    });
  }

function verifiedReducedWidths(from, to, requiredClearance) {
  const widthBudget = 2 * (Math.abs(to.cx - from.cx) - requiredClearance);
  if (widthBudget < 64) return null;
  const widths = [from.width, to.width];
  let excess = widths[0] + widths[1] - widthBudget;
  for (const index of widths[0] >= widths[1] ? [0, 1] : [1, 0]) {
    const reduction = Math.min(excess, widths[index] - 32);
    widths[index] -= reduction;
    excess -= reduction;
  }
  if (excess > 0.0001) return null;
  const candidates = [from, to];
  const labelsFit = candidates.every((node, index) => (
    textUnits(node.label) * 6.8 <= widths[index] + 6
    && (!node.sublabel || minimumNodeTextWidth(node.sublabel, nodeTextFit.sublabelMinimum) <= availableNodeTextWidth(widths[index]))
    && (!node.tag || minimumNodeTextWidth(node.tag, nodeTextFit.tagMinimum) <= availableNodeTextWidth(widths[index]))
  ));
  if (!labelsFit) return null;
  const serializedWidths = widths.map((width) => Math.floor((width + 1e-9) * 100) / 100);
  const signedClearance = Math.abs(to.cx - from.cx)
    - serializedWidths[0] / 2 - serializedWidths[1] / 2;
  if (signedClearance + 0.0001 < requiredClearance) return null;
  const accepted = acceptsFix((document) => {
    document.nodes.find((node) => node.id === from.id).width = serializedWidths[0];
    document.nodes.find((node) => node.id === to.id).width = serializedWidths[1];
  });
  return accepted ? serializedWidths : null;
}

function enforceLegacyColumnCapacity() {
  if (workflow.schema_version !== 1) return;
  for (const edge of workflow.edges) {
    const from = nodes.get(edge.from);
    const to = nodes.get(edge.to);
    if (!from || !to || from.lane !== to.lane || from.col === to.col) continue;
    if (!verticalIntervalsOverlap(from, to, 8)) continue;
    const centerDistance = Math.abs(to.cx - from.cx);
    const actualSignedClearance = centerDistance - from.width / 2 - to.width / 2;
    const direct = !edge.via && ['auto', 'straight'].includes(edge.route || 'auto')
      && Math.abs(from.cy - to.cy) < 0.0001;
    const requiredDirectClearance = direct ? 28 : 8;
    if (actualSignedClearance >= requiredDirectClearance) continue;
    const alternative = verifiedLegacyAlternative(edge, from, to, requiredDirectClearance);
    const reducedWidths = verifiedReducedWidths(from, to, requiredDirectClearance);
    const capacity = actualSignedClearance < 0
      ? `overlap by ${Math.abs(Math.round(actualSignedClearance))}px`
      : `leave only ${Math.round(actualSignedClearance)}px of direct clearance`;
    const message = `Workflow columns ${from.col}→${to.col} place nodes "${from.id}" and "${to.id}" so they ${capacity} under the fixed-v1 layout.`;
    const supportedFixes = [];
    if (readableMigrationProvidesCapacity(from, to, requiredDirectClearance)) {
      supportedFixes.push('migrate this workflow to schema_version 2');
    }
    if (alternative !== null) supportedFixes.push(`move node "${to.id}" to verified free column ${alternative}`);
    if (reducedWidths) {
      supportedFixes.push(`set node widths "${from.id}"=${Math.round(reducedWidths[0] * 100) / 100}px and "${to.id}"=${Math.round(reducedWidths[1] * 100) / 100}px`);
    }
    throwDiagnosticError(message, [{
      code: 'workflow/column-capacity',
      severity: 'error',
      message,
      subject: {
        diagramType: 'workflow',
        edge: edge.id ?? null,
        from: edge.from,
        to: edge.to,
        fromCol: from.col,
        toCol: to.col,
      },
      evidence: {
        centerDistancePx: centerDistance,
        nodeWidthsPx: [from.width, to.width],
        actualSignedClearancePx: actualSignedClearance,
        requiredDirectClearancePx: requiredDirectClearance,
      },
      supportedFixes,
      suppresses: [
        'workflow/short-edge',
        'clean-flow/endpoint-side-direction',
        'workflow/label-node-overlap',
      ],
    }]);
  }
}

function verifiedEdgeFix(edge, message, mutator) {
  const edgeIndex = workflow.edges.indexOf(edge);
  if (edgeIndex < 0) return null;
  const accepted = acceptsFix((document) => mutator(document.edges[edgeIndex], document));
  return accepted ? message : null;
}

function verifiedAutomaticRouteFix(edge, { clearSides = false } = {}) {
  const edgeName = workflowEdgeName(edge);
  return verifiedEdgeFix(
    edge,
    clearSides
      ? `remove explicit route geometry and endpoint sides from edge "${edgeName}" so readable-v2 can use its verified automatic candidate`
      : `remove explicit route geometry from edge "${edgeName}" so readable-v2 can use its verified automatic candidate`,
    (candidate) => {
      delete candidate.via;
      delete candidate.channelX;
      delete candidate.channelY;
      delete candidate.route;
      if (clearSides) {
        delete candidate.fromSide;
        delete candidate.toSide;
      }
    },
  );
}

function authoredPinEvidence(edge, field) {
  const authoredEdgeIndex = sourceIndexes.edges.get(edge);
  const value = Array.isArray(edge[field])
    ? edge[field].map((item) => (Array.isArray(item) ? [...item] : item))
    : edge[field];
  return {
    edge: workflowEdgeName(edge),
    field,
    ...(Number.isInteger(authoredEdgeIndex) ? { path: `/edges/${authoredEdgeIndex}/${field}` } : {}),
    value,
  };
}

function combinations(values, size, start = 0, prefix = [], output = []) {
  if (prefix.length === size) {
    output.push([...prefix]);
    return output;
  }
  for (let index = start; index <= values.length - (size - prefix.length); index += 1) {
    prefix.push(values[index]);
    combinations(values, size, index + 1, prefix, output);
    prefix.pop();
  }
  return output;
}

function verifiedPinRemovalAlternatives(edge, fields, reason) {
  if (!discoverFixes) return { removalSets: [], supportedFixes: [] };
  const edgeIndex = workflow.edges.indexOf(edge);
  if (edgeIndex < 0) return { removalSets: [], supportedFixes: [] };
  const uniqueFields = [...new Set(fields.filter((field) => edge[field] !== undefined))];
  for (let size = 1; size <= uniqueFields.length; size += 1) {
    const removalSets = combinations(uniqueFields, size).filter((fieldSet) => (
      acceptsFix((document) => {
        for (const field of fieldSet) delete document.edges[edgeIndex][field];
      })
    ));
    if (!removalSets.length) continue;
    const edgeName = workflowEdgeName(edge);
    return {
      removalSets,
      supportedFixes: removalSets.map((fieldSet) => (
        `remove ${fieldSet.join(' and ')} from edge "${edgeName}" ${reason}`
      )),
    };
  }
  return { removalSets: [], supportedFixes: [] };
}

function conflictPinsFromRemovalSets(edge, removalSets, fallbackFields = []) {
  const fields = removalSets.length
    ? [...new Set(removalSets.flat())]
    : [...new Set(fallbackFields)];
  return fields.map((field) => authoredPinEvidence(edge, field));
}

function authoredRouteAssertionFields(edge) {
  return [
    ...(Array.isArray(edge?.via) ? ['via'] : []),
    ...(edge?.channelX !== undefined ? ['channelX'] : []),
    ...(edge?.channelY !== undefined ? ['channelY'] : []),
    ...(edge?.route && edge.route !== 'auto' ? ['route'] : []),
    ...(edge?.fromSide && edge.fromSide !== 'auto' ? ['fromSide'] : []),
    ...(edge?.toSide && edge.toSide !== 'auto' ? ['toSide'] : []),
  ];
}

function hasAuthoredRouteAssertions(edge) {
  return authoredRouteAssertionFields(edge).length > 0;
}

function verifiedPinReferenceAlternatives(candidateRefs, reason) {
  const seenRefs = new Set();
  const refs = candidateRefs.filter(({ edge, edgeIndex, field }) => {
    if (edgeIndex < 0 || edge?.[field] === undefined) return false;
    const key = `${edgeIndex}:${field}`;
    if (seenRefs.has(key)) return false;
    seenRefs.add(key);
    return true;
  });
  const fallbackPins = refs.map(({ edge, field }) => authoredPinEvidence(edge, field));
  if (!discoverFixes) {
    return {
      removalSets: [], conflictingRefs: refs, conflictingPins: fallbackPins, repairs: [], supportedFixes: [],
    };
  }

  for (let size = 1; size <= refs.length; size += 1) {
    const removalSets = combinations(refs, size).filter((removalSet) => (
      acceptsFix((document) => {
        for (const { edgeIndex, field } of removalSet) delete document.edges[edgeIndex][field];
      })
    ));
    if (!removalSets.length) continue;
    const conflictingRefs = [];
    const conflictingPins = [];
    const seenPins = new Set();
    for (const removalSet of removalSets) {
      for (const { edge, field } of removalSet) {
        const key = `${workflow.edges.indexOf(edge)}:${field}`;
        if (seenPins.has(key)) continue;
        seenPins.add(key);
        conflictingRefs.push({ edge, edgeIndex: workflow.edges.indexOf(edge), field });
        conflictingPins.push(authoredPinEvidence(edge, field));
      }
    }
    const repairs = removalSets.map((removalSet) => {
      const grouped = [];
      for (const ref of removalSet) {
        let group = grouped.find(({ edge }) => edge === ref.edge);
        if (!group) {
          group = { edge: ref.edge, fields: [] };
          grouped.push(group);
        }
        group.fields.push(ref.field);
      }
      const removals = grouped.map(({ edge, fields }) => (
        `remove ${fields.join(' and ')} from edge "${workflowEdgeName(edge)}"`
      ));
      return { removalSet, message: `${removals.join(' and ')} ${reason}` };
    });
    return {
      removalSets,
      conflictingRefs,
      conflictingPins,
      repairs,
      supportedFixes: repairs.map(({ message }) => message),
    };
  }
  return {
    removalSets: [], conflictingRefs: refs, conflictingPins: fallbackPins, repairs: [], supportedFixes: [],
  };
}

function verifiedRoutePairPinAlternatives(leftEdge, rightEdge, reason) {
  const refs = [leftEdge, rightEdge].flatMap((edge) => {
    const edgeIndex = workflow.edges.indexOf(edge);
    return authoredRouteAssertionFields(edge).map((field) => ({ edge, edgeIndex, field }));
  });
  return verifiedPinReferenceAlternatives(refs, reason);
}

function verifiedLabelRoutePinAlternatives(labelEdge, routeEdge) {
  const refs = [];
  const labelEdgeIndex = workflow.edges.indexOf(labelEdge);
  if (Array.isArray(labelEdge?.labelAt)) {
    refs.push({ edge: labelEdge, edgeIndex: labelEdgeIndex, field: 'labelAt' });
  }
  const routeEdgeIndex = workflow.edges.indexOf(routeEdge);
  for (const field of authoredRouteAssertionFields(routeEdge)) {
    refs.push({ edge: routeEdge, edgeIndex: routeEdgeIndex, field });
  }
  return verifiedPinReferenceAlternatives(
    refs,
    Array.isArray(labelEdge?.labelAt)
      ? 'so readable-v2 can replan the remaining authored label-route pins'
      : 'so readable-v2 can replan the remaining authored route assertions',
  );
}

function verifiedLabelPairPinAlternatives(leftEdge, rightEdge) {
  return verifiedPinReferenceAlternatives(
    [leftEdge, rightEdge].flatMap((edge) => (
      Array.isArray(edge?.labelAt)
        ? [{ edge, edgeIndex: workflow.edges.indexOf(edge), field: 'labelAt' }]
        : []
    )),
    'so readable-v2 can replan the remaining authored label pins',
  );
}

function verifiedRepairsWithLabelNudges(alternatives) {
  return alternatives.repairs.flatMap(({ removalSet, message }) => {
    if (removalSet.length !== 1 || removalSet[0].field !== 'labelAt') return [message];
    const nudges = verifiedLabelAtAlternatives(removalSet[0].edge);
    return nudges.length ? nudges : [message];
  });
}

function throwExplicitPinConflict(edge, invariant, evidence, supportedFixes = []) {
  const message = `Workflow edge "${workflowEdgeName(edge)}" has explicit geometry that violates ${invariant}.`;
  const [onlyPin] = asArray(evidence?.conflictingPins);
  const authoredEdgeIndex = sourceIndexes.edges.get(edge);
  const pinPath = asArray(evidence?.conflictingPins).length === 1
    && Number.isInteger(authoredEdgeIndex)
    && onlyPin?.field
    ? onlyPin.path || `/edges/${authoredEdgeIndex}/${onlyPin.field}`
    : null;
  throwDiagnosticError(message, [{
    code: 'workflow/explicit-pin-conflict',
    severity: 'error',
    message,
    subject: {
      diagramType: 'workflow',
      edge: edge.id ?? null,
      from: edge.from,
      to: edge.to,
      ...(pinPath ? { path: pinPath } : {}),
    },
    evidence: { invariant, ...evidence },
    supportedFixes: supportedFixes.filter(Boolean),
  }]);
}

function hasAbsoluteRoutePins(edge) {
  return Array.isArray(edge?.via)
    || edge?.channelX !== undefined
    || edge?.channelY !== undefined;
}

function presentRouteGeometryFields(edge) {
  return [
    ...(Array.isArray(edge?.via) ? ['via'] : []),
    ...(edge?.channelX !== undefined ? ['channelX'] : []),
    ...(edge?.channelY !== undefined ? ['channelY'] : []),
  ];
}

function verifiedRouteGeometryPinAlternatives(
  edge,
  reason = 'so readable-v2 can replan the remaining explicit route assertions',
) {
  const edgeIndex = workflow.edges.indexOf(edge);
  return verifiedPinReferenceAlternatives(
    authoredRouteAssertionFields(edge).map((field) => ({ edge, edgeIndex, field })),
    reason,
  );
}

function properOrthogonalIntersection(leftStart, leftEnd, rightStart, rightEnd) {
  const leftOrientation = segmentOrientation(leftStart, leftEnd);
  const rightOrientation = segmentOrientation(rightStart, rightEnd);
  if (leftOrientation === rightOrientation
    || leftOrientation === 'diagonal'
    || rightOrientation === 'diagonal') return null;
  const horizontalStart = leftOrientation === 'horizontal' ? leftStart : rightStart;
  const horizontalEnd = leftOrientation === 'horizontal' ? leftEnd : rightEnd;
  const verticalStart = leftOrientation === 'vertical' ? leftStart : rightStart;
  const verticalEnd = leftOrientation === 'vertical' ? leftEnd : rightEnd;
  const point = [verticalStart[0], horizontalStart[1]];
  const epsilon = 0.0001;
  const insideHorizontal = point[0] > Math.min(horizontalStart[0], horizontalEnd[0]) + epsilon
    && point[0] < Math.max(horizontalStart[0], horizontalEnd[0]) - epsilon;
  const insideVertical = point[1] > Math.min(verticalStart[1], verticalEnd[1]) + epsilon
    && point[1] < Math.max(verticalStart[1], verticalEnd[1]) - epsilon;
  return insideHorizontal && insideVertical ? point : null;
}

function verifiedLabelAtAlternatives(edge) {
  if (!Array.isArray(edge.labelAt)) return [];
  const [x, y] = edge.labelAt;
  return [
    [0, 24], [0, -24], [24, 0], [-24, 0],
    [0, 48], [0, -48], [48, 0], [-48, 0],
  ].map(([dx, dy]) => {
    const next = [x + dx, y + dy];
    return verifiedEdgeFix(
      edge,
      `set labelAt on edge "${workflowEdgeName(edge)}" to [${next[0]}, ${next[1]}]`,
      (candidate) => { candidate.labelAt = next; },
    );
  }).filter(Boolean);
}

function verifiedLabelAtNudge(edge) {
  const [alternative] = verifiedLabelAtAlternatives(edge);
  if (alternative) return alternative;
  return verifiedEdgeFix(
    edge,
    `remove labelAt from edge "${workflowEdgeName(edge)}" so readable-v2 can use verified automatic label placement`,
    (candidate) => { delete candidate.labelAt; },
  );
}

function throwReadableLabelRoutePinConflict(hit, routePoints = null) {
  const labelEdge = hit.labelRelation;
  const routeEdge = hit.otherRelation;
  const labelPinned = Array.isArray(labelEdge?.labelAt);
  const routePinned = hasAuthoredRouteAssertions(routeEdge);
  if (!labelPinned && !routePinned) return false;
  const alternatives = verifiedLabelRoutePinAlternatives(labelEdge, routeEdge);
  const actualRoutePoints = routePoints
    || pathCache.get(routeEdge)?.points
    || pathFor(routeEdge).points;
  const diagnosticEdge = alternatives.conflictingRefs[0]?.edge
    || (labelPinned ? labelEdge : routeEdge);
  throwExplicitPinConflict(diagnosticEdge, 'explicit label-route clearance', {
    conflictingPins: alternatives.conflictingPins,
    ...(labelPinned ? { labelAt: [...labelEdge.labelAt] } : {}),
    labelRect: {
      x: hit.rect.x,
      y: hit.rect.y,
      width: hit.rect.width,
      height: hit.rect.height,
    },
    collidedRoute: {
      edge: routeEdge.id || `${routeEdge.from}->${routeEdge.to}`,
      from: routeEdge.from,
      to: routeEdge.to,
      points: actualRoutePoints.map((point) => [...point]),
    },
    routeSegmentIndex: hit.segmentIndex,
    routeSegment: { from: [...hit.start], to: [...hit.end] },
    clearancePx: Math.round(hit.clearance * 10) / 10,
    minimumPx: hit.threshold,
  }, [
    ...verifiedRepairsWithLabelNudges(alternatives),
  ]);
  return true;
}

function throwReadableLabelLabelPinConflict(left, right) {
  const leftEdge = left.relation;
  const rightEdge = right.relation;
  const pinnedEdges = [leftEdge, rightEdge].filter((edge) => Array.isArray(edge?.labelAt));
  if (!pinnedEdges.length) return false;
  const alternatives = verifiedLabelPairPinAlternatives(leftEdge, rightEdge);
  const causalLabelEdges = [...new Set(alternatives.conflictingRefs.map(({ edge }) => edge))];
  const diagnosticEdge = causalLabelEdges[0] || pinnedEdges[0];
  throwExplicitPinConflict(diagnosticEdge, 'explicit label-label clearance', {
    conflictingPins: alternatives.conflictingPins,
    labelRects: [left, right].map((rect) => ({
      edge: rect.relation.id ?? null,
      x: rect.x,
      y: rect.y,
      width: rect.width,
      height: rect.height,
    })),
    minimumGapPx: -2,
  }, verifiedRepairsWithLabelNudges(alternatives));
  return true;
}

function classifyFailedAutomaticCandidatePins(edge, rawCandidates) {
  const relationIndex = workflow.edges.indexOf(edge);
  const priorRoutes = [...pathCache.entries()]
    .filter(([otherEdge]) => otherEdge !== edge)
    .map(([relation, routed]) => ({
      relation,
      relationIndex: workflow.edges.indexOf(relation),
      points: routed.points,
    }));
  if (!priorRoutes.length) return;
  const priorLabels = priorRoutes.map(({ relation, relationIndex }) => (
    labelRectFor(relation, relationIndex)
  )).filter(Boolean);

  for (const { points } of rawCandidates) {
    const candidateRect = candidateLabelRect(edge, points);
    const candidateLabel = candidateRect
      ? { ...candidateRect, relation: edge, relationIndex, label: edge.label }
      : null;
    if (candidateLabel) {
      const priorLabel = priorLabels.find((otherLabel) => (
        rectsOverlap(candidateLabel, otherLabel, -2)
        && (Array.isArray(edge.labelAt) || Array.isArray(otherLabel.relation?.labelAt))
      ));
      if (priorLabel) throwReadableLabelLabelPinConflict(candidateLabel, priorLabel);

      const labelRouteHit = collectLabelRouteClearance({
        labels: [candidateLabel],
        routedRelations: priorRoutes,
        threshold: 4,
      }).find((hit) => (
        Array.isArray(edge.labelAt) || hasAbsoluteRoutePins(hit.otherRelation)
      ));
      if (labelRouteHit) {
        const collidedRoute = priorRoutes.find(({ relation }) => relation === labelRouteHit.otherRelation);
        throwReadableLabelRoutePinConflict(labelRouteHit, collidedRoute?.points);
      }
    }

    const reverseHit = collectLabelRouteClearance({
      labels: priorLabels,
      routedRelations: [{ relation: edge, relationIndex, points }],
      threshold: 4,
    }).find((hit) => Array.isArray(hit.labelRelation?.labelAt));
    if (reverseHit) throwReadableLabelRoutePinConflict(reverseHit, points);
  }
}

function validateReadablePairwisePinConflicts() {
  const labels = workflow.edges.map((edge, relationIndex) => (
    labelRectFor(edge, relationIndex)
  )).filter(Boolean);
  const routedRelations = workflow.edges.map((edge, relationIndex) => (
    nodes.has(edge.from) && nodes.has(edge.to)
      ? { relation: edge, relationIndex, points: pathFor(edge).points }
      : null
  )).filter(Boolean);

  const labelRouteHit = collectLabelRouteClearance({
    labels,
    routedRelations,
    threshold: 4,
  }).find((hit) => (
    Array.isArray(hit.labelRelation?.labelAt) || hasAuthoredRouteAssertions(hit.otherRelation)
  ));
  if (labelRouteHit) throwReadableLabelRoutePinConflict(labelRouteHit);

  for (let leftIndex = 0; leftIndex < labels.length; leftIndex += 1) {
    for (let rightIndex = leftIndex + 1; rightIndex < labels.length; rightIndex += 1) {
      const left = labels[leftIndex];
      const right = labels[rightIndex];
      if (!rectsOverlap(left, right, -2)) continue;
      throwReadableLabelLabelPinConflict(left, right);
    }
  }

  const requestedProfile = workflow.meta?.quality_profile;
  if (requestedProfile !== 'showcase') return;
  for (let leftIndex = 0; leftIndex < routedRelations.length; leftIndex += 1) {
    const left = routedRelations[leftIndex];
    for (let rightIndex = leftIndex + 1; rightIndex < routedRelations.length; rightIndex += 1) {
      const right = routedRelations[rightIndex];
      const leftPinned = hasAuthoredRouteAssertions(left.relation);
      const rightPinned = hasAuthoredRouteAssertions(right.relation);
      if (!leftPinned && !rightPinned) continue;
      if ([left.relation.from, left.relation.to].some((id) => (
        id === right.relation.from || id === right.relation.to
      ))) continue;
      const leftAnalysis = forwardCollinearAnalysisSegments(left.points);
      const rightAnalysis = forwardCollinearAnalysisSegments(right.points);
      for (const leftSegment of leftAnalysis) {
        for (const rightSegment of rightAnalysis) {
          const point = properOrthogonalIntersection(
            leftSegment.start,
            leftSegment.end,
            rightSegment.start,
            rightSegment.end,
          );
          if (!point) continue;
          const leftSourceIndex = sourceSegmentIndexAtPoint(leftSegment, point);
          const rightSourceIndex = sourceSegmentIndexAtPoint(rightSegment, point);
          const leftSource = {
            from: left.points[leftSourceIndex],
            to: left.points[leftSourceIndex + 1],
          };
          const rightSource = {
            from: right.points[rightSourceIndex],
            to: right.points[rightSourceIndex + 1],
          };
          const alternatives = verifiedRoutePairPinAlternatives(
            left.relation,
            right.relation,
            'so readable-v2 can replan the remaining authored route assertions',
          );
          const diagnosticEdge = leftPinned ? left.relation : right.relation;
          throwExplicitPinConflict(diagnosticEdge, 'explicit route-route crossing', {
            conflictingPins: alternatives.conflictingPins,
            point,
            segmentIndex: leftSourceIndex,
            otherSegmentIndex: rightSourceIndex,
            routeSegments: [
              { edge: left.relation.id ?? null, from: [...leftSegment.start], to: [...leftSegment.end] },
              { edge: right.relation.id ?? null, from: [...rightSegment.start], to: [...rightSegment.end] },
            ],
            sourceRouteSegments: [
              { edge: left.relation.id ?? null, from: [...leftSource.from], to: [...leftSource.to] },
              { edge: right.relation.id ?? null, from: [...rightSource.from], to: [...rightSource.to] },
            ],
          }, alternatives.supportedFixes);
        }
      }
    }
  }

  const corridorHit = collectAmbiguousCorridors({
    routedRelations,
    minOverlapPx: 8,
  }).find((hit) => (
    hasAuthoredRouteAssertions(hit.left.relation)
    || hasAuthoredRouteAssertions(hit.right.relation)
  ));
  if (corridorHit) {
    const leftSegment = {
      from: corridorHit.left.points[corridorHit.leftSegment],
      to: corridorHit.left.points[corridorHit.leftSegment + 1],
    };
    const rightSegment = {
      from: corridorHit.right.points[corridorHit.rightSegment],
      to: corridorHit.right.points[corridorHit.rightSegment + 1],
    };
    const leftPinned = hasAuthoredRouteAssertions(corridorHit.left.relation);
    const alternatives = verifiedRoutePairPinAlternatives(
      corridorHit.left.relation,
      corridorHit.right.relation,
      'so readable-v2 can replan the remaining authored route assertions',
    );
    const diagnosticEdge = leftPinned ? corridorHit.left.relation : corridorHit.right.relation;
    throwExplicitPinConflict(diagnosticEdge, 'explicit route-route corridor clearance', {
      conflictingPins: alternatives.conflictingPins,
      segmentIndex: corridorHit.leftSegment,
      otherSegmentIndex: corridorHit.rightSegment,
      routeSegments: [
        {
          edge: corridorHit.left.relation.id ?? null,
          from: [...leftSegment.from],
          to: [...leftSegment.to],
        },
        {
          edge: corridorHit.right.relation.id ?? null,
          from: [...rightSegment.from],
          to: [...rightSegment.to],
        },
      ],
      overlapStart: [...corridorHit.overlapStart],
      overlapEnd: [...corridorHit.overlapEnd],
      overlapLengthPx: corridorHit.overlapLength,
      minimumClearancePx: 8,
    }, alternatives.supportedFixes);
  }
}

const READABLE_PRESET_PIN_FIELDS = Object.freeze({
  straight: [],
  drop: ['channelY'],
  'outside-right': ['channelX'],
  'return-left': ['channelX'],
  'bottom-channel': ['channelY'],
  'up-channel': ['channelY'],
});

function presentChannelPins(edge) {
  return ['channelX', 'channelY'].filter((field) => edge[field] !== undefined);
}

function validateReadableRouteControls(edge) {
  const channelPins = presentChannelPins(edge);
  const preset = edge.route || 'auto';
  if (preset === 'auto') return;
  const allowedPins = new Set(READABLE_PRESET_PIN_FIELDS[preset] || []);
  const conflictingPins = channelPins.filter((field) => !allowedPins.has(field));
  if (!conflictingPins.length) return;
  const edgeIndex = workflow.edges.indexOf(edge);
  const alternatives = verifiedPinReferenceAlternatives([
    { edge, edgeIndex, field: 'route' },
    ...conflictingPins.map((field) => ({ edge, edgeIndex, field })),
  ], 'and keep the remaining verified route assertions');
  throwExplicitPinConflict(edge, 'route preset compatibility', {
    route: preset,
    allowedPins: [...allowedPins],
    conflictingPins: alternatives.conflictingPins,
  }, alternatives.supportedFixes);
}

function segmentOrientation(start, end) {
  if (Math.abs(start[0] - end[0]) <= 0.0001) return 'vertical';
  if (Math.abs(start[1] - end[1]) <= 0.0001) return 'horizontal';
  return 'diagonal';
}

function routeSegments(points) {
  return points.slice(0, -1).map((start, index) => ({
    start,
    end: points[index + 1],
    orientation: segmentOrientation(start, points[index + 1]),
  }));
}

function endpointSideIsHonored(points, side, endpoint) {
  if (!side || side === 'auto' || points.length < 2) return true;
  const source = endpoint === 'source';
  const from = source ? points[0] : points.at(-2);
  const to = source ? points[1] : points.at(-1);
  const dx = to[0] - from[0];
  const dy = to[1] - from[1];
  if (source) {
    if (side === 'right') return dx > 0 && Math.abs(dy) <= 0.0001;
    if (side === 'left') return dx < 0 && Math.abs(dy) <= 0.0001;
    if (side === 'bottom') return dy > 0 && Math.abs(dx) <= 0.0001;
    if (side === 'top') return dy < 0 && Math.abs(dx) <= 0.0001;
    return false;
  }
  if (side === 'right') return dx < 0 && Math.abs(dy) <= 0.0001;
  if (side === 'left') return dx > 0 && Math.abs(dy) <= 0.0001;
  if (side === 'bottom') return dy < 0 && Math.abs(dx) <= 0.0001;
  if (side === 'top') return dy > 0 && Math.abs(dx) <= 0.0001;
  return false;
}

function corridorTopologyMatches(points, axis, coordinate) {
  const collapsed = normalizeRoutePoints(points.map((point) => [...point]));
  const start = collapsed[0];
  const end = collapsed.at(-1);
  const via = axis === 'x'
    ? [[coordinate, start[1]], [coordinate, end[1]]]
    : [[start[0], coordinate], [end[0], coordinate]];
  const expected = normalizeRoutePoints([start, ...via, end]);
  const actualPattern = routeSegments(collapsed).map(({ orientation }) => orientation);
  const expectedPattern = routeSegments(expected).map(({ orientation }) => orientation);
  return actualPattern.length === expectedPattern.length
    && actualPattern.every((orientation, index) => orientation === expectedPattern[index])
    && routeContainsChannelPin(
      collapsed,
      axis === 'x' ? 'channelX' : 'channelY',
      coordinate,
    );
}

function routeMatchesPresetFamily(preset, points, from, to) {
  const collapsed = normalizeRoutePoints(points.map((point) => [...point]));
  const segments = routeSegments(collapsed);
  if (preset === 'straight') return collapsed.length === 2;
  if (preset === 'drop') {
    if (from.lane === to.lane) return false;
    if (collapsed.length === 2 && segments[0]?.orientation === 'vertical') return true;
    const upper = from.cy <= to.cy ? from : to;
    const lower = upper === from ? to : from;
    return segments.some(({ start, orientation }) => (
      orientation === 'horizontal'
      && start[1] >= upper.y + upper.height - 0.0001
      && start[1] <= lower.y + 0.0001
      && corridorTopologyMatches(points, 'y', start[1])
    ));
  }
  if (preset === 'outside-right' || preset === 'return-left') {
    const boundary = preset === 'outside-right'
      ? Math.max(from.x + from.width, to.x + to.width)
      : Math.min(from.x, to.x);
    return segments.some(({ start, orientation }) => (
      orientation === 'vertical'
      && (preset === 'outside-right'
        ? start[0] > boundary + 0.0001
        : start[0] < boundary - 0.0001)
      && corridorTopologyMatches(points, 'x', start[0])
    ));
  }
  if (preset === 'bottom-channel' || preset === 'up-channel') {
    const boundary = preset === 'bottom-channel'
      ? Math.max(from.y + from.height, to.y + to.height)
      : Math.min(from.y, to.y);
    return segments.some(({ start, orientation }) => (
      orientation === 'horizontal'
      && (preset === 'bottom-channel'
        ? start[1] > boundary + 0.0001
        : start[1] < boundary - 0.0001)
      && corridorTopologyMatches(points, 'y', start[1])
    ));
  }
  return false;
}

function routeContainsChannelPin(points, field, value) {
  return points.slice(0, -1).some((start, index) => {
    const end = points[index + 1];
    if (field === 'channelX') {
      return start[0] === value
        && end[0] === value
        && Math.abs(end[1] - start[1]) > 0.0001;
    }
    return start[1] === value
      && end[1] === value
      && Math.abs(end[0] - start[0]) > 0.0001;
  });
}

function validateReadablePinnedGeometry() {
  if (workflow.schema_version !== 2) return;
  for (const edge of workflow.edges) {
    if (!nodes.has(edge.from) || !nodes.has(edge.to)) continue;
    validateReadableRouteControls(edge);
    const edgeName = workflowEdgeName(edge);
    const edgeIndex = sourceIndexes.edges.get(edge);
    if (Array.isArray(edge.labelAt)) {
      const rect = labelRectFor(edge, workflow.edges.indexOf(edge));
      if (rect && (rect.x < 0 || rect.y < 0)) {
        throwExplicitPinConflict(edge, 'viewBox-origin containment', {
          conflictingPins: [{
            edge: edgeName,
            field: 'labelAt',
            path: `/edges/${edgeIndex}/labelAt`,
            value: [...edge.labelAt],
          }],
          offendingRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
          minimumCoordinate: 0,
        }, [verifiedLabelAtNudge(edge)]);
      }
    }
    const negativeViaIndex = asArray(edge.via).findIndex(([x, y]) => x < 0 || y < 0);
    const negativeRoutePin = negativeViaIndex >= 0
      ? {
          field: 'via',
          path: `/edges/${edgeIndex}/via/${negativeViaIndex}`,
          value: [...edge.via[negativeViaIndex]],
        }
      : edge.channelX < 0
        ? { field: 'channelX', path: `/edges/${edgeIndex}/channelX`, value: edge.channelX }
        : edge.channelY < 0
          ? { field: 'channelY', path: `/edges/${edgeIndex}/channelY`, value: edge.channelY }
          : null;
    if (negativeRoutePin) {
      throwExplicitPinConflict(edge, 'viewBox-origin containment', {
        conflictingPins: [{ edge: edgeName, ...negativeRoutePin }],
        minimumCoordinate: 0,
      }, [
        verifiedAutomaticRouteFix(edge),
        verifiedAutomaticRouteFix(edge, { clearSides: true }),
      ]);
    }
    const hasPinnedRoute = Array.isArray(edge.via)
      || edge.channelX !== undefined
      || edge.channelY !== undefined;
    const points = pathFor(edge).points;
    if (hasPinnedRoute) {
      const invalidPointIndex = points.findIndex((point) => (
        !Array.isArray(point) || point.length !== 2 || !isFinitePoint(...point)
      ));
      if (invalidPointIndex !== -1) {
        const alternatives = verifiedRouteGeometryPinAlternatives(edge);
        throwExplicitPinConflict(edge, 'finite route coordinates', {
          conflictingPins: alternatives.conflictingPins,
          pointIndex: invalidPointIndex,
          point: points[invalidPointIndex],
        }, alternatives.supportedFixes);
      }
      for (let segmentIndex = 0; segmentIndex < points.length - 1; segmentIndex += 1) {
        const start = points[segmentIndex];
        const end = points[segmentIndex + 1];
        const dx = Math.abs(end[0] - start[0]);
        const dy = Math.abs(end[1] - start[1]);
        if (dx <= 0.0001 && dy <= 0.0001) {
          const duplicateFix = Array.isArray(edge.via) && edge.via.length
            ? verifiedEdgeFix(
              edge,
              `remove duplicate via[${Math.min(segmentIndex, edge.via.length - 1)}] and keep the remaining authored pins unchanged`,
              (candidate) => candidate.via.splice(Math.min(segmentIndex, candidate.via.length - 1), 1),
            )
            : verifiedAutomaticRouteFix(edge);
          const alternatives = Array.isArray(edge.via)
            ? null
            : verifiedRouteGeometryPinAlternatives(edge);
          throwExplicitPinConflict(edge, 'non-zero route segments', {
            conflictingPins: alternatives?.conflictingPins
              || [authoredPinEvidence(edge, 'via')],
            segmentIndex,
            from: start,
            to: end,
          }, alternatives?.supportedFixes || [duplicateFix]);
        }
        if (dx > 0.0001 && dy > 0.0001) {
          const alternatives = verifiedRouteGeometryPinAlternatives(edge);
          throwExplicitPinConflict(edge, 'orthogonal route segments', {
            conflictingPins: alternatives.conflictingPins,
            segmentIndex,
            from: start,
            to: end,
          }, alternatives.supportedFixes);
        }
        const endpoint = segmentIndex === 0 || segmentIndex === points.length - 2;
        const minimumPx = points.length === 2 ? 28 : endpoint ? 8 : 16;
        const lengthPx = dx + dy;
        if (lengthPx + 0.0001 < minimumPx) {
          const alternatives = verifiedRouteGeometryPinAlternatives(edge);
          throwExplicitPinConflict(edge, endpoint ? '8px endpoint stub clearance' : '16px interior turn clearance', {
            conflictingPins: alternatives.conflictingPins,
            segmentIndex,
            position: segmentIndex === 0 ? 'source-stub' : segmentIndex === points.length - 2 ? 'target-stub' : 'interior',
            from: start,
            to: end,
            lengthPx,
            minimumPx,
          }, alternatives.supportedFixes);
        }
      }
      const { fromSide, toSide } = edgeSides(edge);
      if (Array.isArray(edge.via)) {
        const missingChannelPins = presentChannelPins(edge).filter((field) => (
          !routeContainsChannelPin(points, field, edge[field])
        ));
        if (missingChannelPins.length) {
          const candidateFields = ['via', ...missingChannelPins];
          const alternatives = verifiedPinRemovalAlternatives(
            edge,
            candidateFields,
            'and replan the remaining explicit route assertions',
          );
          throwExplicitPinConflict(edge, 'channel pin preservation', {
            route: edge.route || 'auto',
            conflictingPins: conflictPinsFromRemovalSets(
              edge,
              alternatives.removalSets,
              candidateFields,
            ),
            points: points.map((point) => [...point]),
          }, alternatives.supportedFixes);
        }
      }
      if (edge.route
        && edge.route !== 'auto'
        && !routeMatchesPresetFamily(
          edge.route,
          points,
          nodes.get(edge.from),
          nodes.get(edge.to),
        )) {
        const authoredEdgeIndex = workflow.edges.indexOf(edge);
        const alternatives = verifiedPinReferenceAlternatives([
          { edge, edgeIndex: authoredEdgeIndex, field: 'route' },
          ...presentRouteGeometryFields(edge)
            .map((field) => ({ edge, edgeIndex: authoredEdgeIndex, field })),
        ], 'and keep the remaining verified route assertions');
        throwExplicitPinConflict(edge, 'route preset compatibility', {
          route: edge.route,
          conflictingPins: alternatives.conflictingPins,
          points: points.map((point) => [...point]),
        }, alternatives.supportedFixes);
      }
      if (!routeHonorsEndpointSides(points, fromSide, toSide)) {
        const mismatchedSideFields = [
          ...(edge.fromSide && edge.fromSide !== 'auto'
            && !endpointSideIsHonored(points, fromSide, 'source') ? ['fromSide'] : []),
          ...(edge.toSide && edge.toSide !== 'auto'
            && !endpointSideIsHonored(points, toSide, 'target') ? ['toSide'] : []),
        ];
        const candidateFields = [
          ...mismatchedSideFields,
          ...presentRouteGeometryFields(edge),
        ];
        const alternatives = verifiedPinRemovalAlternatives(
          edge,
          candidateFields,
          'and replan the remaining explicit pins',
        );
        throwExplicitPinConflict(edge, 'perpendicular endpoint-side direction', {
          conflictingPins: conflictPinsFromRemovalSets(
            edge,
            alternatives.removalSets,
            candidateFields,
          ),
          points: points.map((point) => [...point]),
          fromSide,
          toSide,
        }, alternatives.supportedFixes);
      }
      const nodeCollision = firstRouteNodeCollision(edge, points);
      if (nodeCollision) {
        const alternatives = verifiedRouteGeometryPinAlternatives(edge);
        throwExplicitPinConflict(edge, 'node clearance', {
          conflictingPins: alternatives.conflictingPins,
          ...nodeCollision,
        }, alternatives.supportedFixes);
      }
      const legendObstacle = workflowLegendRects().find((rect) => points.slice(0, -1).some((point, index) => (
        segmentIntersectsRect({ start: point, end: points[index + 1] }, rect)
      )));
      if (legendObstacle) {
        const alternatives = verifiedRouteGeometryPinAlternatives(edge);
        throwExplicitPinConflict(edge, 'legend clearance', {
          conflictingPins: alternatives.conflictingPins,
          points: points.map((point) => [...point]),
          legendObstacle,
        }, alternatives.supportedFixes);
      }
      const compositionObstacle = workflowSceneLabelObstacles().find((rect) => (
        points.slice(0, -1).some((point, index) => (
          segmentIntersectsRect({ start: point, end: points[index + 1] }, rect)
        ))
      ));
      if (compositionObstacle) {
        const alternatives = verifiedRouteGeometryPinAlternatives(edge);
        throwExplicitPinConflict(edge, 'lane/phase/group label clearance', {
          conflictingPins: alternatives.conflictingPins,
          points: points.map((point) => [...point]),
          compositionObstacle,
        }, alternatives.supportedFixes);
      }
      const [frameRun] = collectBorderRuns({
        routedRelations: [{ points }],
        frames: workflowCompositionFrames(),
      });
      if (frameRun) {
        const alternatives = verifiedRouteGeometryPinAlternatives(edge);
        throwExplicitPinConflict(edge, 'structural-frame border clearance', {
          conflictingPins: alternatives.conflictingPins,
          points: points.map((point) => [...point]),
          frame: frameRun.frame?.id ?? frameRun.frameIndex,
          side: frameRun.side,
          overlapLengthPx: frameRun.overlapLength,
        }, alternatives.supportedFixes);
      }
    }

    if (edge.labelAt) {
      const rect = labelRectFor(edge, workflow.edges.indexOf(edge));
      const obstacle = rect && [...nodes.values()].find((node) => rectsOverlap(rect, node, -2));
      if (obstacle) {
        throwExplicitPinConflict(edge, 'edge-label node clearance', {
          conflictingPins: [authoredPinEvidence(edge, 'labelAt')],
          labelAt: [...edge.labelAt],
          labelRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
          obstacleNode: obstacle.id,
        }, [verifiedEdgeFix(
          edge,
          'remove labelAt so readable-v2 can use its verified automatic label placement',
          (candidate) => { delete candidate.labelAt; },
        )]);
      }
      const legendObstacle = rect && workflowLegendRects().find((legendRect) => (
        rectsOverlap(rect, legendRect)
      ));
      if (legendObstacle) {
        throwExplicitPinConflict(edge, 'edge-label legend clearance', {
          conflictingPins: [authoredPinEvidence(edge, 'labelAt')],
          labelAt: [...edge.labelAt],
          labelRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
          legendObstacle,
        }, [verifiedEdgeFix(
          edge,
          'remove labelAt so readable-v2 can use its verified automatic label placement',
          (candidate) => { delete candidate.labelAt; },
        )]);
      }
      const compositionObstacle = rect && workflowSceneLabelObstacles().find((candidate) => (
        rectsOverlap(rect, candidate)
      ));
      if (compositionObstacle) {
        throwExplicitPinConflict(edge, 'edge-label lane/phase/group clearance', {
          conflictingPins: [authoredPinEvidence(edge, 'labelAt')],
          labelAt: [...edge.labelAt],
          labelRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
          compositionObstacle,
        }, [verifiedEdgeFix(
          edge,
          `remove labelAt from edge "${workflowEdgeName(edge)}" so readable-v2 can use its verified automatic label placement`,
          (candidate) => { delete candidate.labelAt; },
        )]);
      }
    }
  }
  validateReadablePairwisePinConflicts();
}

function validateWorkflow() {
  const problems = [];
  if (workflow.schema_version !== 1 && workflow.schema_version !== 2) {
    problems.push('Workflow files must set "schema_version" to 1 or 2.');
  }
  if (workflow.diagram_type !== 'workflow') {
    problems.push(`Unsupported diagram_type "${workflow.diagram_type}". Expected "workflow".`);
  }
  if (!workflow.meta || !workflow.meta.title) {
    problems.push('Workflow files must include meta.title.');
  }
  if (!Array.isArray(workflow.lanes) || !workflow.lanes.length) {
    problems.push('Workflow files must include at least one lane.');
  }
  if (!Array.isArray(workflow.nodes)) {
    problems.push('Workflow files must include a nodes array.');
  }
  if (!Array.isArray(workflow.edges)) {
    problems.push('Workflow files must include an edges array.');
  }
  if (workflow.phases !== undefined && !Array.isArray(workflow.phases)) {
    problems.push('Workflow "phases" must be an array.');
  }
  if (workflow.groups !== undefined && !Array.isArray(workflow.groups)) {
    problems.push('Workflow "groups" must be an array.');
  }
  if (workflow.mainPath !== undefined && !Array.isArray(workflow.mainPath)) {
    problems.push('Workflow "mainPath" must be an array of node ids.');
  }
  if (workflow.cards !== undefined && !Array.isArray(workflow.cards)) {
    problems.push('Workflow "cards" must be an array.');
  }
  if (problems.length) {
    throwDiagnosticProblems('Workflow layout validation failed', problems, {
      subject: { diagramType: 'workflow' },
    });
  }

  enforceLegacyColumnCapacity();

  const laneIds = new Set(workflow.lanes.map((lane) => lane.id));
  if (laneIds.size !== workflow.lanes.length) {
    problems.push('Lane ids must be unique.');
  }
  if (nodes.size !== workflow.nodes.length) {
    problems.push('Node ids must be unique.');
  }
  const phaseIds = new Set(asArray(workflow.phases).map((phase) => phase.id));
  if (phaseIds.size !== asArray(workflow.phases).length) {
    problems.push('Phase ids must be unique.');
  }
  const groupIds = new Set(asArray(workflow.groups).map((group) => group.id));
  if (groupIds.size !== asArray(workflow.groups).length) {
    problems.push('Group ids must be unique.');
  }

  for (const node of nodes.values()) {
    if (!laneIds.has(node.lane)) {
      problems.push(`Node "${node.id}" uses unknown lane "${node.lane}".`);
      continue;
    }
    if (!Number.isInteger(node.col) || node.col < 0 || node.col >= layout.colXs.length) {
      problems.push(`Node "${node.id}" uses column ${node.col}, but valid columns are integers 0..${layout.colXs.length - 1}.`);
      continue;
    }
    if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) {
      problems.push(`Node "${node.id}" produced non-finite coordinates — check col, width, height, and yOffset are numbers.`);
      continue;
    }
    const estLabelW = textUnits(node.label) * 6.8;
    if (estLabelW > node.width + 6) {
      problems.push(`Label "${node.label}" (~${Math.round(estLabelW)}px) is wider than node "${node.id}" (${node.width}px) — shorten the label or increase node.width.`);
    }
    const brandRailProblem = brandTopRailProblem(node, node.width, nodeTextFit.labelMinimum);
    if (brandRailProblem) problems.push(brandRailProblem);
    const availableTextW = availableNodeTextWidth(node.width);
    for (const [field, value, minimum] of [
      ['Sublabel', node.sublabel, nodeTextFit.sublabelMinimum],
      ['Tag', node.tag, nodeTextFit.tagMinimum],
    ]) {
      if (!value) continue;
      const minimumW = minimumNodeTextWidth(value, minimum);
      if (minimumW > availableTextW) {
        problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but node "${node.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or increase node.width.`);
      }
    }

    const top = laneTop(node.lane);
    const contentTop = top + layout.laneTitleH + laneGroupHeaderH(node.lane);
    const laneRight = layout.laneX + layout.laneW;
    if (node.x < layout.laneX || node.x + node.width > laneRight) {
      problems.push(`Node "${node.id}" exceeds the horizontal bounds of lane "${node.lane}".`);
    }
    if (node.y < contentTop || node.y + node.height > top + laneHeight(node.lane)) {
      problems.push(`Node "${node.id}" collides with the title or boundary of lane "${node.lane}".`);
    }
  }

  const phaseRanges = [];
  for (const phase of asArray(workflow.phases)) {
    if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol)) {
      problems.push(`Phase "${phase.id}" must use integer fromCol/toCol values.`);
      continue;
    }
    if (phase.fromCol < 0 || phase.toCol >= layout.colXs.length || phase.fromCol > phase.toCol) {
      problems.push(`Phase "${phase.id}" uses invalid columns ${phase.fromCol}..${phase.toCol}; use an ordered range within 0..${layout.colXs.length - 1}.`);
    } else {
      phaseRanges.push(phase);
    }
    const estLabelW = textUnits(phase.label) * 5.6;
    const width = phaseSpan(phase).width;
    if (estLabelW > width + 8) {
      problems.push(`Phase label "${phase.label}" (~${Math.round(estLabelW)}px) is wider than its ${Math.round(width)}px span — shorten the label or widen the phase range.`);
    }
  }
  phaseRanges.sort((a, b) => a.fromCol - b.fromCol || a.toCol - b.toCol);
  for (let i = 0; i < phaseRanges.length; i += 1) {
    for (let j = i + 1; j < phaseRanges.length; j += 1) {
      const earlier = phaseRanges[i];
      const later = phaseRanges[j];
      if (later.fromCol > earlier.toCol) break;
      problems.push(`Phase "${later.id}" (${later.fromCol}..${later.toCol}) overlaps phase "${earlier.id}" (${earlier.fromCol}..${earlier.toCol}) — start at col ${earlier.toCol + 1} or later, or end the earlier phase at col ${later.fromCol - 1}.`);
    }
  }

  for (const group of asArray(workflow.groups)) {
    if (!laneIds.has(group.lane)) {
      problems.push(`Group "${group.id}" uses unknown lane "${group.lane}".`);
      continue;
    }
    if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)) {
      problems.push(`Group "${group.id}" must use integer fromCol/toCol values.`);
      continue;
    }
    if (group.fromCol < 0 || group.toCol >= layout.colXs.length || group.fromCol > group.toCol) {
      problems.push(`Group "${group.id}" uses invalid columns ${group.fromCol}..${group.toCol}; use an ordered range within 0..${layout.colXs.length - 1}.`);
    }
    const contained = [...nodes.values()].some((node) => node.lane === group.lane && node.col >= group.fromCol && node.col <= group.toCol);
    if (!contained) {
      problems.push(`Group "${group.id}" does not contain any nodes — align its lane/columns with the parallel or branch work it frames.`);
    }
  }

  const byLane = new Map();
  for (const node of nodes.values()) {
    byLane.set(node.lane, [...(byLane.get(node.lane) || []), node]);
  }
  for (const [lane, laneNodes] of byLane) {
    for (let i = 0; i < laneNodes.length; i += 1) {
      for (let j = i + 1; j < laneNodes.length; j += 1) {
        if (rectsOverlap(laneNodes[i], laneNodes[j], 8)) {
          problems.push(`Nodes "${laneNodes[i].id}" and "${laneNodes[j].id}" are less than 8px apart in lane "${lane}" — move one to another col, adjust yOffset, or reduce width/height.`);
        }
      }
    }
  }

  for (const edge of workflow.edges) {
    if (!nodes.has(edge.from)) problems.push(`Edge "${edge.label || edge.from}" references unknown source "${edge.from}".`);
    if (!nodes.has(edge.to)) problems.push(`Edge "${edge.label || edge.to}" references unknown target "${edge.to}".`);
    if (nodes.has(edge.from) && nodes.has(edge.to)) {
      const routed = pathFor(edge);
      if (routed.points.length === 2) {
        const [start, end] = routed.points;
        const segmentLength = Math.hypot(end[0] - start[0], end[1] - start[1]);
        if (segmentLength < 28) {
          problems.push(`Edge "${edge.from}" -> "${edge.to}" is too short (${Math.round(segmentLength)}px; minimum 28px) — move the nodes farther apart or use a verified orthogonal route with readable clearance.`);
        }
      }
    }
  }

  problems.push(...cleanEndpointSideProblems({
    relations: workflow.edges,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'workflow',
    relationCollection: 'edges',
    fromSideFor: (edge) => edgeSides(edge).fromSide,
    toSideFor: (edge) => edgeSides(edge).toSide,
    routeHint: 'keep automatic routing, or choose fromSide/toSide and via points whose first and final segments cross node borders perpendicularly',
  }));
  problems.push(...cleanFlowProblems({
    relations: workflow.edges,
    obstacles: nodes.values(),
    pathFor,
    diagramType: 'workflow',
    relationCollection: 'edges',
    obstacleKind: 'node',
    routeHint: 'adjust fromSide/toSide, set route/via or channel coordinates, or move the node to a clearer lane/column'
  }));
  problems.push(...cleanCrossingProblems({
    relations: workflow.edges,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'workflow',
    relationCollection: 'edges',
    profile: workflow.meta?.quality_profile,
    profileIsAuthoritative: true,
    mergeForwardCollinearWaypoints: workflow.schema_version === 2,
    routeHint: 'adjust route/via, bias, or channel coordinates so the edges use separate lane corridors'
  }));
  problems.push(...cleanAmbiguousCorridorProblems({
    relations: workflow.edges,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'workflow',
    relationCollection: 'edges',
    profile: workflow.meta?.quality_profile,
    profileIsAuthoritative: true,
    routeHint: 'adjust route/via, bias, or channel coordinates so unrelated edges do not visually merge'
  }));
  problems.push(...cleanBorderRunProblems({
    relations: workflow.edges,
    endpointIds: new Set(nodes.keys()),
    frames: workflowCompositionFrames(),
    pathFor,
    diagramType: 'workflow',
    relationCollection: 'edges',
    profile: workflow.meta?.quality_profile,
    profileIsAuthoritative: true,
    routeHint: 'adjust route/via, bias, or channel coordinates so the edge crosses the lane or group perpendicularly instead of following its border'
  }));
  problems.push(...cleanRouteRhythmProblems({
    relations: workflow.edges,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'workflow',
    relationCollection: 'edges',
    profile: workflow.meta?.quality_profile,
    profileIsAuthoritative: true,
    routeHint: 'adjust route/via, bias, or channel coordinates so each turn has a readable run-up'
  }));

  if (Array.isArray(workflow.mainPath)) {
    for (const id of workflow.mainPath) {
      if (!nodes.has(id)) {
        problems.push(`mainPath references unknown node "${id}".`);
      }
    }
    for (let i = 0; i < workflow.mainPath.length - 1; i += 1) {
      const fromId = workflow.mainPath[i];
      const toId = workflow.mainPath[i + 1];
      const from = nodes.get(fromId);
      const to = nodes.get(toId);
      if (!from || !to) continue;
      const linked = workflow.edges.some((edge) => edge.from === fromId && edge.to === toId);
      if (!linked) {
        problems.push(`mainPath step "${fromId}" -> "${toId}" has no matching edge — add the edge or remove the pair from mainPath.`);
      }
      if (to.col < from.col) {
        problems.push(`mainPath step "${fromId}" -> "${toId}" moves backward from col ${from.col} to ${to.col} — use a return edge outside mainPath for loops.`);
      }
    }
  }

  const labelRects = [];
  for (const [edgeIndex, edge] of workflow.edges.entries()) {
    const labelRect = labelRectFor(edge, edgeIndex);
    if (labelRect) labelRects.push(labelRect);
  }
  for (const rect of labelRects) {
    for (const node of nodes.values()) {
      if (rectsOverlap(rect, node, -2)) {
        problems.push(`Label "${rect.label}" overlaps node "${node.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, node, 'node')}`);
      }
    }
  }
  for (let i = 0; i < labelRects.length; i += 1) {
    for (let j = i + 1; j < labelRects.length; j += 1) {
      if (rectsOverlap(labelRects[i], labelRects[j], -2)) {
        problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy/labelSegment or route one relationship through a separate corridor.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}`);
      }
    }
  }
  problems.push(...cleanLabelRouteClearanceProblems({
    relations: workflow.edges,
    labels: labelRects,
    endpointIds: new Set(nodes.keys()),
    pathFor,
    diagramType: 'workflow',
    relationCollection: 'edges',
    profile: workflow.meta?.quality_profile,
    profileIsAuthoritative: true,
  }));

  if (workflow.schema_version === 1) {
    if (viewBox[0] < layout.laneX + layout.laneW + 16) {
      problems.push(`viewBox width ${viewBox[0]} clips the ${layout.laneW}px lanes — set meta.viewBox[0] to at least ${layout.laneX + layout.laneW + 16}.`);
    }
    if (legendY() + 18 > viewBox[1]) {
      problems.push(`Legend exceeds viewBox height ${viewBox[1]} — set meta.viewBox[1] to at least ${legendY() + 18}.`);
    }
  }

  if (problems.length) {
    throwDiagnosticProblems('Workflow layout validation failed', problems, {
      subject: { diagramType: 'workflow' },
    });
  }
}

function validateReadableInputsBeforeRouting() {
  if (workflow.schema_version !== 2) return;
  const fail = (diagnostic) => throwDiagnosticError(diagnostic.message, [diagnostic]);
  const unusedId = (base, used) => {
    for (let suffix = 2; ; suffix += 1) {
      const candidate = `${base}-${suffix}`;
      if (!used.has(candidate)) return candidate;
    }
  };
  const authoredLanes = [...workflow.lanes].sort((left, right) => (
    sourceIndexes.lanes.get(left) - sourceIndexes.lanes.get(right)
  ));
  const authoredNodes = [...workflow.nodes].sort((left, right) => (
    sourceIndexes.nodes.get(left) - sourceIndexes.nodes.get(right)
  ));
  const authoredEdges = [...workflow.edges].sort((left, right) => (
    sourceIndexes.edges.get(left) - sourceIndexes.edges.get(right)
  ));

  const firstLaneIndex = new Map();
  for (const lane of authoredLanes) {
    const laneIndex = sourceIndexes.lanes.get(lane);
    if (firstLaneIndex.has(lane.id)) {
      const message = `Workflow lane id "${lane.id}" is duplicated.`;
      const replacement = unusedId(lane.id, new Set(workflow.lanes.map(({ id }) => id)));
      const canonicalLaneIndex = workflow.lanes.indexOf(lane);
      const supportedFixes = acceptsFix((document) => {
        document.lanes[canonicalLaneIndex].id = replacement;
      }) ? [`rename /lanes/${laneIndex}/id to verified unique id "${replacement}"`] : [];
      fail({
        code: 'workflow/duplicate-lane-id',
        severity: 'error',
        message,
        subject: { diagramType: 'workflow', lane: lane.id, path: `/lanes/${laneIndex}/id` },
        evidence: {
          duplicateLaneId: lane.id,
          firstPath: `/lanes/${firstLaneIndex.get(lane.id)}/id`,
          duplicatePath: `/lanes/${laneIndex}/id`,
        },
        supportedFixes,
      });
    }
    firstLaneIndex.set(lane.id, laneIndex);
  }

  const firstNodeIndex = new Map();
  for (const node of authoredNodes) {
    const nodeIndex = sourceIndexes.nodes.get(node);
    if (firstNodeIndex.has(node.id)) {
      const message = `Workflow node id "${node.id}" is duplicated.`;
      const replacement = unusedId(node.id, new Set(workflow.nodes.map(({ id }) => id)));
      const canonicalNodeIndex = workflow.nodes.indexOf(node);
      const supportedFixes = acceptsFix((document) => {
        document.nodes[canonicalNodeIndex].id = replacement;
      }) ? [`rename /nodes/${nodeIndex}/id to verified unique id "${replacement}"`] : [];
      fail({
        code: 'workflow/duplicate-node-id',
        severity: 'error',
        message,
        subject: { diagramType: 'workflow', node: node.id, path: `/nodes/${nodeIndex}/id` },
        evidence: {
          duplicateNodeId: node.id,
          firstPath: `/nodes/${firstNodeIndex.get(node.id)}/id`,
          duplicatePath: `/nodes/${nodeIndex}/id`,
        },
        supportedFixes,
      });
    }
    firstNodeIndex.set(node.id, nodeIndex);
  }

  const availableNodeIds = [...nodes.keys()].sort(stableCompare);
  for (const edge of authoredEdges) {
    const edgeIndex = sourceIndexes.edges.get(edge);
    for (const [field, endpoint] of [['from', 'source'], ['to', 'target']]) {
      if (nodes.has(edge[field])) continue;
      const message = `Workflow edge "${workflowEdgeName(edge)}" references unknown ${endpoint} "${edge[field]}".`;
      const canonicalEdgeIndex = workflow.edges.indexOf(edge);
      const supportedFixes = availableNodeIds.flatMap((nodeId) => (
        acceptsFix((document) => {
          document.edges[canonicalEdgeIndex][field] = nodeId;
        })
          ? [`set /edges/${edgeIndex}/${field} to verified node id "${nodeId}"`]
          : []
      ));
      fail({
        code: 'workflow/unknown-edge-endpoint',
        severity: 'error',
        message,
        subject: {
          diagramType: 'workflow',
          edge: edge.id ?? null,
          path: `/edges/${edgeIndex}/${field}`,
          from: edge.from,
          to: edge.to,
        },
        evidence: {
          endpoint,
          unknownNodeId: edge[field],
          availableNodeIds,
        },
        supportedFixes,
      });
    }
  }
  const laneIds = new Set(workflow.lanes.map((lane) => lane.id));
  const availableLaneIds = [...laneIds].sort(stableCompare);
  const nodeSourceIndexes = new Map(authoredNodes.map((node) => [
    node.id,
    sourceIndexes.nodes.get(node),
  ]));

  const byLane = new Map();
  for (const authoredNode of authoredNodes) {
    const nodeIndex = sourceIndexes.nodes.get(authoredNode);
    const node = nodes.get(authoredNode.id);
    if (!laneIds.has(node.lane)) {
      const message = `Workflow node "${node.id}" uses unknown lane "${node.lane}".`;
      const canonicalNodeIndex = workflow.nodes.findIndex((candidate) => candidate.id === node.id);
      const supportedFixes = availableLaneIds.flatMap((laneId) => (
        acceptsFix((document) => {
          document.nodes[canonicalNodeIndex].lane = laneId;
        })
          ? [`set /nodes/${nodeIndex}/lane to verified lane id "${laneId}"`]
          : []
      ));
      fail({
        code: 'workflow/unknown-node-lane',
        severity: 'error',
        message,
        subject: { diagramType: 'workflow', node: node.id, path: `/nodes/${nodeIndex}/lane` },
        evidence: { unknownLaneId: node.lane, availableLaneIds },
        supportedFixes,
      });
    }
    if (!Number.isInteger(node.col) || node.col < 0 || node.col >= layout.colXs.length) {
      const message = `Workflow node "${node.id}" uses column ${node.col}, but valid columns are integers 0..${layout.colXs.length - 1}.`;
      const canonicalNodeIndex = workflow.nodes.findIndex((candidate) => candidate.id === node.id);
      const supportedFixes = layout.colXs.flatMap((_x, col) => (
        acceptsFix((document) => {
          document.nodes[canonicalNodeIndex].col = col;
        })
          ? [`set /nodes/${nodeIndex}/col to verified column ${col}`]
          : []
      ));
      fail({
        code: 'workflow/invalid-node-column',
        severity: 'error',
        message,
        subject: { diagramType: 'workflow', node: node.id, path: `/nodes/${nodeIndex}/col` },
        evidence: { actualColumn: node.col, minimumColumn: 0, maximumColumn: layout.colXs.length - 1 },
        supportedFixes,
      });
    }
    if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) {
      const message = `Workflow node "${node.id}" produced non-finite coordinates.`;
      fail({
        code: 'workflow/non-finite-node-geometry',
        severity: 'error',
        message,
        subject: { diagramType: 'workflow', node: node.id, path: `/nodes/${nodeIndex}` },
        evidence: {
          measuredRect: { x: node.x, y: node.y, width: node.width, height: node.height },
          authored: {
            col: authoredNode.col,
            width: authoredNode.width ?? null,
            height: authoredNode.height ?? null,
            yOffset: authoredNode.yOffset ?? null,
          },
        },
        supportedFixes: [],
      });
    }
    byLane.set(node.lane, [...(byLane.get(node.lane) || []), node]);
  }
  for (const [lane, laneNodes] of byLane) {
    for (let left = 0; left < laneNodes.length; left += 1) {
      for (let right = left + 1; right < laneNodes.length; right += 1) {
        if (rectsOverlap(laneNodes[left], laneNodes[right], 8)) {
          const leftNode = laneNodes[left];
          const rightNode = laneNodes[right];
          const rightIndex = nodeSourceIndexes.get(rightNode.id);
          const canonicalNodeIndex = workflow.nodes.findIndex((candidate) => (
            candidate.id === rightNode.id
          ));
          const supportedFixes = layout.colXs.flatMap((_x, col) => {
            if (col === rightNode.col) return [];
            return acceptsFix((document) => {
              document.nodes[canonicalNodeIndex].col = col;
            })
              ? [`set /nodes/${rightIndex}/col to verified free column ${col}`]
              : [];
          });
          const message = `Workflow nodes "${leftNode.id}" and "${rightNode.id}" are less than 8px apart in lane "${lane}".`;
          fail({
            code: 'workflow/node-overlap',
            severity: 'error',
            message,
            subject: { diagramType: 'workflow', node: rightNode.id, path: `/nodes/${rightIndex}` },
            evidence: {
              lane,
              minimumClearancePx: 8,
              nodes: [
                { id: leftNode.id, rect: { x: leftNode.x, y: leftNode.y, width: leftNode.width, height: leftNode.height } },
                { id: rightNode.id, rect: { x: rightNode.x, y: rightNode.y, width: rightNode.width, height: rightNode.height } },
              ],
            },
            supportedFixes,
          });
        }
      }
    }
  }
}

function gapYBetween(fromLane, toLane, bias = 0.5) {
  const a = laneTop(fromLane) + laneHeight(fromLane);
  const b = laneTop(toLane);
  return a + (b - a) * bias;
}

function spanForCols(fromCol, toCol, pad = 46, minimumWidth = 0) {
  const start = layout.colXs[fromCol] - pad;
  const end = layout.colXs[toCol] + pad;
  const width = Math.max(end - start, minimumWidth);
  if (fromCol === toCol && width > end - start) {
    return { x: start, width, cx: start + width / 2 };
  }
  const cx = (start + end) / 2;
  return { x: cx - width / 2, width, cx };
}

function phaseSpan(phase) {
  return spanForCols(
    phase.fromCol,
    phase.toCol,
    46,
    workflow.schema_version === 2 ? textUnits(phase.label) * 5.6 + 8 : 0,
  );
}

function groupSpan(group) {
  if (workflow.schema_version === 2) {
    return readableGroupBounds(workflow, group, layout.colXs);
  }
  return spanForCols(
    group.fromCol,
    group.toCol,
    50,
    0,
  );
}

function sameLaneAutoVia(start, end) {
  if (start[0] === end[0] || start[1] === end[1]) return [];
  const midX = (start[0] + end[0]) / 2;
  return [[midX, start[1]], [midX, end[1]]];
}

function routeClearsUnrelatedNodes(edge, points, clearance = 2) {
  const endpointIds = new Set([edge.from, edge.to]);
  for (const node of nodes.values()) {
    if (endpointIds.has(node.id)) continue;
    for (let index = 0; index < points.length - 1; index += 1) {
      if (segmentIntersectsRect({ start: points[index], end: points[index + 1] }, node, clearance)) {
        return false;
      }
    }
  }
  return true;
}

function firstRouteNodeCollision(edge, points) {
  const lastSegment = points.length - 2;
  for (const node of nodes.values()) {
    const endpointRole = node.id === edge.from
      ? 'source-endpoint'
      : node.id === edge.to ? 'target-endpoint' : 'unrelated';
    for (let segmentIndex = 0; segmentIndex <= lastSegment; segmentIndex += 1) {
      if (endpointRole === 'source-endpoint' && segmentIndex === 0) continue;
      if (endpointRole === 'target-endpoint' && segmentIndex === lastSegment) continue;
      const clearancePx = endpointRole === 'unrelated' ? 2 : 0;
      const from = points[segmentIndex];
      const to = points[segmentIndex + 1];
      if (segmentIntersectsRect({ start: from, end: to }, node, clearancePx)) {
        return {
          obstacleNode: node.id,
          obstacleRole: endpointRole,
          segmentIndex,
          from: [...from],
          to: [...to],
          clearancePx,
        };
      }
    }
  }
  return null;
}

function oneBendCrossLaneVia(edge, start, end, fromSide, toSide) {
  const fromVertical = fromSide === 'top' || fromSide === 'bottom';
  const toVertical = toSide === 'top' || toSide === 'bottom';
  if (fromVertical === toVertical) return null;

  const corner = fromVertical ? [start[0], end[1]] : [end[0], start[1]];
  const points = normalizeRoutePoints([start, corner, end]);
  if (points.length !== 3 || !routeHonorsEndpointSides(points, fromSide, toSide)) return null;

  const segmentsAreReadable = points.slice(0, -1).every((point, index) => (
    Math.hypot(
      points[index + 1][0] - point[0],
      points[index + 1][1] - point[1],
    ) >= 8
  ));
  if (!segmentsAreReadable || !routeClearsUnrelatedNodes(edge, points)) return null;
  return points.slice(1, -1);
}

const pathCache = new Map();
const readableSideCache = new Map();

function legacyAutomaticOneBendSides(edge, from, to) {
  const automaticRoute = !edge.via && (!edge.route || edge.route === 'auto');
  const automaticFrom = !edge.fromSide || edge.fromSide === 'auto';
  const automaticTo = !edge.toSide || edge.toSide === 'auto';
  if (!automaticRoute || !automaticFrom || !automaticTo || from.lane === to.lane) return null;
  if (from.cx === to.cx || from.cy === to.cy) return null;
  const verticalFrom = to.cy < from.cy ? 'top' : 'bottom';
  const horizontalTo = to.cx < from.cx ? 'right' : 'left';
  const horizontalFrom = to.cx < from.cx ? 'left' : 'right';
  const verticalTo = to.cy < from.cy ? 'bottom' : 'top';
  const candidates = [
    { fromSide: verticalFrom, toSide: horizontalTo },
    { fromSide: horizontalFrom, toSide: verticalTo },
  ];

  return candidates.find(({ fromSide, toSide }) => {
    const start = anchor(from, fromSide);
    const end = anchor(to, toSide);
    return oneBendCrossLaneVia(edge, start, end, fromSide, toSide);
  }) || null;
}

function readableAutomaticSides(edge, from, to) {
  const automaticRoute = !edge.via
    && edge.channelX === undefined
    && edge.channelY === undefined
    && (!edge.route || edge.route === 'auto');
  const authoredFrom = edge.fromSide && edge.fromSide !== 'auto' ? edge.fromSide : null;
  const authoredTo = edge.toSide && edge.toSide !== 'auto' ? edge.toSide : null;
  if (!automaticRoute || (authoredFrom && authoredTo)) return null;
  if (readableSideCache.has(edge)) return readableSideCache.get(edge);

  const preferred = [];
  const legacyPreferred = legacyAutomaticOneBendSides(edge, from, to);
  if (legacyPreferred) preferred.push(legacyPreferred);
  preferred.push({
    fromSide: authoredFrom || defaultFromSide(from, to),
    toSide: authoredTo || defaultToSide(from, to),
  });
  const sideOrder = ['right', 'bottom', 'left', 'top'];
  for (const fromSide of authoredFrom ? [authoredFrom] : sideOrder) {
    for (const toSide of authoredTo ? [authoredTo] : sideOrder) {
      preferred.push({ fromSide, toSide });
    }
  }

  const seen = new Set();
  const sidePairs = [];
  for (const candidate of preferred) {
    if (authoredFrom && candidate.fromSide !== authoredFrom) continue;
    if (authoredTo && candidate.toSide !== authoredTo) continue;
    const key = `${candidate.fromSide}:${candidate.toSide}`;
    if (seen.has(key)) continue;
    seen.add(key);
    sidePairs.push(candidate);
  }

  const naturalFromSide = authoredFrom || defaultFromSide(from, to);
  const naturalToSide = authoredTo || defaultToSide(from, to);
  const planFor = (candidate, pairOrdinal) => {
    const start = anchor(from, candidate.fromSide);
    const end = anchor(to, candidate.toSide);
    return {
      start,
      end,
      planned: readableAutomaticCandidateSet(
        edge,
        from,
        to,
        start,
        end,
        candidate.fromSide,
        candidate.toSide,
        {
          ordinalOffset: pairOrdinal * 9,
          naturalFromSide,
          naturalToSide,
        },
      ),
    };
  };

  const primary = sidePairs[0];
  if (primary) {
    const { planned } = planFor(primary, 0);
    if (planned.candidates.length) {
      readableSideCache.set(edge, primary);
      return primary;
    }
  }

  const candidates = [];
  for (const [pairOrdinal, candidate] of sidePairs.entries()) {
    const { planned } = planFor(candidate, pairOrdinal);
    candidates.push(...planned.candidates.map((route) => ({ ...route, ...candidate })));
  }
  candidates.sort((left, right) => compareCost(left.cost, right.cost));
  if (candidates.length) {
    const selected = {
      fromSide: candidates[0].fromSide,
      toSide: candidates[0].toSide,
    };
    readableSideCache.set(edge, selected);
    return selected;
  }
  readableSideCache.set(edge, null);
  return null;
}

function automaticOneBendSides(edge, from, to) {
  return workflow.schema_version === 2
    ? readableAutomaticSides(edge, from, to)
    : legacyAutomaticOneBendSides(edge, from, to);
}

const OUTWARD_SIDE_VECTOR = Object.freeze({
  left: [-1, 0],
  right: [1, 0],
  top: [0, -1],
  bottom: [0, 1],
});

function outwardStub(point, side, distance = 16) {
  const [dx, dy] = OUTWARD_SIDE_VECTOR[side] || [0, 0];
  return [point[0] + dx * distance, point[1] + dy * distance];
}

function orthogonalRoute(points) {
  return points.every((point, index) => {
    if (!Array.isArray(point) || point.length !== 2 || !isFinitePoint(...point)) return false;
    if (index === 0) return true;
    const previous = points[index - 1];
    const dx = Math.abs(point[0] - previous[0]);
    const dy = Math.abs(point[1] - previous[1]);
    return (dx <= 0.0001) !== (dy <= 0.0001);
  });
}

function routeClearsEndpointNodes(points, from, to) {
  const lastSegment = points.length - 2;
  for (let index = 0; index <= lastSegment; index += 1) {
    const segment = { start: points[index], end: points[index + 1] };
    if (index > 0 && segmentIntersectsRect(segment, from)) return false;
    if (index < lastSegment && segmentIntersectsRect(segment, to)) return false;
  }
  return true;
}

function routeMeetsHardRhythm(points) {
  if (points.length === 2) {
    return Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]) + 0.0001 >= 28;
  }
  return points.slice(0, -1).every((point, index) => {
    const length = Math.abs(points[index + 1][0] - point[0]) + Math.abs(points[index + 1][1] - point[1]);
    const endpoint = index === 0 || index === points.length - 2;
    return length + 0.0001 >= (endpoint ? 8 : 16);
  });
}

function routeLabelClearsNodes(edge, points) {
  if (!edge.label || edge.labelAt) return true;
  const [lx, ly] = workflowEdgeLabelPoint(edge, points);
  const rect = {
    x: lx - workflowLabelWidth(edge.label) / 2,
    y: ly - 10,
    width: workflowLabelWidth(edge.label),
    height: 14,
  };
  return [...nodes.values()].every((node) => !rectsOverlap(rect, node, -2));
}

function candidateLabelRect(edge, points) {
  if (!edge.label) return null;
  const [lx, ly] = workflowEdgeLabelPoint(edge, points);
  const width = workflowLabelWidth(edge.label);
  return { x: lx - width / 2, y: ly - 10, width, height: 14 };
}

function labelRouteClearanceDeficit(edge, points, threshold = 8) {
  const candidateLabel = candidateLabelRect(edge, points);
  let deficit = 0;
  for (const [otherEdge, routed] of pathCache) {
    const otherIndex = workflow.edges.indexOf(otherEdge);
    const otherLabel = labelRectFor(otherEdge, otherIndex);
    if (candidateLabel) {
      for (let index = 0; index < routed.points.length - 1; index += 1) {
        const clearance = segmentRectClearance({
          start: routed.points[index],
          end: routed.points[index + 1],
        }, candidateLabel);
        if (clearance != null) deficit += Math.max(0, threshold - clearance);
      }
    }
    if (otherLabel) {
      for (let index = 0; index < points.length - 1; index += 1) {
        const clearance = segmentRectClearance({
          start: points[index],
          end: points[index + 1],
        }, otherLabel);
        if (clearance != null) deficit += Math.max(0, threshold - clearance);
      }
    }
  }
  return deficit;
}

function routeClearsPlacedLabels(edge, points) {
  const candidateLabel = candidateLabelRect(edge, points);
  for (const [otherEdge, routed] of pathCache) {
    const otherIndex = workflow.edges.indexOf(otherEdge);
    const otherLabel = labelRectFor(otherEdge, otherIndex);
    if (candidateLabel && otherLabel && rectsOverlap(candidateLabel, otherLabel, -2)) return false;
    if (candidateLabel) {
      for (let index = 0; index < routed.points.length - 1; index += 1) {
        const clearance = segmentRectClearance({
          start: routed.points[index],
          end: routed.points[index + 1],
        }, candidateLabel);
        if (clearance != null && clearance + 0.0001 < 4) return false;
      }
    }
    if (otherLabel) {
      for (let index = 0; index < points.length - 1; index += 1) {
        const clearance = segmentRectClearance({
          start: points[index],
          end: points[index + 1],
        }, otherLabel);
        if (clearance != null && clearance + 0.0001 < 4) return false;
      }
    }
  }
  return true;
}

function routeClearsLegend(edge, points) {
  if (!workflowLegendEntries.length) return true;
  const legendRects = workflowLegendRects();
  for (const rect of legendRects) {
    for (let index = 0; index < points.length - 1; index += 1) {
      if (segmentIntersectsRect({ start: points[index], end: points[index + 1] }, rect)) return false;
    }
    const label = candidateLabelRect(edge, points);
    if (label && rectsOverlap(label, rect)) return false;
  }
  return true;
}

function routeClearsSceneLabelObstacles(edge, points) {
  const label = candidateLabelRect(edge, points);
  for (const obstacle of workflowSceneLabelObstacles()) {
    for (let index = 0; index < points.length - 1; index += 1) {
      if (segmentIntersectsRect({ start: points[index], end: points[index + 1] }, obstacle)) {
        return false;
      }
    }
    if (label && rectsOverlap(label, obstacle)) return false;
  }
  return true;
}

function routeClearsFrameBorders(points) {
  return collectBorderRuns({
    routedRelations: [{ points }],
    frames: workflowCompositionFrames(),
  }).length === 0;
}

function routeExtentCoordinates(edge, points) {
  const coordinates = [...points];
  if (!edge.labelAt) {
    const label = candidateLabelRect(edge, points);
    if (label) {
      coordinates.push([label.x, label.y], [label.x + label.width, label.y + label.height]);
    }
  }
  return coordinates;
}

function routeFitsCanvasOrigin(edge, points) {
  return routeExtentCoordinates(edge, points).every(([x, y]) => x >= 0 && y >= 0);
}

function readableCandidateIsFeasible(edge, points, from, to, fromSide, toSide) {
  return points.length >= 2
    && orthogonalRoute(points)
    && routeHonorsEndpointSides(points, fromSide, toSide)
    && routeMeetsHardRhythm(points)
    && routeClearsEndpointNodes(points, from, to)
    && routeClearsUnrelatedNodes(edge, points)
    && routeLabelClearsNodes(edge, points)
    && routeClearsPlacedLabels(edge, points)
    && routeClearsLegend(edge, points)
    && routeClearsSceneLabelObstacles(edge, points)
    && routeClearsFrameBorders(points)
    && routeFitsCanvasOrigin(edge, points);
}

function corridorViaY(start, end, fromSide, toSide, y) {
  const startStub = outwardStub(start, fromSide);
  const endStub = outwardStub(end, toSide);
  return [startStub, [startStub[0], y], [endStub[0], y], endStub];
}

function corridorViaX(start, end, fromSide, toSide, x) {
  const startStub = outwardStub(start, fromSide);
  const endStub = outwardStub(end, toSide);
  return [startStub, [x, startStub[1]], [x, endStub[1]], endStub];
}

function axisOverlapLength(a, b, c, d) {
  const horizontal = Math.abs(a[1] - b[1]) <= 0.0001
    && Math.abs(c[1] - d[1]) <= 0.0001
    && Math.abs(a[1] - c[1]) <= 0.0001;
  const vertical = Math.abs(a[0] - b[0]) <= 0.0001
    && Math.abs(c[0] - d[0]) <= 0.0001
    && Math.abs(a[0] - c[0]) <= 0.0001;
  if (!horizontal && !vertical) return 0;
  const axis = horizontal ? 0 : 1;
  return Math.max(0, Math.min(Math.max(a[axis], b[axis]), Math.max(c[axis], d[axis]))
    - Math.max(Math.min(a[axis], b[axis]), Math.min(c[axis], d[axis])));
}

function properAxisCrossing(a, b, c, d) {
  const firstHorizontal = Math.abs(a[1] - b[1]) <= 0.0001;
  const secondHorizontal = Math.abs(c[1] - d[1]) <= 0.0001;
  if (firstHorizontal === secondHorizontal) return false;
  const horizontal = firstHorizontal ? [a, b] : [c, d];
  const vertical = firstHorizontal ? [c, d] : [a, b];
  const x = vertical[0][0];
  const y = horizontal[0][1];
  return x > Math.min(horizontal[0][0], horizontal[1][0]) + 0.0001
    && x < Math.max(horizontal[0][0], horizontal[1][0]) - 0.0001
    && y > Math.min(vertical[0][1], vertical[1][1]) + 0.0001
    && y < Math.max(vertical[0][1], vertical[1][1]) - 0.0001;
}

function routeInteractionMetrics(edge, points) {
  let properCrossingCount = 0;
  let sharedCorridorPx = 0;
  for (const [otherEdge, routed] of pathCache) {
    if ([edge.from, edge.to].some((id) => id === otherEdge.from || id === otherEdge.to)) continue;
    for (let left = 0; left < points.length - 1; left += 1) {
      for (let right = 0; right < routed.points.length - 1; right += 1) {
        if (properAxisCrossing(points[left], points[left + 1], routed.points[right], routed.points[right + 1])) {
          properCrossingCount += 1;
        }
        sharedCorridorPx += axisOverlapLength(
          points[left], points[left + 1], routed.points[right], routed.points[right + 1],
        );
      }
    }
  }
  return { properCrossingCount, sharedCorridorPx };
}

function automaticForwardReversePx(edge, points) {
  const from = nodes.get(edge.from);
  const to = nodes.get(edge.to);
  if (!from || !to || ['return', 'error'].includes(edge.role) || to.col <= from.col) return 0;
  return points.slice(0, -1).reduce((total, point, index) => (
    total + Math.max(0, point[0] - points[index + 1][0])
  ), 0);
}

function readableCandidateCost(
  edge,
  points,
  ordinal,
  naturalFromSide,
  naturalToSide,
) {
  const interaction = routeInteractionMetrics(edge, points);
  const segmentLengths = points.slice(0, -1).map((point, index) => (
    Math.abs(points[index + 1][0] - point[0]) + Math.abs(points[index + 1][1] - point[1])
  ));
  const routeLength = segmentLengths.reduce((total, length) => total + length, 0);
  const directLength = Math.abs(points.at(-1)[0] - points[0][0]) + Math.abs(points.at(-1)[1] - points[0][1]);
  const interiorPreferred28Deficit = segmentLengths.slice(1, -1)
    .reduce((total, length) => total + Math.max(0, 28 - length), 0);
  const xs = points.map(([x]) => x);
  const ys = points.map(([, y]) => y);
  const canvasGrowthPx = Math.max(0, -Math.min(...xs))
    + Math.max(0, Math.max(...xs) - minimumCanvasWidth)
    + Math.max(0, -Math.min(...ys))
    + Math.max(0, Math.max(...ys) - autoHeight);
  const from = nodes.get(edge.from);
  const to = nodes.get(edge.to);
  const naturalStart = anchor(from, naturalFromSide);
  const naturalEnd = anchor(to, naturalToSide);
  const portDisplacementPx = Math.abs(points[0][0] - naturalStart[0])
    + Math.abs(points[0][1] - naturalStart[1])
    + Math.abs(points.at(-1)[0] - naturalEnd[0])
    + Math.abs(points.at(-1)[1] - naturalEnd[1]);
  const legacyCoordinateDisplacement = Math.abs(from.cx - LEGACY_COLUMN_CENTERS[from.col])
    + Math.abs(to.cx - LEGACY_COLUMN_CENTERS[to.col]);
  return {
    automaticForwardReversePx: automaticForwardReversePx(edge, points),
    properCrossingCount: interaction.properCrossingCount,
    sharedCorridorPx: interaction.sharedCorridorPx,
    labelRouteClearanceDeficit: labelRouteClearanceDeficit(edge, points),
    interiorPreferred28Deficit,
    bendCount: Math.max(0, points.length - 2),
    stretchMilli: Math.round((directLength > 0 ? routeLength / directLength : 1) * 1000),
    canvasGrowthPx,
    portDisplacementMilli: Math.round(portDisplacementPx * 1000),
    legacyCoordinateDisplacement,
    stableCandidateOrdinal: ordinal,
  };
}

function compareCost(left, right) {
  for (const dimension of READABLE_CANDIDATE_COST_PRIORITY) {
    if ((left[dimension] || 0) !== (right[dimension] || 0)) {
      return (left[dimension] || 0) - (right[dimension] || 0);
    }
  }
  return 0;
}

function readableAutomaticCandidateSet(
  edge,
  from,
  to,
  start,
  end,
  fromSide,
  toSide,
  {
    ordinalOffset = 0,
    naturalFromSide = fromSide,
    naturalToSide = toSide,
  } = {},
) {
  const midX = (start[0] + end[0]) / 2;
  const laneGapY = from.lane === to.lane
    ? laneTop(from.lane) - 16
    : gapYBetween(from.lane, to.lane, edge.bias ?? 0.5);
  const topY = Math.max(8, Math.min(laneTop(from.lane), laneTop(to.lane)) - 16);
  const bottomY = Math.max(
    laneTop(from.lane) + laneHeight(from.lane),
    laneTop(to.lane) + laneHeight(to.lane),
  ) + 16;
  const outsideLeft = layout.laneX - 20;
  const outsideRight = layout.laneX + layout.laneW + 12;
  const rawCandidates = [
    { family: 'facing-straight', via: [] },
    { family: 'horizontal-then-vertical', via: [[end[0], start[1]]] },
    { family: 'vertical-then-horizontal', via: [[start[0], end[1]]] },
    { family: 'lane-gap-corridor', via: corridorViaY(start, end, fromSide, toSide, laneGapY) },
    { family: 'column-gap-corridor', via: corridorViaX(start, end, fromSide, toSide, midX) },
    { family: 'outside-left', via: corridorViaX(start, end, fromSide, toSide, outsideLeft) },
    { family: 'outside-right', via: corridorViaX(start, end, fromSide, toSide, outsideRight) },
    { family: 'top-corridor', via: corridorViaY(start, end, fromSide, toSide, topY) },
    { family: 'bottom-corridor', via: corridorViaY(start, end, fromSide, toSide, bottomY) },
  ];
  const candidates = rawCandidates.map((candidate, ordinal) => ({
    ...candidate,
    ordinal: ordinalOffset + ordinal,
    points: normalizeRoutePoints([start, ...candidate.via, end]),
  })).filter(({ points }) => (
    readableCandidateIsFeasible(edge, points, from, to, fromSide, toSide)
  )).map((candidate) => ({
    ...candidate,
    cost: readableCandidateCost(
      edge,
      candidate.points,
      candidate.ordinal,
      naturalFromSide,
      naturalToSide,
    ),
  })).sort((left, right) => compareCost(left.cost, right.cost));
  return { rawCandidates, candidates, outsideRight };
}

function readableAutomaticVia(edge, from, to, start, end, fromSide, toSide) {
  const { rawCandidates, candidates, outsideRight } = readableAutomaticCandidateSet(
    edge,
    from,
    to,
    start,
    end,
    fromSide,
    toSide,
  );

  if (candidates.length) return candidates[0].points.slice(1, -1);
  const outsideRightCandidate = rawCandidates.find(({ family }) => family === 'outside-right');
  if (outsideRightCandidate) {
    const currentPoints = normalizeRoutePoints([start, ...outsideRightCandidate.via, end]);
    const labelRect = candidateLabelRect(edge, currentPoints);
    let outsideRightMinX = outsideRight;
    for (const node of nodes.values()) {
      if (!labelRect || !rectsOverlap(labelRect, node, -2)) continue;
      const rightwardLabelDeficit = node.x + node.width - 2 - labelRect.x;
      if (rightwardLabelDeficit > 0) {
        outsideRightMinX = Math.max(
          outsideRightMinX,
          outsideRight + rightwardLabelDeficit * 2,
        );
      }
    }
    for (const [otherEdge, routed] of pathCache) {
      const otherIndex = workflow.edges.indexOf(otherEdge);
      const otherLabel = labelRectFor(otherEdge, otherIndex);
      if (labelRect && otherLabel && rectsOverlap(labelRect, otherLabel, -2)) {
        const rightwardLabelDeficit = otherLabel.x + otherLabel.width - 2 - labelRect.x;
        if (rightwardLabelDeficit > 0) {
          outsideRightMinX = Math.max(
            outsideRightMinX,
            outsideRight + rightwardLabelDeficit * 2,
          );
        }
      }
      if (!labelRect) continue;
      for (let index = 0; index < routed.points.length - 1; index += 1) {
        const segment = {
          start: routed.points[index],
          end: routed.points[index + 1],
        };
        const clearance = segmentRectClearance(segment, labelRect);
        if (clearance == null || clearance + 0.0001 >= 4) continue;
        const rightwardLabelDeficit = Math.max(segment.start[0], segment.end[0])
          + 4 - labelRect.x;
        if (rightwardLabelDeficit > 0) {
          outsideRightMinX = Math.max(
            outsideRightMinX,
            outsideRight + rightwardLabelDeficit * 2,
          );
        }
      }
    }
    outsideRightMinX = Math.ceil(outsideRightMinX * 1000) / 1000;
    let rightmostPlacedX = outsideRight;
    for (const node of nodes.values()) {
      rightmostPlacedX = Math.max(rightmostPlacedX, node.x + node.width);
    }
    for (const [otherEdge, routed] of pathCache) {
      for (const [x] of routed.points) rightmostPlacedX = Math.max(rightmostPlacedX, x);
      const otherLabel = labelRectFor(otherEdge, workflow.edges.indexOf(otherEdge));
      if (otherLabel) {
        rightmostPlacedX = Math.max(rightmostPlacedX, otherLabel.x + otherLabel.width);
      }
    }
    let probeGrowth = Math.max(
      32,
      labelRect?.width ?? 0,
      rightmostPlacedX + 16 - outsideRightMinX,
    );
    let lastInfeasibleX = outsideRight;
    for (let probe = 0; probe < 7; probe += 1) {
      if (outsideRightMinX > outsideRight + 0.0001) {
        const expandedPoints = normalizeRoutePoints([
          start,
          ...corridorViaX(start, end, fromSide, toSide, outsideRightMinX),
          end,
        ]);
        if (readableCandidateIsFeasible(edge, expandedPoints, from, to, fromSide, toSide)) {
          let feasibleX = outsideRightMinX;
          let feasiblePoints = expandedPoints;
          let infeasibleX = lastInfeasibleX;
          for (let refinement = 0;
            refinement < 53 && feasibleX - infeasibleX > 0.001;
            refinement += 1) {
            const midpointX = Math.ceil(((infeasibleX + feasibleX) / 2) * 1000) / 1000;
            if (midpointX >= feasibleX - 0.0001) break;
            const midpointPoints = normalizeRoutePoints([
              start,
              ...corridorViaX(start, end, fromSide, toSide, midpointX),
              end,
            ]);
            if (readableCandidateIsFeasible(
              edge,
              midpointPoints,
              from,
              to,
              fromSide,
              toSide,
            )) {
              feasibleX = midpointX;
              feasiblePoints = midpointPoints;
            } else {
              infeasibleX = midpointX;
            }
          }
          return feasiblePoints.slice(1, -1);
        }
        lastInfeasibleX = outsideRightMinX;
      }
      outsideRightMinX = Math.ceil((outsideRightMinX + probeGrowth) * 1000) / 1000;
      probeGrowth *= 2;
    }
  }
  const hasRelevantAbsolutePin = Array.isArray(edge.labelAt)
    || [...pathCache.keys()].some((otherEdge) => (
      Array.isArray(otherEdge.labelAt) || hasAbsoluteRoutePins(otherEdge)
    ));
  if (hasRelevantAbsolutePin) classifyFailedAutomaticCandidatePins(edge, rawCandidates);
  const horizontallyFacing = (
    fromSide === 'right' && toSide === 'left' && end[0] > start[0]
  ) || (
    fromSide === 'left' && toSide === 'right' && start[0] > end[0]
  );
  if (horizontallyFacing && from.col !== to.col) {
    const fromCol = Math.min(from.col, to.col);
    const toCol = Math.max(from.col, to.col);
    const requiredRankGap = from.width / 2 + 32 + to.width / 2;
    const actualRankGap = layout.colXs[toCol] - layout.colXs[fromCol];
    if (actualRankGap + 0.0001 < requiredRankGap) {
      throw new WorkflowLayoutFeedback({
        kind: 'rank-gap-minimum',
        fromCol,
        toCol,
        minimum: Math.ceil(requiredRankGap * 1000) / 1000,
        edge: edge.id ?? null,
        from: edge.from,
        to: edge.to,
        attemptedCandidateFamilies: rawCandidates.map(({ family }) => family),
        candidateCount: rawCandidates.length,
      });
    }
  }
  if (from.lane !== to.lane && layout.laneGap < 32) {
    throw new WorkflowLayoutFeedback({
      kind: 'lane-gap-minimum',
      minimum: 32,
      edge: edge.id ?? null,
      from: edge.from,
      to: edge.to,
      attemptedCandidateFamilies: rawCandidates.map(({ family }) => family),
      candidateCount: rawCandidates.length,
    });
  }
  const message = `Workflow edge "${workflowEdgeName(edge)}" has no feasible readable-v2 automatic route.`;
  throwDiagnosticError(message, [{
    code: 'workflow/solver-budget-exhausted',
    severity: 'error',
    message,
    subject: {
      diagramType: 'workflow',
      edge: edge.id ?? null,
      from: edge.from,
      to: edge.to,
    },
    evidence: {
      attemptedCandidateFamilies: rawCandidates.map(({ family }) => family),
      candidateCount: rawCandidates.length,
    },
    supportedFixes: [],
  }]);
}

function readablePresetVia(edge, from, to, start, end, fromSide, toSide) {
  const preset = edge.route;
  let via;
  switch (preset) {
    case 'straight':
      via = [];
      break;
    case 'drop': {
      const y = gapYBetween(from.lane, to.lane, edge.bias ?? 0.5);
      via = [[start[0], y], [end[0], y]];
      break;
    }
    case 'outside-right': {
      const x = layout.laneX + layout.laneW + 12;
      via = [[x, start[1]], [x, end[1]]];
      break;
    }
    case 'return-left': {
      const x = Math.min(from.x, to.x) - 28;
      via = [[x, start[1]], [x, end[1]]];
      break;
    }
    case 'bottom-channel': {
      const y = Math.max(from.y + from.height, to.y + to.height) + 32;
      via = [[start[0], y], [end[0], y]];
      break;
    }
    case 'up-channel': {
      const y = Math.min(from.y, to.y) - 28;
      via = [[start[0], y], [end[0], y]];
      break;
    }
    default:
      return readableAutomaticVia(edge, from, to, start, end, fromSide, toSide);
  }
  const points = normalizeRoutePoints([start, ...via, end]);
  if (readableCandidateIsFeasible(edge, points, from, to, fromSide, toSide)
    && routeMatchesPresetFamily(preset, points, from, to)) {
    return points.slice(1, -1);
  }
  const message = `Workflow edge "${workflowEdgeName(edge)}" cannot satisfy route preset "${preset}" under readable-v2 constraints (minimum 8px endpoint stubs, 16px interior turns, and 28px direct clearance).`;
  const edgeIndex = workflow.edges.indexOf(edge);
  const edgeName = workflowEdgeName(edge);
  const supportedFixes = [];
  for (const candidatePreset of ['straight', 'drop', 'outside-right', 'return-left', 'bottom-channel', 'up-channel']) {
    if (candidatePreset === preset) continue;
    if (acceptsFix((document) => {
      document.edges[edgeIndex].route = candidatePreset;
    })) {
      supportedFixes.push(`set edge "${edgeName}" route to verified preset "${candidatePreset}"`);
    }
  }
  if (acceptsFix((document) => {
    delete document.edges[edgeIndex].route;
  })) {
    supportedFixes.push(`remove route from edge "${edgeName}" so readable-v2 can use its verified automatic candidate`);
  }
  throwDiagnosticError(message, [{
    code: 'workflow/route-preset-conflict',
    severity: 'error',
    message,
    subject: {
      diagramType: 'workflow',
      edge: edge.id ?? null,
      from: edge.from,
      to: edge.to,
      route: preset,
    },
    evidence: {
      attemptedCandidateFamily: preset,
      points,
      fromSide,
      toSide,
      requiredEndpointStubPx: 8,
      requiredInteriorSegmentPx: 16,
      requiredDirectClearancePx: 28,
    },
    supportedFixes,
  }]);
}

function routeVia(
  edge,
  from,
  to,
  start,
  end,
  fromSide,
  toSide,
  { validateReadablePreset = true } = {},
) {
  if (edge.via) return edge.via;
  const hasCoordinatePins = edge.channelX !== undefined || edge.channelY !== undefined;
  if (workflow.schema_version === 2
    && edge.route
    && edge.route !== 'auto'
    && !hasCoordinatePins
    && validateReadablePreset) {
    return readablePresetVia(edge, from, to, start, end, fromSide, toSide);
  }
  switch (edge.route || 'auto') {
    case 'straight':
      return [];
    case 'drop': {
      const y = edge.channelY ?? gapYBetween(from.lane, to.lane, edge.bias ?? 0.5);
      return [[start[0], y], [end[0], y]];
    }
    case 'outside-right': {
      const x = edge.channelX ?? layout.laneX + layout.laneW + 12;
      return [[x, start[1]], [x, end[1]]];
    }
    case 'return-left': {
      const x = edge.channelX ?? Math.min(from.x, to.x) - 28;
      return [[x, start[1]], [x, end[1]]];
    }
    case 'bottom-channel': {
      const y = edge.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 32;
      return [[start[0], y], [end[0], y]];
    }
    case 'up-channel': {
      const y = edge.channelY ?? Math.min(from.y, to.y) - 28;
      return [[start[0], y], [end[0], y]];
    }
    case 'auto':
    default: {
      if (workflow.schema_version === 2) {
        if (edge.channelX !== undefined && edge.channelY !== undefined) {
          return [[edge.channelX, start[1]], [edge.channelX, edge.channelY], [end[0], edge.channelY]];
        }
        if (edge.channelX !== undefined) return [[edge.channelX, start[1]], [edge.channelX, end[1]]];
        if (edge.channelY !== undefined) return [[start[0], edge.channelY], [end[0], edge.channelY]];
        return readableAutomaticVia(edge, from, to, start, end, fromSide, toSide);
      }
      if (from.lane === to.lane) return sameLaneAutoVia(start, end);
      const oneBendVia = oneBendCrossLaneVia(edge, start, end, fromSide, toSide);
      if (oneBendVia) return oneBendVia;
      const y = gapYBetween(from.lane, to.lane, edge.bias ?? 0.5);
      return [[start[0], y], [end[0], y]];
    }
  }
}

function workflowEdgeLabelPoint(edge, points) {
  if (workflow.schema_version === 1) {
    if (edge.labelAt || Number.isInteger(edge.labelSegment) || points.length !== 3) {
      return labelPoint(edge, points);
    }
    const segmentLengths = [0, 1].map((index) => Math.hypot(
      points[index + 1][0] - points[index][0],
      points[index + 1][1] - points[index][1],
    ));
    const labelSegment = segmentLengths[0] >= segmentLengths[1] ? 0 : 1;
    const point = labelPoint({ ...edge, labelSegment }, points);
    if (points[labelSegment][0] === points[labelSegment + 1][0]) point[1] += 10;
    return point;
  }
  if (edge.labelAt || Number.isInteger(edge.labelSegment) || points.length <= 2) {
    return labelPoint(edge, points);
  }
  const segments = points.slice(0, -1).map((point, index) => ({
    index,
    horizontal: Math.abs(points[index + 1][1] - point[1]) <= 0.0001,
    length: Math.hypot(
      points[index + 1][0] - point[0],
      points[index + 1][1] - point[1],
    ),
  })).sort((left, right) => (
    Number(right.horizontal) - Number(left.horizontal)
    || right.length - left.length
    || left.index - right.index
  ));
  const labelSegment = segments[0]?.index ?? 0;
  const point = labelPoint({ ...edge, labelSegment }, points);
  if (points[labelSegment][0] === points[labelSegment + 1][0]) point[1] += 10;
  return point;
}

function edgeSides(edge) {
  const from = nodes.get(edge.from);
  const to = nodes.get(edge.to);
  const resolved = workflow.schema_version === 2 ? readableSideCache.get(edge) : null;
  if (resolved) return resolved;
  const oneBendSides = automaticOneBendSides(edge, from, to);
  if (oneBendSides) return oneBendSides;
  if (workflow.schema_version === 2
    && layout.channelLabelEdgeKeys?.has(stableValueKey(edge))
    && !edge.fromSide
    && !edge.toSide) {
    return { fromSide: 'top', toSide: 'top' };
  }
  return {
    fromSide: chosenSide(edge.fromSide, defaultFromSide(from, to)),
    toSide: chosenSide(edge.toSide, defaultToSide(from, to)),
  };
}

const automaticPorts = automaticPortSpread(workflow.edges, nodes, {
  sideFor: (edge, endpoint) => edgeSides(edge)[endpoint === 'source' ? 'fromSide' : 'toSide'],
});

function readableAutomaticRoute(edge, from, to, primarySides, primaryPorts) {
  const authoredFrom = edge.fromSide && edge.fromSide !== 'auto' ? edge.fromSide : null;
  const authoredTo = edge.toSide && edge.toSide !== 'auto' ? edge.toSide : null;
  const sideOrder = ['right', 'bottom', 'left', 'top'];
  const sidePairs = [primarySides];
  for (const fromSide of authoredFrom ? [authoredFrom] : sideOrder) {
    for (const toSide of authoredTo ? [authoredTo] : sideOrder) {
      sidePairs.push({ fromSide, toSide });
    }
  }

  const naturalFromSide = authoredFrom || defaultFromSide(from, to);
  const naturalToSide = authoredTo || defaultToSide(from, to);
  const seen = new Set();
  const plans = [];
  const feedback = [];
  let firstFailure = null;
  for (const candidateSides of sidePairs) {
    if (authoredFrom && candidateSides.fromSide !== authoredFrom) continue;
    if (authoredTo && candidateSides.toSide !== authoredTo) continue;
    const key = `${candidateSides.fromSide}:${candidateSides.toSide}`;
    if (seen.has(key)) continue;
    const pairOrdinal = seen.size;
    seen.add(key);
    const primary = pairOrdinal === 0;
    const start = primaryPorts?.from && primary
      ? primaryPorts.from
      : anchor(from, candidateSides.fromSide);
    const end = primaryPorts?.to && primary
      ? primaryPorts.to
      : anchor(to, candidateSides.toSide);
    const planned = readableAutomaticCandidateSet(
      edge,
      from,
      to,
      start,
      end,
      candidateSides.fromSide,
      candidateSides.toSide,
      {
        ordinalOffset: pairOrdinal * 9,
        naturalFromSide,
        naturalToSide,
      },
    );
    plans.push(...planned.candidates.map((candidate) => ({
      ...candidate,
      ...candidateSides,
    })));
    if (planned.candidates.length) continue;
    try {
      const expandedVia = withDiagnosticRecordingSuppressed(() => readableAutomaticVia(
        edge,
        from,
        to,
        start,
        end,
        candidateSides.fromSide,
        candidateSides.toSide,
      ));
      const expandedPoints = normalizeRoutePoints([start, ...expandedVia, end]);
      const outsideRightOrdinal = planned.rawCandidates.findIndex(({ family }) => (
        family === 'outside-right'
      ));
      const ordinal = pairOrdinal * 9 + Math.max(0, outsideRightOrdinal);
      plans.push({
        family: 'outside-right',
        ordinal,
        points: expandedPoints,
        cost: readableCandidateCost(
          edge,
          expandedPoints,
          ordinal,
          naturalFromSide,
          naturalToSide,
        ),
        ...candidateSides,
      });
    } catch (error) {
      if (error instanceof WorkflowLayoutFeedback) {
        feedback.push({ error, pairOrdinal });
      } else if (!firstFailure) {
        firstFailure = error;
      }
    }
  }

  plans.sort((left, right) => compareCost(left.cost, right.cost));
  if (plans.length) {
    const selected = plans[0];
    return {
      points: selected.points,
      fromSide: selected.fromSide,
      toSide: selected.toSide,
    };
  }

  const feedbackPriority = {
    'rank-gap-minimum': 0,
    'lane-gap-minimum': 1,
  };
  feedback.sort((left, right) => (
    (feedbackPriority[left.error.request?.kind] ?? 99)
      - (feedbackPriority[right.error.request?.kind] ?? 99)
    || left.pairOrdinal - right.pairOrdinal
  ));
  if (feedback.length) throw feedback[0].error;
  const authoredSideFields = [
    ...(authoredFrom ? ['fromSide'] : []),
    ...(authoredTo ? ['toSide'] : []),
  ];
  if (authoredSideFields.length) {
    const alternatives = verifiedPinRemovalAlternatives(
      edge,
      authoredSideFields,
      'so readable-v2 can replan the remaining endpoint-side pins',
    );
    const sourceAnchor = primaryPorts?.from || anchor(from, primarySides.fromSide);
    const targetAnchor = primaryPorts?.to || anchor(to, primarySides.toSide);
    const attemptedEvidence = firstFailure?.archifyDiagnostics?.[0]?.evidence || {};
    throwExplicitPinConflict(edge, 'readable route feasibility with authored endpoint sides', {
      conflictingPins: conflictPinsFromRemovalSets(
        edge,
        alternatives.removalSets,
        authoredSideFields,
      ),
      actualCoordinates: {
        sourceAnchor: [...sourceAnchor],
        targetAnchor: [...targetAnchor],
      },
      fromSide: primarySides.fromSide,
      toSide: primarySides.toSide,
      ...(attemptedEvidence.attemptedCandidateFamilies
        ? { attemptedCandidateFamilies: attemptedEvidence.attemptedCandidateFamilies }
        : {}),
      ...(attemptedEvidence.candidateCount !== undefined
        ? { candidateCount: attemptedEvidence.candidateCount }
        : {}),
    }, alternatives.supportedFixes);
  }
  if (firstFailure) throw firstFailure;
  throw new Error('readable-v2 automatic route enumeration produced no result');
}

function isReadableControlledRoute(edge) {
  return workflow.schema_version === 2 && (
    Array.isArray(edge.via)
    || edge.channelX !== undefined
    || edge.channelY !== undefined
    || (edge.route && edge.route !== 'auto')
  );
}

function readableControlledRoute(edge, from, to) {
  const authoredFrom = edge.fromSide && edge.fromSide !== 'auto' ? edge.fromSide : null;
  const authoredTo = edge.toSide && edge.toSide !== 'auto' ? edge.toSide : null;
  const naturalFromSide = authoredFrom || defaultFromSide(from, to);
  const naturalToSide = authoredTo || defaultToSide(from, to);
  const sideOrder = ['right', 'bottom', 'left', 'top'];
  const preferredPairs = [{
    fromSide: naturalFromSide,
    toSide: naturalToSide,
  }];
  for (const fromSide of authoredFrom ? [authoredFrom] : sideOrder) {
    for (const toSide of authoredTo ? [authoredTo] : sideOrder) {
      preferredPairs.push({ fromSide, toSide });
    }
  }

  const seen = new Set();
  const sidePairs = preferredPairs.filter(({ fromSide, toSide }) => {
    if (authoredFrom && fromSide !== authoredFrom) return false;
    if (authoredTo && toSide !== authoredTo) return false;
    const key = `${fromSide}:${toSide}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
  const hasAbsoluteRoutePins = Array.isArray(edge.via)
    || edge.channelX !== undefined
    || edge.channelY !== undefined;
  const candidates = [];
  const diagnosticCandidates = [];
  const materializedCandidates = [];
  for (const [ordinal, { fromSide, toSide }] of sidePairs.entries()) {
    const start = anchor(from, fromSide);
    const end = anchor(to, toSide);
    const via = routeVia(
      edge,
      from,
      to,
      start,
      end,
      fromSide,
      toSide,
      { validateReadablePreset: false },
    );
    const authoredPoints = [start, ...via, end];
    const points = hasAbsoluteRoutePins
      ? authoredPoints
      : normalizeRoutePoints(authoredPoints);
    const materialized = { points, fromSide, toSide, ordinal };
    materializedCandidates.push(materialized);
    if (points.length >= 2
      && points.every((point) => (
        Array.isArray(point) && point.length === 2 && isFinitePoint(...point)
      ))
      && routeHonorsEndpointSides(points, fromSide, toSide)) {
      diagnosticCandidates.push(materialized);
    }
    if (!readableCandidateIsFeasible(edge, points, from, to, fromSide, toSide)) continue;
    if (edge.route && edge.route !== 'auto' && !routeMatchesPresetFamily(
      edge.route,
      points,
      from,
      to,
    )) continue;
    if (presentChannelPins(edge).some((field) => (
      !routeContainsChannelPin(points, field, edge[field])
    ))) continue;
    candidates.push({
      points,
      fromSide,
      toSide,
      cost: readableCandidateCost(
        edge,
        points,
        ordinal,
        naturalFromSide,
        naturalToSide,
      ),
    });
  }
  candidates.sort((left, right) => compareCost(left.cost, right.cost));
  if (candidates.length) return candidates[0];

  // Absolute geometry is authoritative even when it is invalid. Preserve the
  // best endpoint-side inference so validation can diagnose the authored
  // segment or preset that actually failed instead of silently falling back to
  // default sides and changing the route's meaning.
  if (hasAbsoluteRoutePins) {
    return diagnosticCandidates[0] || materializedCandidates[0] || null;
  }

  // Preset-only routes retain their dedicated typed conflict (and verified
  // alternative search) when exhaustive side inference found no valid plan.
  const fallback = materializedCandidates[0];
  if (!fallback) return null;
  const fallbackVia = readablePresetVia(
    edge,
    from,
    to,
    fallback.points[0],
    fallback.points.at(-1),
    fallback.fromSide,
    fallback.toSide,
  );
  return {
    ...fallback,
    points: normalizeRoutePoints([
      fallback.points[0],
      ...fallbackVia,
      fallback.points.at(-1),
    ]),
  };
}

function pathFor(edge) {
  if (pathCache.has(edge)) return pathCache.get(edge);
  const from = nodes.get(edge.from);
  const to = nodes.get(edge.to);
  if (isReadableControlledRoute(edge)) {
    const planned = readableControlledRoute(edge, from, to);
    if (planned) {
      readableSideCache.set(edge, {
        fromSide: planned.fromSide,
        toSide: planned.toSide,
      });
      const routed = { d: polylinePath(planned.points), points: planned.points };
      pathCache.set(edge, routed);
      return routed;
    }
  }
  const ports = automaticPorts.get(edge);
  const { fromSide, toSide } = edgeSides(edge);
  const readableAutomatic = workflow.schema_version === 2
    && !Array.isArray(edge.via)
    && edge.channelX === undefined
    && edge.channelY === undefined
    && (!edge.route || edge.route === 'auto');
  if (readableAutomatic) {
    const planned = readableAutomaticRoute(
      edge,
      from,
      to,
      { fromSide, toSide },
      ports,
    );
    readableSideCache.set(edge, {
      fromSide: planned.fromSide,
      toSide: planned.toSide,
    });
    const routed = { d: polylinePath(planned.points), points: planned.points };
    pathCache.set(edge, routed);
    return routed;
  }
  const start = ports?.from || anchor(from, fromSide);
  const end = ports?.to || anchor(to, toSide);
  const authoredPoints = [start, ...routeVia(edge, from, to, start, end, fromSide, toSide), end];
  const hasAbsoluteRoutePins = Array.isArray(edge.via)
    || edge.channelX !== undefined
    || edge.channelY !== undefined;
  const points = workflow.schema_version === 2 && !hasAbsoluteRoutePins
    ? normalizeRoutePoints(authoredPoints)
    : authoredPoints;
  const routed = { d: polylinePath(points), points };
  pathCache.set(edge, routed);
  return routed;
}

function labelRectFor(edge, relationIndex) {
  if (!edge.label || !nodes.has(edge.from) || !nodes.has(edge.to)) return null;
  const [lx, ly] = workflowEdgeLabelPoint(edge, pathFor(edge).points);
  const width = workflowLabelWidth(edge.label);
  return {
    relation: edge,
    relationIndex,
    label: edge.label,
    x: lx - width / 2,
    y: ly - 10,
    width,
    height: 14,
    lx,
    ly,
  };
}

function measuredContentBounds() {
  let left = layout.laneX;
  let top = 27;
  let right = layout.laneX + layout.laneW;
  let bottom = legendY() + 18;
  const owners = {
    left: 'workflow lanes',
    top: asArray(workflow.phases).length ? 'phase header band' : 'workflow top padding',
    right: 'workflow lanes',
    bottom: workflowLegendEntries.length ? 'legend' : 'workflow lanes and bottom padding',
  };
  const includePoint = ([x, y], contributor) => {
    if (x < left) {
      left = x;
      owners.left = contributor;
    }
    if (y < top) {
      top = y;
      owners.top = contributor;
    }
    if (x > right) {
      right = x;
      owners.right = contributor;
    }
    if (y > bottom) {
      bottom = y;
      owners.bottom = contributor;
    }
  };
  const includeRect = (rect, contributor) => {
    includePoint([rect.x, rect.y], contributor);
    includePoint([rect.x + rect.width, rect.y + rect.height], contributor);
  };

  for (const node of nodes.values()) includeRect(node, `node ${node.id}`);
  for (const [index, edge] of workflow.edges.entries()) {
    if (!nodes.has(edge.from) || !nodes.has(edge.to)) continue;
    for (const point of pathFor(edge).points) includePoint(point, `edge ${edge.id || index}`);
    const label = labelRectFor(edge, index);
    if (label) includeRect(label, `edge ${edge.id || index} label mask`);
  }
  for (const phase of asArray(workflow.phases)) {
    if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol)
      || phase.fromCol < 0 || phase.toCol >= layout.colXs.length || phase.fromCol > phase.toCol) continue;
    const span = phaseSpan(phase);
    includeRect({ x: span.x, y: 27, width: span.width, height: 16 }, `phase ${phase.id}`);
  }
  for (const group of asArray(workflow.groups)) {
    if (!laneIndex.has(group.lane) || !Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)
      || group.fromCol < 0 || group.toCol >= layout.colXs.length || group.fromCol > group.toCol) continue;
    const span = groupSpan(group);
    includeRect({
      x: span.x,
      y: laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET,
      width: span.width,
      height: workflow.schema_version === 2
        ? laneHeight(group.lane) - layout.laneTitleH
          - GROUP_FRAME_TOP_INSET - GROUP_FRAME_BOTTOM_INSET
        : layout.laneH - layout.laneTitleH - 16,
    }, `group ${group.id}`);
    if (workflow.schema_version === 2) {
      const frameY = laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET;
      const labelBaseline = frameY + GROUP_LABEL_BASELINE_OFFSET;
      includeRect({
        x: span.x + 10,
        y: labelBaseline - GROUP_LABEL_MASK_ASCENT,
        width: textUnits(group.label) * 5.6,
        height: GROUP_LABEL_MASK_H,
      }, `group ${group.id} label`);
    }
  }
  if (workflowLegendEntries.length) {
    for (const rect of workflowLegendRects()) includeRect(rect, `legend ${rect.kind}`);
  }
  return {
    left,
    top,
    right,
    bottom,
    contributors: [...new Set([
      ...Object.values(owners),
      ...asArray(layout.widthContributors),
      ...asArray(layout.heightContributors),
    ])],
  };
}

function finalizeReadableViewBox() {
  if (workflow.schema_version !== 2) {
    requiredViewBox = [...viewBox];
    return;
  }
  const bounds = measuredContentBounds();
  requiredViewBox = [
    Math.max(minimumCanvasWidth, Math.ceil(bounds.right + 16)),
    Math.max(autoHeight, Math.ceil(bounds.bottom + 18)),
  ];
  const outsideOrigin = bounds.left < 0 || bounds.top < 0;
  if (outsideOrigin) {
    const hasAbsolutePins = workflow.edges.some((edge) => (
      Array.isArray(edge.via)
      || Array.isArray(edge.labelAt)
      || edge.channelX !== undefined
      || edge.channelY !== undefined
    ));
    const message = `Workflow geometry extends above or left of the viewBox origin (${Math.round(bounds.left)}, ${Math.round(bounds.top)}).`;
    throwDiagnosticError(message, [{
      code: hasAbsolutePins ? 'workflow/explicit-pin-conflict' : 'workflow/solver-budget-exhausted',
      severity: 'error',
      message,
      subject: { diagramType: 'workflow', path: '/meta/viewBox' },
      evidence: {
        actualViewBox: [...viewBox],
        requiredViewBox: [...requiredViewBox],
        contentBounds: [bounds.left, bounds.top, bounds.right, bounds.bottom],
        contributors: bounds.contributors,
      },
      supportedFixes: [],
    }]);
  }
  if (!workflow.meta?.viewBox) {
    viewBox = [...requiredViewBox];
    return;
  }
  const tooNarrow = viewBox[0] < requiredViewBox[0];
  const tooShort = viewBox[1] < requiredViewBox[1];
  if (!tooNarrow && !tooShort) return;
  const message = `Workflow viewBox ${viewBox[0]}×${viewBox[1]} cannot contain the readable-v2 layout; minimum ${requiredViewBox[0]}×${requiredViewBox[1]}.`;
  const supportedFixes = [];
  if (acceptsFix((document) => {
    document.meta.viewBox = [...requiredViewBox];
  })) {
    supportedFixes.push(`set meta.viewBox to at least [${requiredViewBox[0]}, ${requiredViewBox[1]}]`);
  }
  if (acceptsFix((document) => {
    delete document.meta.viewBox;
  })) {
    supportedFixes.push('omit meta.viewBox so the compiler can use its measured intrinsic canvas');
  }
  throwDiagnosticError(message, [{
    code: 'workflow/viewbox-capacity',
    severity: 'error',
    message,
    subject: { diagramType: 'workflow', path: '/meta/viewBox' },
    evidence: {
      actualViewBox: [...viewBox],
      requiredViewBox: [...requiredViewBox],
      contentBounds: [bounds.left, bounds.top, bounds.right, bounds.bottom],
      contributors: bounds.contributors,
    },
    supportedFixes,
  }]);
}

function renderLane(lane, index) {
  const y = laneTop(lane.id);
  const height = laneHeight(index);
  const exception = lane.variant === 'exception'
    ? `\n        <rect data-graph-role="structural-frame" data-composition-frame-kind="exception-lane" data-composition-frame-id="lane-${index}-exception" x="${layout.laneX + 6}" y="${y + 6}" width="${layout.laneW - 12}" height="${height - 12}" rx="8" class="c-security-group" stroke-width="1"/>`
    : '';
  const labelClass = lane.variant === 'exception' ? 't-security' : 't-dim';
  const prefix = lane.variant === 'exception' ? 'EX' : String(index + 1).padStart(2, '0');
  return `        <rect data-graph-role="structural-frame" data-composition-frame-kind="lane" data-composition-frame-id="lane-${index}" x="${layout.laneX}" y="${y}" width="${layout.laneW}" height="${height}" rx="10" class="c-lane" stroke-width="1"/>${exception}
        <text x="${layout.laneX + 14}" y="${y + 22}" class="${labelClass}" font-size="10" font-weight="600">${prefix} / ${esc(lane.label)}</text>`;
}

function renderPhase(phase) {
  const span = phaseSpan(phase);
  const accent = variantAccent(phase.variant);
  const [lineClass] = arrowClassMap[phase.variant || 'default'] || arrowClassMap.default;
  return `        <line x1="${span.x}" y1="35" x2="${span.x + span.width}" y2="35" class="${lineClass}" stroke-width="1.1"/>
        <rect x="${span.x}" y="27" width="${span.width}" height="16" rx="4" class="c-mask"/>
        <text x="${span.cx}" y="39" class="${accent}" font-size="8" font-weight="600" text-anchor="middle">${esc(phase.label)}</text>`;
}

function renderGroup(group, index) {
  const span = groupSpan(group);
  const y = laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET;
  const height = workflow.schema_version === 2
    ? laneHeight(group.lane) - layout.laneTitleH
      - GROUP_FRAME_TOP_INSET - GROUP_FRAME_BOTTOM_INSET
    : layout.laneH - layout.laneTitleH - 16;
  const cls = group.variant === 'security' ? 'c-security-group' : 'c-lane';
  const textClass = variantAccent(group.variant);
  const labelY = workflow.schema_version === 2 ? y + GROUP_LABEL_BASELINE_OFFSET : y + 14;
  return `        <rect data-graph-role="structural-frame" data-composition-frame-kind="group" data-composition-frame-id="group-${index}" x="${span.x}" y="${y}" width="${span.width}" height="${height}" rx="9" class="${cls}" stroke-width="1"/>
        <text x="${span.x + 10}" y="${labelY}" class="${textClass}" font-size="7" font-weight="600">${esc(group.label)}</text>`;
}

function renderNode(node) {
  const fill = componentFill[node.type] || 'c-external';
  const accent = componentText[node.type] || 't-muted';
  const hasSub = node.sublabel != null && node.sublabel !== '';
  const labelFontSize = fittedNodeFontSize(node.label, brandLabelFitWidth(node, node.width), nodeTextFit.labelPreferred, nodeTextFit.labelMinimum);
  const sublabelFontSize = hasSub
    ? fittedNodeFontSize(node.sublabel, node.width, nodeTextFit.sublabelPreferred, nodeTextFit.sublabelMinimum)
    : nodeTextFit.sublabelPreferred;
  const sub = hasSub
    ? `\n          <text data-detail="context" x="${node.cx}" y="${node.y + 38}" class="t-muted" font-size="${sublabelFontSize}" text-anchor="middle">${esc(node.sublabel)}</text>`
    : '';
  const tag = node.tag
    ? `\n        <text data-detail="fine" x="${node.cx}" y="${node.y + node.height - 12}" class="${accent}" font-size="${fittedNodeFontSize(node.tag, node.width, nodeTextFit.tagPreferred, nodeTextFit.tagMinimum)}" text-anchor="middle">${esc(node.tag)}</text>`
    : '';
  const brand = renderBrandMark(node, { x: node.x + node.width - 22, y: node.y + 6 });
  const passport = { kind: node.type, sublabel: node.sublabel, tag: node.tag, context: nodeContext(node), ...brandMetadataFor(node) };
  return `        <g ${focusNodeAttrs(node.id, node.label, passport, workflow.meta.locale)}>
          ${focusNodeTitle(node.label, passport)}
          <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="6" class="c-mask"/>
          <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="6" class="${fill}"${animateAttr(workflow.meta, 'node', nodeStep(node))} stroke-width="1.5"/>
          ${renderSemanticSigil(node.type, { x: node.x + 6, y: node.y + 6 })}${brand ? `\n          ${brand}` : ''}
          <text data-node-label=""${hasSub ? ' data-detail-anchor=""' : ''} x="${node.cx}" y="${node.y + 21}" class="t-primary" font-size="${labelFontSize}" font-weight="600" text-anchor="middle">${esc(node.label)}</text>${sub}${tag}
        </g>`;
}

function renderEdgePath(edge, index) {
  const [cls, marker] = arrowClassMap[edge.variant || 'default'] || arrowClassMap.default;
  const routed = pathFor(edge);
  const strokeWidth = edge.width || (edge.variant === 'emphasis' ? 1.8 : 1.4);
  return `        <path ${focusEdgeAttrs(edge.from, edge.to, edge.label, index, edge.id)} data-composition-points="${routePointsValue(routed.points)}" d="${routed.d}" class="${cls}"${animateAttr(workflow.meta, 'edge', edgeSteps.get(edge))} stroke-width="${strokeWidth}" marker-end="url(#${marker})"/>`;
}

function renderEdgeLabel(edge, index) {
  if (!edge.label) return '';
  const routed = pathFor(edge);
  const [lx, ly] = workflowEdgeLabelPoint(edge, routed.points);
  const labelW = workflowLabelWidth(edge.label);
  return `        <g data-detail="context" ${focusEdgeAttrs(edge.from, edge.to, edge.label, index, edge.id)}>
          <rect x="${lx - labelW / 2}" y="${ly - 10}" width="${labelW}" height="14" rx="3" class="c-mask"/>
          <text x="${lx}" y="${ly}" class="${variantAccent(edge.variant, { dashed: 't-database' })}" font-size="8" text-anchor="middle">${esc(edge.label)}</text>
        </g>`;
}

function renderLegend() {
  const obstacles = workflow.schema_version === 2
    ? relationshipLegendObstacles(workflow.edges, {
        pointsFor: (edge) => pathFor(edge).points,
        labelRectFor,
      })
    : [];
  return renderResolvedLegend({
    entries: workflowLegendEntries,
    locale: workflow.meta.locale,
    layout: workflowLegendLayout(obstacles),
    renderSwatch: (entry) => `<rect x="${entry.x}" y="${entry.baseline - 8}" width="14" height="9" rx="2" class="${componentFill[entry.kind] || 'c-external'}" stroke-width="1"/>`,
  });
}

function renderSvg() {
  return `      <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(workflow.meta, 'workflow diagram')}>
${svgAccessibleText(workflow.meta, 'workflow')}
${renderDefinitions()}

        <!-- Background Grid -->
        <rect width="100%" height="100%" fill="url(#grid)" />

        <!-- Swimlanes -->
${workflow.lanes.map(renderLane).join('\n\n')}

        <!-- Phase headers -->
${asArray(workflow.phases).map(renderPhase).join('\n')}

        <!-- Workflow groups -->
${asArray(workflow.groups).map(renderGroup).join('\n')}

        <!-- Edge paths -->
${workflow.edges.map(renderEdgePath).join('\n')}

        <!-- Nodes -->
${[...nodes.values()].map(renderNode).join('\n\n')}

        <!-- Edge labels -->
${workflow.edges.map(renderEdgeLabel).join('\n')}

        <!-- Legend -->
${renderLegend()}
      </svg>`;
}


  try {
    validateReadableInputsBeforeRouting();
    validateReadablePinnedGeometry();
    validateWorkflow();
    finalizeReadableViewBox();
    const svg = renderSvg();
    const receipt = {
      contract: layout.contract,
      viewBox: [...viewBox],
      requiredViewBox: [...requiredViewBox],
      columns: [...layout.colXs],
      nodes: [...nodes.values()].map((node) => ({
        id: node.id,
        lane: node.lane,
        col: node.col,
        x: node.x,
        y: node.y,
        width: node.width,
        height: node.height,
      })),
      edges: workflow.edges.map((edge) => ({
        id: edge.id ?? null,
        from: edge.from,
        to: edge.to,
        points: pathFor(edge).points.map((point) => [...point]),
      })),
      labels: workflow.edges.flatMap((edge) => {
        if (!edge.label || !nodes.has(edge.from) || !nodes.has(edge.to)) return [];
        const [x, y] = workflowEdgeLabelPoint(edge, pathFor(edge).points);
        return [{ edge: edge.id ?? null, label: edge.label, x, y, width: workflowLabelWidth(edge.label), height: 14 }];
      }),
      diagnostics: [],
    };
    return { ok: true, svg, receipt };
  } catch (error) {
    if (!Array.isArray(error?.archifyDiagnostics)) throw error;
    const diagnostics = error.archifyDiagnostics.map((diagnostic) => ({ ...diagnostic }));
    return compilerFailure(layout.contract, diagnostics, error.message);
  }
}

function feedbackFailure(request) {
  const message = `Workflow edge "${request.edge || `${request.from}->${request.to}`}" exhausted bounded readable-v2 layout feedback without a feasible automatic route.`;
  const diagnostics = [{
    code: 'workflow/solver-budget-exhausted',
    severity: 'error',
    message,
    subject: {
      diagramType: 'workflow',
      edge: request.edge,
      from: request.from,
      to: request.to,
    },
    evidence: {
      attemptedCandidateFamilies: request.attemptedCandidateFamilies,
      candidateCount: request.candidateCount,
    },
    supportedFixes: [],
  }];
  return compilerFailure('readable-v2', diagnostics, message);
}

function compileWorkflowWithFeedback({ workflow, qualityProfile, discoverFixes = true } = {}) {
  let layoutFeedback = {};
  for (let attempt = 0; attempt <= MAX_READABLE_LAYOUT_FEEDBACK_ROUNDS; attempt += 1) {
    try {
      return compileWorkflowInternal({
        workflow,
        qualityProfile,
        discoverFixes,
        layoutFeedback,
      });
    } catch (error) {
      if (!(error instanceof WorkflowLayoutFeedback)) throw error;
      const request = error.request;
      let nextFeedback = null;
      if (request.kind === 'rank-gap-minimum'
        && Number.isInteger(request.fromCol)
        && Number.isInteger(request.toCol)
        && Number.isFinite(request.minimum)) {
        const key = `${request.fromCol}:${request.toCol}`;
        const current = layoutFeedback.rankGapMinimums?.[key] ?? -Infinity;
        if (request.minimum > current + 0.0001) {
          nextFeedback = {
            ...layoutFeedback,
            rankGapMinimums: {
              ...(layoutFeedback.rankGapMinimums || {}),
              [key]: request.minimum,
            },
            rankGapContributors: {
              ...(layoutFeedback.rankGapContributors || {}),
              [key]: [
                `rank ${request.fromCol}→${request.toCol} route clearance`,
                `edge ${request.edge || `${request.from}->${request.to}`} route`,
              ],
            },
          };
        }
      } else if (request.kind === 'lane-gap-minimum'
        && Number.isFinite(request.minimum)
        && request.minimum > (layoutFeedback.laneGapMin ?? -Infinity) + 0.0001) {
        nextFeedback = {
          ...layoutFeedback,
          laneGapMin: request.minimum,
          laneGapContributors: [
            `edge ${request.edge || `${request.from}->${request.to}`} lane-gap route clearance`,
          ],
        };
      }
      if (!nextFeedback || attempt === MAX_READABLE_LAYOUT_FEEDBACK_ROUNDS) {
        return feedbackFailure(request);
      }
      layoutFeedback = nextFeedback;
    }
  }
  throw new Error('unreachable readable-v2 layout feedback state');
}

export function compileWorkflow({ workflow, qualityProfile } = {}) {
  return compileWorkflowWithFeedback({ workflow, qualityProfile });
}
```

## renderers/workflow/workflow-migration-geometry.mjs

```js
const TARGET_SCHEMA_VERSION = 2;

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

/**
 * Return the authored workflow as a schema-v2 document without its capacity
 * override. The compiler can use this projection to discover the intrinsic v2
 * rank plan before deciding whether an explicit viewBox needs to grow.
 */
export function intrinsicWorkflow(workflow) {
  const intrinsic = clone(workflow);
  intrinsic.schema_version = TARGET_SCHEMA_VERSION;
  intrinsic.meta = { ...intrinsic.meta };
  delete intrinsic.meta.viewBox;
  return intrinsic;
}

/**
 * Return a schema-v2 planning projection that removes authored route geometry
 * which may only become valid after its legacy X coordinates are remapped.
 * Rank-affecting automatic and straight relationships remain in the projection.
 */
export function planningWorkflow(workflow) {
  const planned = intrinsicWorkflow(workflow);
  planned.edges = planned.edges.flatMap((edge) => {
    const hasRoutedGeometry = Array.isArray(edge.via)
      || (edge.route && !['auto', 'straight'].includes(edge.route))
      || edge.channelX !== undefined
      || edge.channelY !== undefined;
    if (hasRoutedGeometry) return [];

    const automatic = {};
    for (const property of ['id', 'from', 'to', 'variant', 'role', 'width']) {
      if (edge[property] !== undefined) automatic[property] = edge[property];
    }
    if (edge.route === 'straight') automatic.route = 'straight';
    if (edge.labelAt === undefined && edge.label !== undefined) automatic.label = edge.label;
    return [automatic];
  });

  if (Array.isArray(planned.mainPath)) {
    const projectedPairs = new Set(planned.edges.map((edge) => `${edge.from}\u0000${edge.to}`));
    const projectionBreaksMainPath = planned.mainPath.some((from, index) => (
      index < planned.mainPath.length - 1
      && !projectedPairs.has(`${from}\u0000${planned.mainPath[index + 1]}`)
    ));
    if (projectionBreaksMainPath) delete planned.mainPath;
  }

  return planned;
}

function mappedNumber(value) {
  return Number(value.toFixed(6));
}

/**
 * Build a deterministic piecewise-linear mapping between corresponding legacy
 * and readable rank centers. Coordinates outside the rank span are extrapolated
 * using the nearest segment so explicitly authored outside corridors retain
 * their relative offset.
 */
export function createHorizontalRankMapper(oldColumns, newColumns) {
  if (
    !Array.isArray(oldColumns)
    || !Array.isArray(newColumns)
    || oldColumns.length !== newColumns.length
    || oldColumns.length < 2
    || !oldColumns.every(Number.isFinite)
    || !newColumns.every(Number.isFinite)
  ) {
    throw new TypeError('Horizontal rank mapping requires matching finite column arrays.');
  }
  for (let index = 1; index < oldColumns.length; index += 1) {
    if (oldColumns[index] <= oldColumns[index - 1] || newColumns[index] <= newColumns[index - 1]) {
      throw new TypeError('Horizontal rank mapping requires strictly increasing columns.');
    }
  }

  return (x) => {
    if (!Number.isFinite(x)) throw new TypeError('Horizontal rank mapping requires a finite x coordinate.');
    let segment = oldColumns.length - 2;
    if (x <= oldColumns[0]) {
      segment = 0;
    } else {
      for (let index = 0; index < oldColumns.length - 1; index += 1) {
        if (x <= oldColumns[index + 1]) {
          segment = index;
          break;
        }
      }
    }
    const oldSpan = oldColumns[segment + 1] - oldColumns[segment];
    const newSpan = newColumns[segment + 1] - newColumns[segment];
    const ratio = (x - oldColumns[segment]) / oldSpan;
    return mappedNumber(newColumns[segment] + ratio * newSpan);
  };
}

/**
 * Apply one horizontal coordinate mapping to every schema-v1 absolute X pin.
 * The caller owns the supplied workflow; this function reports an audit trail
 * for each changed coordinate in stable document order.
 */
export function mapExplicitCoordinates(workflow, mapX) {
  const changedCoordinates = [];
  const record = (path, owner, property) => {
    const from = owner[property];
    const to = mapX(from);
    owner[property] = to;
    if (to !== from) changedCoordinates.push({ path, from, to });
  };

  for (const [edgeIndex, edge] of workflow.edges.entries()) {
    if (Array.isArray(edge.via)) {
      for (const [pointIndex, point] of edge.via.entries()) {
        if (Array.isArray(point) && Number.isFinite(point[0])) {
          record(`/edges/${edgeIndex}/via/${pointIndex}/0`, point, 0);
        }
      }
    }
    if (Array.isArray(edge.labelAt) && Number.isFinite(edge.labelAt[0])) {
      record(`/edges/${edgeIndex}/labelAt/0`, edge.labelAt, 0);
    }
    if (Number.isFinite(edge.channelX)) {
      record(`/edges/${edgeIndex}/channelX`, edge, 'channelX');
    }
  }
  return changedCoordinates;
}

/**
 * Construct an independently owned schema-v2 candidate with all authored
 * absolute X pins mapped to the readable rank plan.
 */
export function createMappedWorkflowCandidate(workflow, oldColumns, newColumns) {
  const document = clone(workflow);
  document.schema_version = TARGET_SCHEMA_VERSION;
  const mapX = createHorizontalRankMapper(oldColumns, newColumns);
  const changedCoordinates = mapExplicitCoordinates(document, mapX);
  return { document, changedCoordinates };
}
```

## schemas

```

```

## schemas/README.md

# Archify JSON IR Schemas

Each typed renderer consumes a JSON intermediate representation (IR) validated
against one of the schemas in this folder before any layout work happens.

## Files

| Schema | Governs | Structural arrays |
|--------|---------|-------------------|
| `workflow.schema.json` | `diagram_type: "workflow"` | `lanes`, `phases`, `groups`, `mainPath`, `nodes`, `edges` |
| `sequence.schema.json` | `diagram_type: "sequence"` | `participants`, `segments`, `messages`, `activations` |
| `dataflow.schema.json` | `diagram_type: "dataflow"` | `stages`, `nodes`, `flows` |
| `lifecycle.schema.json` | `diagram_type: "lifecycle"` | `lanes`, `states`, `transitions` |
| `architecture.schema.json` | `diagram_type: "architecture"` | `components`, `boundaries`, `connections` |
| `common.schema.json` | shared `$defs` only (no top-level document) | — |

Every diagram schema requires `schema_version`, `diagram_type`, `meta` (with
`title`), and its structural arrays — except `segments`, `activations`, and
`cards`, which are optional — and sets `additionalProperties: false` at every
level, so unknown fields are rejected rather than silently ignored.

Every `meta` object also accepts `animation: "trace"` for opt-in SVG/CSS motion
in generated HTML. Omit it, or set `"none"`, for the default static output.
It also accepts `locale: "en" | "zh-CN"`. The field selects the fixed Viewer
UI, renderer-owned default legend and accessibility copy, document-title
suffix, and `<html lang>` value; it does not translate authored strings.
Omitting it preserves legacy behavior and resolves to English. Unsupported
locale values fail schema validation instead of being guessed or silently
rewritten.
`visual_preset` accepts `classic` (the stable default), `signal-flow` (luminous
motion-forward presentation), `blueprint` (high-contrast engineering review),
or `editorial` (warm publication-style design review and documentation).
Presets change only viewer styling; they do not alter semantic IDs or geometry.
Sequence `meta` additionally accepts `column_fit`. The default `fixed` keeps
the historical 108px column gap and 86px participant boxes, so an authored
diagram renders at the same coordinates no matter how wide its viewBox is.
`spread` derives the gap and box width from the viewBox instead, which turns a
wide canvas into column distance and label room rather than empty space on the
right. Lane order, IDs, and message semantics are unchanged either way.

It may also include up to five guided `views`. Each view has a unique `id`, a
reader-facing `label`, a non-empty `focus` list of existing semantic node IDs,
and an optional short `note`.

### Legend presentation contract

Every `meta` object accepts the same optional legend shape without changing
the schema version already selected for that renderer:

```json
"legend": {
  "mode": "auto",
  "entries": {
    "security": { "label": "restricted data", "visible": true }
  }
}
```

`mode` is `auto` (the default), `all`, or `hidden`. `auto` includes only kinds
present in typed IR; `all` includes the renderer's full stable catalog;
`hidden` removes the complete legend and takes precedence over entry overrides.
Architecture documents that omit an explicit `viewBox` size that automatic
viewBox from the same measured resolved legend footprint used for final SVG
layout. Across all renderers, legacy documents that omit `meta.legend` use a
compatibility-safe implicit `auto`: if the resolved legend cannot fit an
explicit authored viewBox without overlap, Archify omits the complete legend
instead of turning a previously valid schema-v1 document into a hard failure.
Once an author adds `meta.legend` (including explicit `mode: "auto"`), the
layout is intentional and unfit labels or bands fail with a path-prefixed
diagnostic. An entry may set a non-empty, bounded `label`, boolean `visible`,
or both.
`visible: false` removes a resolved entry and `visible: true` forces a supported
but unused kind into the visual legend. Unknown kinds and properties fail
strict validation.

Supported keys are renderer-owned:

| Renderer | `meta.legend.entries` keys |
|---|---|
| Architecture | `frontend`, `backend`, `database`, `cloud`, `security`, `messagebus`, `external` |
| Workflow | `frontend`, `backend`, `security`, `messagebus`, `database`, `cloud`, `external` |
| Sequence | `emphasis`, `return`, `security`, `dashed`, `default` |
| Dataflow | `emphasis`, `security`, `dashed`, `database`, `default` |
| Lifecycle | `start`, `active`, `waiting`, `decision`, `success`, `failure`, `neutral`, `external` |

Labels are presentation only: they do not rename the stable kind, change
nodes/relationships, or create Semantic Lens edge facts. Sequence message and
Dataflow flow-variant entries are visual keys. Component/state entries backed
by exact compiled node facts receive the interactive Semantic Legend bridge;
this includes Dataflow `database` when a real `nodes[].type: "database"` fact
exists.

Every relationship collection (`connections`, `edges`, `messages`, `flows`, and
`transitions`) accepts an optional author-controlled `id` using the shared ID
pattern. The renderer keeps its source-order runtime key separately, while the
authored ID enables a stable `#relation=<id>` viewer link that survives array
reordering. ID-less documents remain valid and their relationship pins stay
local to the current page.

Every semantic node collection (`components`, `nodes`, `participants`, and
`states`) also accepts one optional `brand`: either a canonical string returned
by `archify brands --json`, or a digest-pinned `{ "url", "sha256" }` object
returned by `archify brands capture <url> --json`. Known IDs and known-brand
domains use the bundled vector catalogue. Unknown URLs must be captured in that
explicit command before authoring; render and validate never perform an
unpinned network capture. Unsafe, unavailable, changed, or unsupported content
fails closed with a brand diagnostic. Omitted `brand` preserves the prior
output.

## schema_version policy

Workflow supports schema versions 1 and 2. Version 1 remains the fixed-layout
compatibility contract; version 2 opts into the readable workflow compiler and
can be produced explicitly with `archify migrate workflow ... --to-schema 2`.
The other four diagram schemas keep `schema_version` pinned to `1`.

Workflow also accepts optional `semanticChecks`. `allowedRoots` and
`allowedTerminals` close the set of intentional graph sources and sinks;
`requiredEdges` requires exact authored relationships; and `requiredPaths`
requires directed reachability while allowing intermediate nodes. The compiler
evaluates these facts before layout and returns typed `workflow/*` diagnostics.
The field is additive and geometry-neutral: omitting it preserves existing
workflow behavior and including a satisfied contract does not change SVG or
layout-receipt bytes.

A file that validates today must keep validating and rendering within its
declared version throughout the 2.x release line. Additive viewer,
accessibility, and presentation improvements may enhance generated HTML, but
they must not reinterpret authored IR or turn a previously valid profile-less
v1 file into a new hard layout failure. Breaking IR changes require a new
version; additive, backwards-compatible fields do not.

## Shared definitions (common.schema.json)

The five diagram schemas reference `common.schema.json#/$defs/...`:

- `id` — element identifiers, pattern `^[a-zA-Z][a-zA-Z0-9_-]*$`
- `point` — an `[x, y]` pair of numbers (used by `via` and `labelAt`)
- `componentType` — `frontend`, `backend`, `database`, `cloud`, `security`,
  `messagebus`, `external`
- `locale` — the bounded renderer locale, `en` or `zh-CN`
- `brandMark` — one optional built-in brand ID or explicit HTTP(S) site URL
- `variant` — `default`, `emphasis`, `security`, `dashed` (sequence messages
  extend this list locally with `return`)
- `legendMode` and `legendEntry` — the shared strict mode and label/visibility
  override shapes used by each renderer-owned key map
- `guidedViews` — the bounded, read-only reader paths accepted by `meta.views`
- `cards` — the summary-card blocks rendered below the SVG

Lifecycle state `type` is mode-specific (`start`/`active`/`waiting`/...) and
stays in `lifecycle.schema.json`.

## Runtime validation

At development time, `scripts/generate-validators.mjs` compiles all five
schemas with ajv's draft 2020-12 standalone generator using `strict: true` and
`allErrors: true`. The generated `renderers/shared/generated-validators.mjs`
is committed and shipped with the skill, so runtime validation has no npm or
network dependency. `renderers/shared/validator.mjs` applies the matching
standalone validator before the renderer's own layout checks.
The shared loader then checks cross-collection facts that JSON Schema cannot
express cleanly here: duplicate view IDs, duplicate focus IDs, focus IDs that do
not exist in the diagram's semantic collection, and duplicate authored
relationship IDs within the mode's relationship collection.

Architecture additionally supports opt-in, revision-pinned repository evidence.
`meta.repository` names a public GitHub URL and full commit SHA; a component may
carry one to three `sources` with repo-relative POSIX paths, optional line
ranges, and optional labels. Shape is schema-checked, then the renderer requires
`--repo-root`: the local Git origin must match, and Git must prove the commit,
blobs, and requested lines. Verified evidence is embedded outside the canonical
SVG for the Semantic Passport and Node Finder; ordinary documents and visual
exports carry no repository evidence.

## Visual quality and engineering truth

`meta.quality_profile` and `meta.engineering_profile` answer different
questions. `quality_profile` is available in all five modes and controls how
strictly Archify judges composition. `engineering_profile` is an optional
Architecture-only semantic contract; omitting it preserves the ordinary v1
behavior.

The first engineering profile is `deployment-ownership`. Enable it only when
the user wants a fail-closed deployment review and the source facts are known.
It requires every non-external component to name an owner in `tag` and belong
to exactly one `region`; the document must contain both `region` and
`security-group` boundaries; every `database` must be inside a
`security-group`; each security group must contain members from one shared
region; and every connection whose region or security-group membership changes
must name the real crossing mechanism in `label`.

The profile validates only authored IR. It does not discover infrastructure,
infer owners, or prove that a diagram matches a live environment. If a fact is
unknown, leave the profile unset or obtain the fact instead of inventing it.

`npm test` runs the generator in check mode and fails when the committed
validators drift from their schemas.

## Error format

Schema violations exit non-zero. Each ajv error is reported on its own line as
the instance path — annotated with the nearest enclosing element's `id` or
`label` — followed by the message and parameters:

```text
workflow schema validation failed:
  /nodes/3 (id/label: "router") must NOT have additional properties {"additionalProperty":"colour"}
```

Schemas catch shape errors (types, enums, ranges, unknown fields); geometry
problems such as overlaps and label collisions are the renderers' job.

## schemas/architecture.schema.json

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/tt-a1i/archify/schemas/architecture.schema.json",
  "title": "Archify Architecture Diagram",
  "type": "object",
  "additionalProperties": false,
  "required": ["schema_version", "diagram_type", "meta", "components"],
  "properties": {
    "schema_version": { "const": 1 },
    "diagram_type": { "const": "architecture" },
    "meta": {
      "type": "object",
      "additionalProperties": false,
      "required": ["title"],
      "properties": {
        "title": { "type": "string", "minLength": 1 },
        "locale": { "$ref": "common.schema.json#/$defs/locale" },
        "subtitle": { "type": "string" },
        "output": { "type": "string" },
        "animation": { "$ref": "common.schema.json#/$defs/animation" },
        "visual_preset": { "$ref": "common.schema.json#/$defs/visualPreset" },
        "quality_profile": { "$ref": "common.schema.json#/$defs/qualityProfile" },
        "engineering_profile": { "enum": ["deployment-ownership"] },
        "repository": {
          "type": "object",
          "additionalProperties": false,
          "required": ["url", "revision"],
          "properties": {
            "url": {
              "type": "string",
              "minLength": 1
            },
            "provider": { "enum": ["github", "gitee"] },
            "link_mode": { "enum": ["web", "local-only"] },
            "revision": { "type": "string", "pattern": "^[a-fA-F0-9]{40}$" }
          }
        },
        "views": { "$ref": "common.schema.json#/$defs/guidedViews" },
        "legend": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "mode": { "$ref": "common.schema.json#/$defs/legendMode" },
            "entries": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "frontend": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "backend": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "database": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "cloud": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "security": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "messagebus": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "external": { "$ref": "common.schema.json#/$defs/legendEntry" }
              }
            }
          }
        },
        "viewBox": {
          "type": "array",
          "prefixItems": [
            { "type": "number", "minimum": 320 },
            { "type": "number", "minimum": 240 }
          ],
          "items": false,
          "minItems": 2,
          "maxItems": 2
        }
      }
    },
    "layout": {
      "type": "object",
      "additionalProperties": false,
      "required": ["mode"],
      "properties": {
        "mode": { "enum": ["grid"] },
        "origin": { "$ref": "common.schema.json#/$defs/point" },
        "cols": { "type": "integer", "minimum": 1, "maximum": 12 },
        "gapX": { "type": "number", "minimum": 0 },
        "gapY": { "type": "number", "minimum": 0 },
        "cellW": { "type": "number", "minimum": 40 },
        "cellH": { "type": "number", "minimum": 24 }
      }
    },
    "components": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "type", "label"],
        "properties": {
          "id": { "$ref": "common.schema.json#/$defs/id" },
          "type": { "$ref": "common.schema.json#/$defs/componentType" },
          "label": { "type": "string", "minLength": 1 },
          "sublabel": { "type": "string" },
          "tag": { "type": "string" },
          "brand": { "$ref": "common.schema.json#/$defs/brandMark" },
          "sources": {
            "type": "array",
            "minItems": 1,
            "maxItems": 3,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["path"],
              "properties": {
                "path": { "type": "string", "minLength": 1, "maxLength": 240 },
                "line": { "type": "integer", "minimum": 1 },
                "end_line": { "type": "integer", "minimum": 1 },
                "label": { "type": "string", "minLength": 1, "maxLength": 48 }
              }
            }
          },
          "row": { "type": "integer", "minimum": 0 },
          "col": { "type": "integer", "minimum": 0 },
          "pos": { "$ref": "common.schema.json#/$defs/point" },
          "size": {
            "type": "array",
            "prefixItems": [
              { "type": "number", "exclusiveMinimum": 0 },
              { "type": "number", "exclusiveMinimum": 0 }
            ],
            "items": false,
            "minItems": 2,
            "maxItems": 2
          }
        }
      }
    },
    "boundaries": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["kind", "label", "wraps"],
        "properties": {
          "kind": { "enum": ["region", "security-group"] },
          "label": { "type": "string", "minLength": 1 },
          "wraps": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "common.schema.json#/$defs/id" }
          },
          "pad": { "type": "number", "minimum": 0 }
        }
      }
    },
    "connections": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["from", "to"],
        "properties": {
          "id": { "$ref": "common.schema.json#/$defs/id" },
          "from": { "$ref": "common.schema.json#/$defs/id" },
          "to": { "$ref": "common.schema.json#/$defs/id" },
          "label": { "type": "string" },
          "variant": { "$ref": "common.schema.json#/$defs/variant" },
          "fromSide": { "$ref": "common.schema.json#/$defs/side" },
          "toSide": { "$ref": "common.schema.json#/$defs/side" },
          "route": { "enum": ["auto", "straight", "orthogonal-h", "orthogonal-v"] },
          "via": {
            "type": "array",
            "items": { "$ref": "common.schema.json#/$defs/point" }
          },
          "labelAt": { "$ref": "common.schema.json#/$defs/point" },
          "labelDx": { "type": "number" },
          "labelDy": { "type": "number" },
          "labelSegment": { "type": "integer", "minimum": 0 },
          "width": { "$ref": "common.schema.json#/$defs/relationshipWidth" }
        }
      }
    },
    "cards": { "$ref": "common.schema.json#/$defs/cards" }
  }
}
```

## schemas/common.schema.json

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/tt-a1i/archify/schemas/common.schema.json",
  "title": "Archify Shared Definitions",
  "$defs": {
    "id": {
      "type": "string",
      "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$"
    },
    "locale": {
      "enum": ["en", "zh-CN"]
    },
    "animation": {
      "enum": ["trace", "none"]
    },
    "visualPreset": {
      "enum": ["classic", "signal-flow", "blueprint", "editorial"]
    },
    "qualityProfile": {
      "enum": ["standard", "showcase"]
    },
    "side": {
      "enum": ["left", "right", "top", "bottom"]
    },
    "relationshipWidth": {
      "type": "number",
      "minimum": 0.5
    },
    "point": {
      "type": "array",
      "prefixItems": [
        { "type": "number" },
        { "type": "number" }
      ],
      "items": false,
      "minItems": 2,
      "maxItems": 2
    },
    "componentType": {
      "enum": ["frontend", "backend", "database", "cloud", "security", "messagebus", "external"]
    },
    "brandMark": {
      "oneOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 2048,
          "anyOf": [
            { "maxLength": 80, "pattern": "^[^\\r\\n]+$" },
            { "pattern": "^https?://" }
          ]
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["url", "sha256"],
          "properties": {
            "url": { "type": "string", "minLength": 8, "maxLength": 2048, "pattern": "^https?://" },
            "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
          }
        }
      ]
    },
    "variant": {
      "enum": ["default", "emphasis", "security", "dashed"]
    },
    "legendMode": {
      "enum": ["auto", "all", "hidden"]
    },
    "legendEntry": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "label": { "type": "string", "minLength": 1, "maxLength": 80 },
        "visible": { "type": "boolean" }
      }
    },
    "guidedViews": {
      "type": "array",
      "maxItems": 5,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "label", "focus"],
        "properties": {
          "id": { "$ref": "#/$defs/id" },
          "label": { "type": "string", "minLength": 1, "maxLength": 48 },
          "focus": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/$defs/id" }
          },
          "note": { "type": "string", "maxLength": 140 }
        }
      }
    },
    "cards": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["dot", "title", "items"],
        "properties": {
          "dot": { "enum": ["cyan", "emerald", "violet", "amber", "rose", "orange", "slate"] },
          "title": { "type": "string", "minLength": 1 },
          "items": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      }
    }
  }
}
```

## schemas/dataflow.schema.json

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/tt-a1i/archify/schemas/dataflow.schema.json",
  "title": "Archify Data Flow Diagram",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "diagram_type",
    "meta",
    "stages",
    "nodes",
    "flows"
  ],
  "properties": {
    "schema_version": {
      "const": 1
    },
    "diagram_type": {
      "const": "dataflow"
    },
    "meta": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "title"
      ],
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1
        },
        "locale": {
          "$ref": "common.schema.json#/$defs/locale"
        },
        "subtitle": {
          "type": "string"
        },
        "output": {
          "type": "string"
        },
        "animation": {
          "$ref": "common.schema.json#/$defs/animation"
        },
        "visual_preset": {
          "$ref": "common.schema.json#/$defs/visualPreset"
        },
        "quality_profile": {
          "$ref": "common.schema.json#/$defs/qualityProfile"
        },
        "views": {
          "$ref": "common.schema.json#/$defs/guidedViews"
        },
        "legend": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "mode": { "$ref": "common.schema.json#/$defs/legendMode" },
            "entries": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "default": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "emphasis": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "security": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "dashed": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "database": { "$ref": "common.schema.json#/$defs/legendEntry" }
              }
            }
          }
        },
        "viewBox": {
          "type": "array",
          "prefixItems": [
            {
              "type": "number",
              "minimum": 360
            },
            {
              "type": "number",
              "minimum": 360
            }
          ],
          "items": false,
          "minItems": 2,
          "maxItems": 2
        }
      }
    },
    "stages": {
      "type": "array",
      "minItems": 2,
      "maxItems": 5,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "label"
        ],
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1
          }
        }
      }
    },
    "nodes": {
      "type": "array",
      "minItems": 2,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "type",
          "label",
          "stage",
          "row"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "type": {
            "$ref": "common.schema.json#/$defs/componentType"
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "sublabel": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          },
          "brand": {
            "$ref": "common.schema.json#/$defs/brandMark"
          },
          "stage": {
            "type": "integer",
            "minimum": 0
          },
          "row": {
            "type": "integer",
            "minimum": 0
          },
          "width": {
            "type": "number",
            "minimum": 48
          },
          "height": {
            "type": "number",
            "minimum": 36
          },
          "yOffset": {
            "type": "number"
          }
        }
      }
    },
    "flows": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to",
          "label"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "from": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "to": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "classification": {
            "type": "string"
          },
          "variant": {
            "$ref": "common.schema.json#/$defs/variant"
          },
          "route": {
            "enum": [
              "auto",
              "straight",
              "vertical-channel",
              "bottom-channel",
              "top-channel"
            ]
          },
          "fromSide": {
            "$ref": "common.schema.json#/$defs/side"
          },
          "toSide": {
            "$ref": "common.schema.json#/$defs/side"
          },
          "channelX": {
            "type": "number"
          },
          "channelY": {
            "type": "number"
          },
          "labelAt": {
            "$ref": "common.schema.json#/$defs/point"
          },
          "labelDx": {
            "type": "number"
          },
          "labelDy": {
            "type": "number"
          },
          "labelSegment": {
            "type": "integer",
            "minimum": 0
          },
          "via": {
            "type": "array",
            "items": {
              "$ref": "common.schema.json#/$defs/point"
            }
          },
          "width": {
            "$ref": "common.schema.json#/$defs/relationshipWidth"
          }
        }
      }
    },
    "cards": {
      "$ref": "common.schema.json#/$defs/cards"
    }
  }
}
```

## schemas/lifecycle.schema.json

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/tt-a1i/archify/schemas/lifecycle.schema.json",
  "title": "Archify Lifecycle Diagram",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "diagram_type",
    "meta",
    "lanes",
    "states",
    "transitions"
  ],
  "properties": {
    "schema_version": {
      "const": 1
    },
    "diagram_type": {
      "const": "lifecycle"
    },
    "meta": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "title"
      ],
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1
        },
        "locale": {
          "$ref": "common.schema.json#/$defs/locale"
        },
        "subtitle": {
          "type": "string"
        },
        "output": {
          "type": "string"
        },
        "animation": {
          "$ref": "common.schema.json#/$defs/animation"
        },
        "visual_preset": {
          "$ref": "common.schema.json#/$defs/visualPreset"
        },
        "quality_profile": {
          "$ref": "common.schema.json#/$defs/qualityProfile"
        },
        "views": {
          "$ref": "common.schema.json#/$defs/guidedViews"
        },
        "legend": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "mode": { "$ref": "common.schema.json#/$defs/legendMode" },
            "entries": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "start": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "active": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "waiting": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "decision": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "success": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "failure": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "neutral": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "external": { "$ref": "common.schema.json#/$defs/legendEntry" }
              }
            }
          }
        },
        "viewBox": {
          "type": "array",
          "prefixItems": [
            {
              "type": "number",
              "minimum": 420
            },
            {
              "type": "number",
              "minimum": 566
            }
          ],
          "items": false,
          "minItems": 2,
          "maxItems": 2
        }
      }
    },
    "lanes": {
      "type": "array",
      "minItems": 1,
      "maxItems": 4,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "label"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "label": {
            "type": "string",
            "minLength": 1
          }
        }
      }
    },
    "states": {
      "type": "array",
      "minItems": 2,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "type",
          "label",
          "lane",
          "col"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "type": {
            "enum": [
              "start",
              "active",
              "waiting",
              "decision",
              "success",
              "failure",
              "neutral",
              "external"
            ]
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "sublabel": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          },
          "brand": {
            "$ref": "common.schema.json#/$defs/brandMark"
          },
          "step": {
            "type": "string"
          },
          "lane": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "col": {
            "type": "integer",
            "minimum": 0,
            "maximum": 4
          },
          "width": {
            "type": "number",
            "minimum": 48
          },
          "height": {
            "type": "number",
            "minimum": 36
          },
          "yOffset": {
            "type": "number"
          }
        }
      }
    },
    "transitions": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "from": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "to": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "label": {
            "type": "string"
          },
          "note": {
            "type": "string"
          },
          "variant": {
            "$ref": "common.schema.json#/$defs/variant"
          },
          "route": {
            "enum": [
              "auto",
              "straight",
              "drop",
              "bottom-channel",
              "top-channel",
              "right-channel",
              "left-channel"
            ]
          },
          "fromSide": {
            "$ref": "common.schema.json#/$defs/side"
          },
          "toSide": {
            "$ref": "common.schema.json#/$defs/side"
          },
          "channelX": {
            "type": "number"
          },
          "channelY": {
            "type": "number"
          },
          "cornerRadius": {
            "type": "number",
            "minimum": 0
          },
          "labelAt": {
            "$ref": "common.schema.json#/$defs/point"
          },
          "labelDx": {
            "type": "number"
          },
          "labelDy": {
            "type": "number"
          },
          "labelSegment": {
            "type": "integer",
            "minimum": 0
          },
          "via": {
            "type": "array",
            "items": {
              "$ref": "common.schema.json#/$defs/point"
            }
          },
          "width": {
            "$ref": "common.schema.json#/$defs/relationshipWidth"
          }
        }
      }
    },
    "cards": {
      "$ref": "common.schema.json#/$defs/cards"
    }
  }
}
```

## schemas/sequence.schema.json

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/tt-a1i/archify/schemas/sequence.schema.json",
  "title": "Archify Sequence Diagram",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "diagram_type",
    "meta",
    "participants",
    "messages"
  ],
  "properties": {
    "schema_version": {
      "const": 1
    },
    "diagram_type": {
      "const": "sequence"
    },
    "meta": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "title"
      ],
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1
        },
        "locale": {
          "$ref": "common.schema.json#/$defs/locale"
        },
        "subtitle": {
          "type": "string"
        },
        "output": {
          "type": "string"
        },
        "animation": {
          "$ref": "common.schema.json#/$defs/animation"
        },
        "visual_preset": {
          "$ref": "common.schema.json#/$defs/visualPreset"
        },
        "quality_profile": {
          "$ref": "common.schema.json#/$defs/qualityProfile"
        },
        "column_fit": {
          "description": "Horizontal participant layout. Omit this field or use fixed for the stable 86px boxes and 108px gap. Use spread when a wide viewBox would leave unused horizontal space or meaningful participant labels do not fit the fixed boxes; spread derives wider boxes and gaps from the viewBox without changing participant order or message semantics.",
          "enum": ["fixed", "spread"]
        },
        "views": {
          "$ref": "common.schema.json#/$defs/guidedViews"
        },
        "legend": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "mode": { "$ref": "common.schema.json#/$defs/legendMode" },
            "entries": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "default": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "emphasis": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "security": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "dashed": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "return": { "$ref": "common.schema.json#/$defs/legendEntry" }
              }
            }
          }
        },
        "viewBox": {
          "type": "array",
          "prefixItems": [
            {
              "type": "number",
              "minimum": 480
            },
            {
              "type": "number",
              "minimum": 480
            }
          ],
          "items": false,
          "minItems": 2,
          "maxItems": 2
        }
      }
    },
    "participants": {
      "type": "array",
      "minItems": 2,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "type",
          "label"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "type": {
            "$ref": "common.schema.json#/$defs/componentType"
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "sublabel": {
            "type": "string"
          },
          "brand": {
            "$ref": "common.schema.json#/$defs/brandMark"
          }
        }
      }
    },
    "segments": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to",
          "label"
        ],
        "properties": {
          "from": {
            "type": "number"
          },
          "to": {
            "type": "number"
          },
          "label": {
            "type": "string",
            "minLength": 1
          }
        }
      }
    },
    "messages": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to",
          "y",
          "label"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "from": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "to": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "y": {
            "type": "number",
            "minimum": 160
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "variant": {
            "enum": [
              "default",
              "emphasis",
              "security",
              "dashed",
              "return"
            ]
          },
          "note": {
            "type": "string"
          }
        }
      }
    },
    "activations": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "participant",
          "from",
          "to"
        ],
        "properties": {
          "participant": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "from": {
            "type": "number"
          },
          "to": {
            "type": "number"
          },
          "type": {
            "$ref": "common.schema.json#/$defs/componentType"
          }
        }
      }
    },
    "cards": {
      "$ref": "common.schema.json#/$defs/cards"
    }
  }
}
```

## schemas/workflow.schema.json

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/tt-a1i/archify/schemas/workflow.schema.json",
  "title": "Archify Workflow Diagram",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "diagram_type",
    "meta",
    "lanes",
    "nodes",
    "edges"
  ],
  "properties": {
    "schema_version": {
      "enum": [
        1,
        2
      ]
    },
    "diagram_type": {
      "const": "workflow"
    },
    "meta": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "title"
      ],
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1
        },
        "locale": {
          "$ref": "common.schema.json#/$defs/locale"
        },
        "subtitle": {
          "type": "string"
        },
        "output": {
          "type": "string"
        },
        "animation": {
          "enum": [
            "trace",
            "none"
          ]
        },
        "visual_preset": {
          "enum": [
            "classic",
            "signal-flow",
            "blueprint",
            "editorial"
          ]
        },
        "quality_profile": {
          "enum": [
            "standard",
            "showcase"
          ]
        },
        "views": {
          "$ref": "common.schema.json#/$defs/guidedViews"
        },
        "legend": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "mode": { "$ref": "common.schema.json#/$defs/legendMode" },
            "entries": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "frontend": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "backend": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "database": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "cloud": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "security": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "messagebus": { "$ref": "common.schema.json#/$defs/legendEntry" },
                "external": { "$ref": "common.schema.json#/$defs/legendEntry" }
              }
            }
          }
        },
        "viewBox": {
          "type": "array",
          "prefixItems": [
            {
              "type": "number",
              "minimum": 700
            },
            {
              "type": "number",
              "minimum": 240
            }
          ],
          "items": false,
          "minItems": 2,
          "maxItems": 2
        }
      }
    },
    "lanes": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "label"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "variant": {
            "enum": [
              "normal",
              "exception"
            ]
          }
        }
      }
    },
    "phases": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "label",
          "fromCol",
          "toCol"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "fromCol": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5
          },
          "toCol": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5
          },
          "variant": {
            "enum": [
              "default",
              "emphasis",
              "security",
              "dashed"
            ]
          }
        }
      }
    },
    "groups": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "label",
          "lane",
          "fromCol",
          "toCol"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "lane": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "fromCol": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5
          },
          "toCol": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5
          },
          "variant": {
            "enum": [
              "default",
              "emphasis",
              "security",
              "dashed"
            ]
          }
        }
      }
    },
    "mainPath": {
      "type": "array",
      "minItems": 2,
      "items": {
        "$ref": "common.schema.json#/$defs/id"
      }
    },
    "semanticChecks": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "allowedRoots": {
          "type": "array",
          "items": {
            "$ref": "common.schema.json#/$defs/id"
          }
        },
        "allowedTerminals": {
          "type": "array",
          "items": {
            "$ref": "common.schema.json#/$defs/id"
          }
        },
        "requiredEdges": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/semanticRelation"
          }
        },
        "requiredPaths": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/semanticRelation"
          }
        }
      }
    },
    "nodes": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "lane",
          "col",
          "type",
          "label"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "lane": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "col": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5
          },
          "type": {
            "$ref": "common.schema.json#/$defs/componentType"
          },
          "label": {
            "type": "string",
            "minLength": 1
          },
          "sublabel": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          },
          "brand": {
            "$ref": "common.schema.json#/$defs/brandMark"
          },
          "width": {
            "type": "number",
            "minimum": 32
          },
          "height": {
            "type": "number",
            "minimum": 32
          },
          "yOffset": {
            "type": "number"
          }
        }
      }
    },
    "edges": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "id": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "from": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "to": {
            "$ref": "common.schema.json#/$defs/id"
          },
          "label": {
            "type": "string"
          },
          "variant": {
            "$ref": "common.schema.json#/$defs/variant"
          },
          "role": {
            "enum": [
              "main",
              "branch",
              "async",
              "return",
              "error"
            ]
          },
          "fromSide": {
            "$ref": "#/$defs/side"
          },
          "toSide": {
            "$ref": "#/$defs/side"
          },
          "route": {
            "enum": [
              "auto",
              "straight",
              "drop",
              "outside-right",
              "return-left",
              "bottom-channel",
              "up-channel"
            ]
          },
          "via": {
            "type": "array",
            "items": {
              "$ref": "common.schema.json#/$defs/point"
            }
          },
          "labelAt": {
            "$ref": "common.schema.json#/$defs/point"
          },
          "labelDx": {
            "type": "number"
          },
          "labelDy": {
            "type": "number"
          },
          "labelSegment": {
            "type": "integer",
            "minimum": 0
          },
          "channelX": {
            "type": "number"
          },
          "channelY": {
            "type": "number"
          },
          "bias": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "width": {
            "type": "number",
            "minimum": 0.5
          }
        }
      }
    },
    "cards": {
      "$ref": "common.schema.json#/$defs/cards"
    }
  },
  "$defs": {
    "semanticRelation": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "from",
        "to"
      ],
      "properties": {
        "from": {
          "$ref": "common.schema.json#/$defs/id"
        },
        "to": {
          "$ref": "common.schema.json#/$defs/id"
        }
      }
    },
    "side": {
      "enum": [
        "left",
        "right",
        "top",
        "bottom"
      ]
    }
  }
}
```

## scripts

```

```

## scripts/check-render-output.mjs

```js
#!/usr/bin/env node

import fs from 'node:fs';
import path from 'node:path';
import { collectAmbiguousCorridors, collectBorderRuns, collectLabelRouteClearance, collectRouteRhythmIssues, routeBudgetMetrics } from '../renderers/shared/geometry.mjs';
import {
  DESKTOP_READABILITY_VIEWPORT,
  DESKTOP_READER_DIAGRAM_WIDTH,
  MIN_PROJECTED_NODE_TEXT_PX,
  projectedNodeTextPx,
} from '../renderers/shared/desktop-readability.mjs';

const input = process.argv[2];

if (!input || input === '-h' || input === '--help') {
  console.error('Usage: node scripts/check-render-output.mjs <diagram.html>');
  process.exit(input ? 0 : 2);
}

const htmlPath = path.resolve(input);
let html;
try {
  html = fs.readFileSync(htmlPath, 'utf8');
} catch (err) {
  console.error(JSON.stringify({
    ok: false,
    file: htmlPath,
    checks: [{ name: 'file_readable', ok: false, details: [err.message] }],
  }, null, 2));
  process.exit(1);
}

const checks = [];
let composition = {
  schemaVersion: 1,
  profile: 'standard',
  status: 'pass',
  summary: { errors: 0, warnings: 0 },
  metrics: {
    properCrossings: 0,
    ambiguousCorridors: 0,
    containerBorderRuns: 0,
    labelRouteClearanceIssues: 0,
    minLabelRouteClearance: null,
    maxBends: 0,
    routesOverSuggestedBends: 0,
    maxStretch: null,
    routesOverSuggestedStretch: 0,
    minSegmentPx: null,
    minInteriorSegmentPx: null,
    shortSegmentCount: 0,
    shortEndpointSegmentCount: 0,
    shortInteriorSegmentCount: 0,
    microSegmentCount: 0,
    desktopReadabilityIssues: 0,
    minProjectedNodeTextPx: null,
  },
  suggestedLimits: { bendsPerRelationship: 2, stretch: 1.35, segmentPx: 16, microSegmentPx: 8 },
  issues: [],
};

function addCheck(name, ok, details = []) {
  checks.push({ name, ok, details });
}

const svgMatches = [...html.matchAll(/<svg\b[\s\S]*?<\/svg>/gi)];
addCheck('single_svg', svgMatches.length === 1, [`found ${svgMatches.length} <svg> block(s)`]);

if (svgMatches.length === 1) {
  const svg = svgMatches[0][0];
  const svgRoot = svg.match(/<svg\b[^>]*>/i)?.[0] || '';
  const svgAttrs = parseAttrs(svgRoot);
  const qualityProfile = svgAttrs['data-quality-profile'] || 'standard';
  const qualityGatesEnforced = svgAttrs['data-quality-gates'] !== 'advisory';
  addCheck('finite_svg', !/\b(?:NaN|undefined|Infinity|-Infinity)\b/.test(svg));
  const legendStart = svg.indexOf('<!-- Legend -->');
  const beforeLegend = legendStart >= 0 ? svg.slice(0, legendStart) : svg;
  const desktopReadabilityIssue = collectDesktopReadability(svgAttrs, beforeLegend);
  const arrows = collectArrows(beforeLegend);
  const diagonal = arrows.flatMap((arrow) => diagonalStraightSegments(arrow).map((segment) => ({ arrow, ...segment })));
  addCheck(
    'orthogonal_arrows',
    diagonal.length === 0,
    diagonal.map(({ arrow, segmentIndex }) => `${arrow.kind} ${arrow.index} segment ${segmentIndex + 1}: ${arrow.raw}`),
  );
  const relationshipCrossings = collectRelationshipCrossings(arrows);
  const compositionFrames = collectCompositionFrames(beforeLegend);
  const containerBorderRuns = collectBorderRuns({
    routedRelations: arrows
      .filter((arrow) => arrow.from && arrow.to && arrow.borderSegments.length)
      .map((arrow) => ({
        relation: arrow,
        relationIndex: arrow.index,
        segments: arrow.borderSegments,
      })),
    frames: compositionFrames,
  });
  const routedRelationships = arrows
    .filter((arrow) => arrow.from && arrow.to && arrow.routePoints.length)
    .map((arrow) => ({ relation: arrow, relationIndex: arrow.index, points: arrow.routePoints }));
  const routeMetrics = routeBudgetMetrics({ routedRelations: routedRelationships });
  const routeRhythmIssues = collectRouteRhythmIssues({ routedRelations: routedRelationships });
  const ambiguousCorridors = collectAmbiguousCorridors({ routedRelations: routedRelationships });
  const relationshipLabels = collectRelationshipLabelMasks(beforeLegend, arrows);
  const labelClearanceThreshold = qualityProfile === 'showcase' ? 4 : 2;
  const labelRouteMeasurements = collectLabelRouteClearance({
    labels: relationshipLabels,
    routedRelations: arrows.map((arrow) => ({ relation: arrow, relationIndex: arrow.index, points: arrow.routePoints })),
    threshold: Number.MAX_VALUE,
  });
  const labelRouteClearance = collectLabelRouteClearance({
    labels: relationshipLabels,
    routedRelations: arrows.map((arrow) => ({ relation: arrow, relationIndex: arrow.index, points: arrow.routePoints })),
    threshold: labelClearanceThreshold,
  });
  const crossingIsError = qualityProfile === 'showcase';
  const corridorIsError = qualityProfile === 'showcase';
  const rhythmIsError = qualityProfile === 'showcase';
  const labelClearanceIsError = qualityProfile === 'showcase';
  const desktopReadabilityIsError = qualityProfile === 'showcase';
  const compositionErrors = (qualityGatesEnforced ? containerBorderRuns.length : 0)
    + (crossingIsError ? relationshipCrossings.length : 0)
    + (corridorIsError ? ambiguousCorridors.length : 0)
    + (labelClearanceIsError ? labelRouteClearance.length : 0)
    + (rhythmIsError ? routeRhythmIssues.length : 0)
    + (desktopReadabilityIsError && desktopReadabilityIssue ? 1 : 0);
  const compositionWarnings = (qualityGatesEnforced ? 0 : containerBorderRuns.length)
    + (crossingIsError ? 0 : relationshipCrossings.length)
    + (corridorIsError ? 0 : ambiguousCorridors.length)
    + (labelClearanceIsError ? 0 : labelRouteClearance.length)
    + (rhythmIsError ? 0 : routeRhythmIssues.length)
    + (desktopReadabilityIsError || !desktopReadabilityIssue ? 0 : 1);
  composition = {
    schemaVersion: 1,
    profile: qualityProfile,
    status: compositionErrors ? 'fail' : 'pass',
    summary: {
      errors: compositionErrors,
      warnings: compositionWarnings,
    },
    metrics: {
      properCrossings: relationshipCrossings.length,
      ambiguousCorridors: ambiguousCorridors.length,
      containerBorderRuns: containerBorderRuns.length,
      labelRouteClearanceIssues: labelRouteClearance.length,
      minLabelRouteClearance: labelRouteMeasurements.length
        ? Math.round(Math.min(...labelRouteMeasurements.map((hit) => hit.clearance)) * 10) / 10
        : null,
      desktopReadabilityIssues: desktopReadabilityIssue ? 1 : 0,
      minProjectedNodeTextPx: desktopReadabilityIssue?.projectedFontPx ?? null,
      ...roundedRouteMetrics(routeMetrics),
    },
    suggestedLimits: { bendsPerRelationship: 2, stretch: 1.35, segmentPx: 16, microSegmentPx: 8 },
    issues: [
      ...containerBorderRuns.map((hit) => ({
        severity: qualityGatesEnforced ? 'error' : 'warning',
        code: 'composition/container-border-run',
        relationship: relationshipRecord(hit.relation),
        frame: frameRecord(hit.frame),
        side: hit.side,
        segmentIndex: hit.segmentIndex,
        overlapLength: Math.round(hit.overlapLength * 10) / 10,
        from: hit.overlapStart.map((value) => Math.round(value * 10) / 10),
        to: hit.overlapEnd.map((value) => Math.round(value * 10) / 10),
      })),
      ...labelRouteClearance.map((hit) => ({
        severity: labelClearanceIsError ? 'error' : 'warning',
        code: 'composition/label-route-clearance',
        label: hit.label?.label || hit.labelRelation?.label || '',
        labelRelationship: relationshipRecord(hit.labelRelation),
        otherRelationship: relationshipRecord(hit.otherRelation),
        segmentIndex: hit.segmentIndex,
        labelRect: roundedRect(hit.rect),
        clearance: Math.round(hit.clearance * 10) / 10,
        intersectionLength: Math.round((hit.intersectionLength || 0) * 10) / 10,
        threshold: hit.threshold,
        from: hit.start.map((value) => Math.round(value * 10) / 10),
        to: hit.end.map((value) => Math.round(value * 10) / 10),
      })),
      ...relationshipCrossings.map((hit) => ({
        severity: crossingIsError ? 'error' : 'warning',
        code: 'composition/proper-crossing',
        relationship: relationshipRecord(hit.left),
        otherRelationship: relationshipRecord(hit.right),
        point: hit.point.map((value) => Math.round(value * 10) / 10),
      })),
      ...ambiguousCorridors.map((hit) => ({
        severity: corridorIsError ? 'error' : 'warning',
        code: 'composition/ambiguous-corridor',
        relationship: relationshipRecord(hit.left.relation),
        otherRelationship: relationshipRecord(hit.right.relation),
        segmentIndex: hit.leftSegment,
        otherSegmentIndex: hit.rightSegment,
        overlapLength: Math.round(hit.overlapLength * 10) / 10,
        from: hit.overlapStart.map((value) => Math.round(value * 10) / 10),
        to: hit.overlapEnd.map((value) => Math.round(value * 10) / 10),
      })),
      ...routeRhythmIssues.map((hit) => ({
        severity: rhythmIsError ? 'error' : 'warning',
        code: hit.code,
        relationship: relationshipRecord(hit.relation),
        segmentIndex: hit.segmentIndex,
        position: hit.position,
        length: Math.round(hit.length * 10) / 10,
        from: hit.start.map((value) => Math.round(value * 10) / 10),
        to: hit.end.map((value) => Math.round(value * 10) / 10),
      })),
      ...(desktopReadabilityIssue ? [{
        severity: desktopReadabilityIsError ? 'error' : 'warning',
        code: 'composition/desktop-readability',
        viewportWidth: DESKTOP_READABILITY_VIEWPORT.width,
        viewportHeight: DESKTOP_READABILITY_VIEWPORT.height,
        availableDiagramWidth: DESKTOP_READER_DIAGRAM_WIDTH,
        viewBoxWidth: desktopReadabilityIssue.viewBoxWidth,
        scale: desktopReadabilityIssue.scale,
        text: desktopReadabilityIssue.text,
        detail: desktopReadabilityIssue.detail,
        sourceFontPx: desktopReadabilityIssue.sourceFontPx,
        projectedFontPx: desktopReadabilityIssue.projectedFontPx,
        minimumProjectedFontPx: MIN_PROJECTED_NODE_TEXT_PX,
      }] : []),
    ],
  };
  addCheck(
    'label_route_clearance',
    !labelClearanceIsError || labelRouteClearance.length === 0,
    labelRouteClearance.map((hit) => (
      `[composition/label-route-clearance] ${qualityProfile} label "${hit.label?.label || hit.labelRelation?.label || ''}" on ${relationshipName(hit.labelRelation)} is ${Math.round(hit.clearance * 10) / 10}px from ${relationshipName(hit.otherRelation)} segment ${hit.segmentIndex} [${formatPoint(hit.start)}] -> [${formatPoint(hit.end)}]${hit.intersectionLength > 0 ? ` with ${Math.round(hit.intersectionLength * 10) / 10}px hidden by the mask` : ''} (minimum ${hit.threshold}px) — use renderer-supported label controls (message y for sequence; otherwise labelAt, labelDx, labelDy, or labelSegment), or adjust the other relationship route/via/channel.`
    )),
  );
  addCheck(
    'relationship_crossings',
    !crossingIsError || relationshipCrossings.length === 0,
    relationshipCrossings.map((hit) => (
      `[composition/proper-crossing] ${qualityProfile} ${relationshipName(hit.left)} crosses ${relationshipName(hit.right)} at [${formatPoint(hit.point)}]`
    )),
  );
  addCheck(
    'relationship_corridors',
    !corridorIsError || ambiguousCorridors.length === 0,
    ambiguousCorridors.map((hit) => (
      `[composition/ambiguous-corridor] ${qualityProfile} ${relationshipName(hit.left.relation)} shares a ${Math.round(hit.overlapLength * 10) / 10}px corridor with ${relationshipName(hit.right.relation)} at [${formatPoint(hit.overlapStart)}] -> [${formatPoint(hit.overlapEnd)}]`
    )),
  );
  addCheck(
    'container_border_runs',
    !qualityGatesEnforced || containerBorderRuns.length === 0,
    containerBorderRuns.map((hit) => (
      `[composition/container-border-run] ${relationshipName(hit.relation)} follows ${frameName(hit.frame)} ${hit.side} border for ${Math.round(hit.overlapLength * 10) / 10}px on segment ${hit.segmentIndex} [${formatPoint(hit.overlapStart)}] -> [${formatPoint(hit.overlapEnd)}]`
    )),
  );
  addCheck(
    'route_rhythm',
    !rhythmIsError || routeRhythmIssues.length === 0,
    routeRhythmIssues.map((hit) => (
      `[${hit.code}] ${qualityProfile} ${relationshipName(hit.relation)} has a ${Math.round(hit.length * 10) / 10}px ${hit.position} segment ${hit.segmentIndex} [${formatPoint(hit.start)}] -> [${formatPoint(hit.end)}]`
    )),
  );

  if (legendStart >= 0) {
    const legendFragment = svg.slice(legendStart);
    const legendBoxes = collectLegendBoxes(legendFragment);
    const collisions = collectLegendCollisions(arrows, legendBoxes);
    addCheck(
      'legend_clearance',
      collisions.length === 0,
      collisions.map((hit) => `${hit.arrow.kind} ${hit.arrow.index} crosses legend ${hit.box.label}`),
    );
  } else {
    addCheck('legend_clearance', true, ['no legend marker found']);
  }
}

const ok = checks.every((check) => check.ok) && composition.status !== 'fail';
console.log(JSON.stringify({ ok, file: htmlPath, checks, composition }, null, 2));
// Let pending stdout writes drain: large receipts are asynchronous when piped.
process.exitCode = ok ? 0 : 1;

function collectArrows(fragment) {
  const arrows = [];
  let index = 0;

  for (const tag of fragment.matchAll(/<(path|line)\b[^>]*>/gi)) {
    const raw = tag[0];
    if (!/\bclass="[^"]*\ba-(?:default|emphasis|security|dashed)\b/.test(raw)) continue;
    if (!/\bmarker-end=/.test(raw)) continue;
    const attrs = parseAttrs(raw);
    const segments = tag[1].toLowerCase() === 'line'
      ? lineSegments(attrs)
      : pathSegments(attrs.d || '');
    const borderSegments = tag[1].toLowerCase() === 'line'
      ? segments
      : straightPathSegments(attrs.d || '');
    arrows.push({
      kind: tag[1].toLowerCase(),
      index: index += 1,
      raw,
      segments,
      borderSegments,
      routePoints: parseRoutePoints(attrs['data-composition-points']) || (
        borderSegments.length ? [borderSegments[0].start, ...borderSegments.map((segment) => segment.end)] : []
      ),
      from: attrs['data-edge-from'] || attrs['data-composition-edge-from'],
      to: attrs['data-edge-to'] || attrs['data-composition-edge-to'],
      id: attrs['data-edge-id'] || attrs['data-composition-edge-id'],
      key: attrs['data-edge-key'],
      label: attrs['data-edge-label'],
      offset: tag.index,
    });
  }

  return arrows;
}

function collectRelationshipLabelMasks(fragment, arrows) {
  const labels = [];
  for (const match of fragment.matchAll(/<g\b[^>]*\bdata-edge-(?:key|id|from)="[^"]*"[^>]*>[\s\S]*?<\/g>/gi)) {
    const group = match[0];
    const groupAttrs = parseAttrs(group.match(/<g\b[^>]*>/i)?.[0] || '');
    const rectTag = [...group.matchAll(/<rect\b[^>]*>/gi)]
      .map((item) => item[0])
      .find((tag) => /\bclass="[^"]*\bc-mask\b/.test(tag));
    if (!rectTag) continue;
    const attrs = parseAttrs(rectTag);
    const rect = {
      x: numberAttr(attrs, 'x'),
      y: numberAttr(attrs, 'y'),
      width: numberAttr(attrs, 'width'),
      height: numberAttr(attrs, 'height'),
    };
    if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) continue;
    const groupStart = match.index;
    const groupEnd = groupStart + group.length;
    const containedOwner = arrows.find((arrow) => (
      arrow.offset > groupStart
      && arrow.offset < groupEnd
      && arrow.from === groupAttrs['data-edge-from']
      && arrow.to === groupAttrs['data-edge-to']
      && (!groupAttrs['data-edge-id'] || !arrow.id || arrow.id === groupAttrs['data-edge-id'])
    ));
    const owner = arrows.find((arrow) => (
      groupAttrs['data-edge-key'] !== undefined && arrow.key === groupAttrs['data-edge-key']
    )) || containedOwner || arrows.find((arrow) => (
      arrow.id === groupAttrs['data-edge-id']
      && arrow.from === groupAttrs['data-edge-from']
      && arrow.to === groupAttrs['data-edge-to']
    ));
    if (!owner) continue;
    if (owner.key === undefined && groupAttrs['data-edge-key'] !== undefined) owner.key = groupAttrs['data-edge-key'];
    if (!owner.id && groupAttrs['data-edge-id']) owner.id = groupAttrs['data-edge-id'];
    if (!owner.label && groupAttrs['data-edge-label']) owner.label = groupAttrs['data-edge-label'];
    labels.push({
      relation: owner,
      relationIndex: owner.index,
      label: groupAttrs['data-edge-label'] || '',
      rect,
    });
  }
  return labels;
}

function roundedRect(rect) {
  return Object.fromEntries(Object.entries(rect).map(([key, value]) => [key, Math.round(value * 10) / 10]));
}

function roundedRouteMetrics(metrics) {
  return {
    ...metrics,
    maxStretch: metrics.maxStretch == null ? null : Math.round(metrics.maxStretch * 1000) / 1000,
    minSegmentPx: metrics.minSegmentPx == null ? null : Math.round(metrics.minSegmentPx * 10) / 10,
    minInteriorSegmentPx: metrics.minInteriorSegmentPx == null ? null : Math.round(metrics.minInteriorSegmentPx * 10) / 10,
  };
}

function parseRoutePoints(value) {
  if (!value) return null;
  const points = value.split(';').map((pair) => pair.split(',').map(Number));
  return points.length >= 2 && points.every(isPoint) ? points : null;
}

function collectRelationshipCrossings(arrows) {
  const relationships = arrows.filter((arrow) => arrow.from && arrow.to && arrow.segments.length);
  const crossings = [];
  for (let leftIndex = 0; leftIndex < relationships.length; leftIndex += 1) {
    const left = relationships[leftIndex];
    for (let rightIndex = leftIndex + 1; rightIndex < relationships.length; rightIndex += 1) {
      const right = relationships[rightIndex];
      if ([left.from, left.to].some((id) => id === right.from || id === right.to)) continue;
      let point = null;
      for (const leftSegment of left.segments) {
        for (const rightSegment of right.segments) {
          point = properSegmentIntersection(leftSegment.start, leftSegment.end, rightSegment.start, rightSegment.end);
          if (point) break;
        }
        if (point) break;
      }
      if (point) crossings.push({ left, right, point });
    }
  }
  return crossings;
}

function relationshipName(arrow) {
  return arrow.id
    ? `relationship id "${arrow.id}" ("${arrow.from}" -> "${arrow.to}")`
    : `relationship "${arrow.from}" -> "${arrow.to}"`;
}

function relationshipRecord(arrow) {
  const stableIndex = Number(arrow.key);
  return {
    id: arrow.id,
    from: arrow.from,
    to: arrow.to,
    label: arrow.label || '',
    collectionIndex: Number.isInteger(stableIndex) && stableIndex >= 0 ? stableIndex : arrow.index - 1,
    artifactIndex: arrow.index,
  };
}

function collectCompositionFrames(fragment) {
  const frames = [];
  for (const match of fragment.matchAll(/<(rect|path|line)\b[^>]*>/gi)) {
    const attrs = parseAttrs(match[0]);
    const kind = attrs['data-composition-frame-kind'];
    if (!kind) continue;
    const identity = attrs['data-composition-frame-id'] || frames.length;
    if (match[1].toLowerCase() === 'rect') {
      const frame = {
        kind,
        id: identity,
        x: numberAttr(attrs, 'x'),
        y: numberAttr(attrs, 'y'),
        width: numberAttr(attrs, 'width'),
        height: numberAttr(attrs, 'height'),
        radius: numberAttr(attrs, 'rx') || 0,
      };
      if ([frame.x, frame.y, frame.width, frame.height].every(Number.isFinite)) frames.push(frame);
      continue;
    }
    const segments = match[1].toLowerCase() === 'line'
      ? lineSegments(attrs)
      : pathSegments(attrs.d || '');
    for (const [segmentIndex, segment] of segments.entries()) {
      frames.push({
        kind,
        id: segments.length > 1 ? `${identity}:${segmentIndex}` : identity,
        shape: 'line',
        start: segment.start,
        end: segment.end,
      });
    }
  }
  return frames;
}

function frameName(frame) {
  return `${frame.kind || 'frame'} "${frame.id}"`;
}

function frameRecord(frame) {
  return { kind: frame.kind, id: frame.id };
}

function formatPoint(point) {
  return point.map((value) => Math.round(value * 10) / 10).join(', ');
}

function lineSegments(attrs) {
  const start = [numberAttr(attrs, 'x1'), numberAttr(attrs, 'y1')];
  const end = [numberAttr(attrs, 'x2'), numberAttr(attrs, 'y2')];
  if (!isPoint(start) || !isPoint(end)) return [];
  return [{ start, end }];
}

function pathSegments(d) {
  const points = pointsFromPath(d);
  const segments = [];
  for (let i = 1; i < points.length; i += 1) {
    segments.push({ start: points[i - 1], end: points[i] });
  }
  return segments;
}

// Border runs use exact visible primitives. Non-collinear Q curves are never
// flattened into chords here: a tangent or sampled near-horizontal curve is
// not a structural border run. A fully collinear Q remains a straight visible
// primitive and is included.
function straightPathSegments(d) {
  const tokens = d.match(/[MLHVQZmlhvqz]|[-+]?(?:\d*\.)?\d+(?:e[-+]?\d+)?/g) || [];
  const segments = [];
  let i = 0;
  let command = '';
  let current = [0, 0];
  let start = null;
  while (i < tokens.length) {
    if (isCommand(tokens[i])) command = tokens[i++];
    if (!command) break;
    const absolute = command === command.toUpperCase();
    switch (command.toUpperCase()) {
      case 'M':
      case 'L': {
        let first = true;
        while (i + 1 < tokens.length && !isCommand(tokens[i])) {
          const point = [Number.parseFloat(tokens[i++]), Number.parseFloat(tokens[i++])];
          if (!point.every(Number.isFinite)) break;
          const next = absolute ? point : [current[0] + point[0], current[1] + point[1]];
          if (command.toUpperCase() === 'L' || !first) segments.push({ start: current, end: next });
          current = next;
          if (!start) start = current;
          first = false;
        }
        break;
      }
      case 'H': {
        while (i < tokens.length && !isCommand(tokens[i])) {
          const value = Number.parseFloat(tokens[i++]);
          if (!Number.isFinite(value)) break;
          const next = [absolute ? value : current[0] + value, current[1]];
          segments.push({ start: current, end: next });
          current = next;
        }
        break;
      }
      case 'V': {
        while (i < tokens.length && !isCommand(tokens[i])) {
          const value = Number.parseFloat(tokens[i++]);
          if (!Number.isFinite(value)) break;
          const next = [current[0], absolute ? value : current[1] + value];
          segments.push({ start: current, end: next });
          current = next;
        }
        break;
      }
      case 'Q': {
        while (i + 3 < tokens.length && !isCommand(tokens[i])) {
          const values = [0, 0, 0, 0].map(() => Number.parseFloat(tokens[i++]));
          if (!values.every(Number.isFinite)) break;
          const control = absolute ? values.slice(0, 2) : [current[0] + values[0], current[1] + values[1]];
          const end = absolute ? values.slice(2, 4) : [current[0] + values[2], current[1] + values[3]];
          if (Math.abs(crossProduct(current, control, end)) <= 1e-9) segments.push({ start: current, end });
          current = end;
        }
        break;
      }
      case 'Z':
        if (start) segments.push({ start: current, end: start });
        current = start || current;
        command = '';
        break;
      default:
        return [];
    }
  }
  return segments.filter(({ start: a, end: b }) => isPoint(a) && isPoint(b));
}

function diagonalStraightSegments(arrow) {
  return arrow.borderSegments.flatMap(({ start, end }, segmentIndex) => (
    Math.abs(start[0] - end[0]) > 0.01 && Math.abs(start[1] - end[1]) > 0.01
      ? [{ segmentIndex, start, end }]
      : []
  ));
}

function collectLegendBoxes(fragment) {
  const boxes = [];

  for (const match of fragment.matchAll(/<rect\b[^>]*>/gi)) {
    const attrs = parseAttrs(match[0]);
    const x = numberAttr(attrs, 'x');
    const y = numberAttr(attrs, 'y');
    const width = numberAttr(attrs, 'width');
    const height = numberAttr(attrs, 'height');
    if ([x, y, width, height].every(Number.isFinite)) {
      boxes.push({ x1: x, y1: y, x2: x + width, y2: y + height, label: `rect@${x},${y}` });
    }
  }

  for (const match of fragment.matchAll(/<text\b([^>]*)>([\s\S]*?)<\/text>/gi)) {
    const attrs = parseAttrs(match[1]);
    const box = textBox(attrs, stripTags(match[2]).trim());
    if (box) boxes.push(box);
  }

  return boxes;
}

function collectLegendCollisions(arrows, boxes) {
  const collisions = [];
  for (const arrow of arrows) {
    for (const segment of arrow.segments) {
      for (const box of boxes) {
        if (segmentIntersectsBox(segment, padBox(box, 2))) {
          collisions.push({ arrow, box });
        }
      }
    }
  }
  return collisions;
}

function textBox(attrs, text) {
  const x = numberAttr(attrs, 'x');
  const y = numberAttr(attrs, 'y');
  const fontSize = Number.parseFloat(attrs['font-size'] || '10');
  if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(fontSize)) return null;
  const width = estimatedTextWidth(text, fontSize);
  const anchor = attrs['text-anchor'] || 'start';
  let x1 = x;
  if (anchor === 'middle') x1 = x - width / 2;
  if (anchor === 'end') x1 = x - width;
  return {
    x1,
    y1: y - fontSize,
    x2: x1 + width,
    y2: y + fontSize * 0.25,
    label: text || `text@${x},${y}`,
  };
}

function collectDesktopReadability(svgAttrs, fragment) {
  const viewBox = String(svgAttrs.viewBox || '').trim().split(/[\s,]+/).map(Number);
  const viewBoxWidth = viewBox.length === 4 ? viewBox[2] : Number.NaN;
  if (!Number.isFinite(viewBoxWidth) || viewBoxWidth <= 0) return null;
  const scale = Math.min(1, DESKTOP_READER_DIAGRAM_WIDTH / viewBoxWidth);
  let worst = null;
  for (const match of fragment.matchAll(/<text\b([^>]*)>([\s\S]*?)<\/text>/gi)) {
    const primary = /\bdata-node-label(?:\s*=|\s|$)/i.test(match[1]);
    const boundary = /\bdata-boundary-label(?:\s*=|\s|$)/i.test(match[1]);
    const context = /\bdata-detail\s*=\s*"context"/i.test(match[1]);
    if (!primary && !boundary && !context) continue;
    const attrs = parseAttrs(match[1]);
    const fontSize = Number.parseFloat(attrs['font-size'] || '');
    if (!Number.isFinite(fontSize)) continue;
    const projected = projectedNodeTextPx(fontSize, viewBoxWidth);
    if (projected >= MIN_PROJECTED_NODE_TEXT_PX) continue;
    const candidate = {
      viewBoxWidth,
      scale,
      text: stripTags(match[2]).trim(),
      detail: primary
        ? 'primary'
        : boundary ? 'boundary' : 'context',
      sourceFontPx: fontSize,
      projectedFontPx: projected,
    };
    if (!worst || candidate.projectedFontPx < worst.projectedFontPx) worst = candidate;
  }
  return worst;
}

function estimatedTextWidth(text, fontSize) {
  let units = 0;
  for (const char of text) units += char.charCodeAt(0) > 255 ? 1.8 : 0.62;
  return Math.max(fontSize, units * fontSize);
}

function pointsFromPath(d) {
  const tokens = d.match(/[MLHVQZmlhvqz]|[-+]?(?:\d*\.)?\d+(?:e[-+]?\d+)?/g) || [];
  const points = [];
  let i = 0;
  let command = '';
  let current = [0, 0];
  let start = null;

  while (i < tokens.length) {
    if (isCommand(tokens[i])) command = tokens[i++];
    if (!command) break;

    const absolute = command === command.toUpperCase();
    switch (command.toUpperCase()) {
      case 'M':
      case 'L': {
        while (i + 1 < tokens.length && !isCommand(tokens[i])) {
          const x = Number.parseFloat(tokens[i++]);
          const y = Number.parseFloat(tokens[i++]);
          if (!Number.isFinite(x) || !Number.isFinite(y)) break;
          current = absolute ? [x, y] : [current[0] + x, current[1] + y];
          points.push(current);
          if (!start) start = current;
        }
        break;
      }
      case 'H': {
        while (i < tokens.length && !isCommand(tokens[i])) {
          const x = Number.parseFloat(tokens[i++]);
          if (!Number.isFinite(x)) break;
          current = absolute ? [x, current[1]] : [current[0] + x, current[1]];
          points.push(current);
        }
        break;
      }
      case 'V': {
        while (i < tokens.length && !isCommand(tokens[i])) {
          const y = Number.parseFloat(tokens[i++]);
          if (!Number.isFinite(y)) break;
          current = absolute ? [current[0], y] : [current[0], current[1] + y];
          points.push(current);
        }
        break;
      }
      case 'Q': {
        while (i + 3 < tokens.length && !isCommand(tokens[i])) {
          const controlX = Number.parseFloat(tokens[i++]);
          const controlY = Number.parseFloat(tokens[i++]);
          const endX = Number.parseFloat(tokens[i++]);
          const endY = Number.parseFloat(tokens[i++]);
          if (![controlX, controlY, endX, endY].every(Number.isFinite)) break;
          const control = absolute
            ? [controlX, controlY]
            : [current[0] + controlX, current[1] + controlY];
          const end = absolute
            ? [endX, endY]
            : [current[0] + endX, current[1] + endY];
          const startPoint = current;
          for (let step = 1; step <= 8; step += 1) {
            const amount = step / 8;
            const remaining = 1 - amount;
            points.push([
              remaining * remaining * startPoint[0] + 2 * remaining * amount * control[0] + amount * amount * end[0],
              remaining * remaining * startPoint[1] + 2 * remaining * amount * control[1] + amount * amount * end[1],
            ]);
          }
          current = end;
        }
        break;
      }
      case 'Z': {
        if (start) points.push(start);
        break;
      }
      default:
        return [];
    }
  }

  return points.filter(isPoint);
}

function properSegmentIntersection(a, b, c, d) {
  const abC = crossProduct(a, b, c);
  const abD = crossProduct(a, b, d);
  const cdA = crossProduct(c, d, a);
  const cdB = crossProduct(c, d, b);
  const epsilon = 1e-9;
  const opposite = (left, right) => (left > epsilon && right < -epsilon) || (left < -epsilon && right > epsilon);
  if (!opposite(abC, abD) || !opposite(cdA, cdB)) return null;
  const denominator = (a[0] - b[0]) * (c[1] - d[1]) - (a[1] - b[1]) * (c[0] - d[0]);
  if (Math.abs(denominator) < epsilon) return null;
  const ab = a[0] * b[1] - a[1] * b[0];
  const cd = c[0] * d[1] - c[1] * d[0];
  return [
    (ab * (c[0] - d[0]) - (a[0] - b[0]) * cd) / denominator,
    (ab * (c[1] - d[1]) - (a[1] - b[1]) * cd) / denominator,
  ];
}

function crossProduct(a, b, c) {
  return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
}

function segmentIntersectsBox(segment, box) {
  const { start, end } = segment;
  if (pointInsideBox(start, box) || pointInsideBox(end, box)) return true;
  const edges = [
    [[box.x1, box.y1], [box.x2, box.y1]],
    [[box.x2, box.y1], [box.x2, box.y2]],
    [[box.x2, box.y2], [box.x1, box.y2]],
    [[box.x1, box.y2], [box.x1, box.y1]],
  ];
  return edges.some(([a, b]) => segmentsIntersect(start, end, a, b));
}

function segmentsIntersect(a, b, c, d) {
  const o1 = orientation(a, b, c);
  const o2 = orientation(a, b, d);
  const o3 = orientation(c, d, a);
  const o4 = orientation(c, d, b);

  if (o1 !== o2 && o3 !== o4) return true;
  if (o1 === 0 && onSegment(a, c, b)) return true;
  if (o2 === 0 && onSegment(a, d, b)) return true;
  if (o3 === 0 && onSegment(c, a, d)) return true;
  if (o4 === 0 && onSegment(c, b, d)) return true;
  return false;
}

function orientation(a, b, c) {
  const value = (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1]);
  if (Math.abs(value) < 1e-9) return 0;
  return value > 0 ? 1 : 2;
}

function onSegment(a, b, c) {
  return b[0] <= Math.max(a[0], c[0]) + 1e-9
    && b[0] + 1e-9 >= Math.min(a[0], c[0])
    && b[1] <= Math.max(a[1], c[1]) + 1e-9
    && b[1] + 1e-9 >= Math.min(a[1], c[1]);
}

function pointInsideBox(point, box) {
  return point[0] >= box.x1 && point[0] <= box.x2 && point[1] >= box.y1 && point[1] <= box.y2;
}

function padBox(box, padding) {
  return {
    ...box,
    x1: box.x1 - padding,
    y1: box.y1 - padding,
    x2: box.x2 + padding,
    y2: box.y2 + padding,
  };
}

function parseAttrs(tag) {
  const attrs = {};
  for (const match of tag.matchAll(/([\w:-]+)\s*=\s*"([^"]*)"/g)) attrs[match[1]] = match[2];
  return attrs;
}

function numberAttr(attrs, name) {
  return Number.parseFloat(attrs[name]);
}

function isCommand(token) {
  return /^[A-Za-z]$/.test(token);
}

function isPoint(point) {
  return Array.isArray(point) && point.length === 2 && point.every(Number.isFinite);
}

function stripTags(value) {
  return value.replace(/<[^>]*>/g, '');
}
```

## scripts/check-update.mjs

```js
#!/usr/bin/env node

import crypto from 'node:crypto';
import { constants as fsConstants } from 'node:fs';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { performance } from 'node:perf_hooks';
import { fileURLToPath } from 'node:url';

import {
  DEFAULT_MANIFEST_URL,
  SKILL_ID,
  UpdateContractError,
  compareSemver,
  isStableCoreVersion,
  parseSemver,
  validateLocalRelease,
  validateReleaseNotesUrl,
  validateStableUpdateManifest,
} from './update-contract.mjs';

const OPERATION_STATE_FILE = 'state.json';
const OPERATION_OWNER_FILE = 'owner.json';
const ACTIVE_CLAIM_DIRECTORY = 'active-claim';
const OPERATION_NAME = /^(reserved|pending|committed|fenced|cancelled)-(\d{1,20})$/;
const MAX_OPERATION_GENERATION = (10n ** 20n) - 1n;
const CHECK_TTL_MS = 72 * 60 * 60 * 1_000;
const MAX_CACHE_HORIZON_MS = Math.ceil(CHECK_TTL_MS * 1.2);
const FIRST_FAILURE_DELAY_MS = 6 * 60 * 60 * 1_000;
const LATER_FAILURE_DELAY_MS = 24 * 60 * 60 * 1_000;
const DEFAULT_TIMEOUT_MS = 1_000;
const MAX_RESPONSE_BYTES = 32 * 1_024;
const MAX_LOCAL_RELEASE_BYTES = 4 * 1_024;
const MAX_CACHE_STATE_BYTES = 64 * 1_024;
const MAX_CLAIM_OWNER_BYTES = 1_024;
const LONGEST_ISO_TIMESTAMP = '+275760-09-13T00:00:00.000Z';
const LOCK_STALE_MS = 30_000;
const ACK_LOCK_WAIT_MS = 1_200;
const LOCK_RETRY_DELAY_MS = 20;
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
const defaultReleasePath = path.resolve(scriptDirectory, '..', 'skill-release.json');

class FileIdentityChangedError extends Error {}

function silent(reason) {
  return { status: 'silent', reason };
}

function isPlainObject(value) {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}

function hasOnlyKeys(value, allowed) {
  return Object.keys(value).every((key) => allowed.has(key));
}

function validateManifest(value) {
  const manifest = validateStableUpdateManifest(value);
  return {
    version: manifest.version,
    targetDigest: `sha256:${manifest.artifact.sha256}`,
    severity: manifest.severity,
    releaseNotes: manifest.releaseNotes,
  };
}

function validateCachedCandidate(value) {
  if (!isPlainObject(value)
    || !hasOnlyKeys(value, new Set([
      'version', 'targetDigest', 'severity', 'releaseNotes',
    ]))) return null;
  try {
    if (!isStableCoreVersion(value.version)) return null;
    if (!DIGEST_PATTERN.test(value.targetDigest)
      || !['normal', 'security'].includes(value.severity)) return null;
    validateReleaseNotesUrl(value.releaseNotes, value.version);
    return { ...value };
  } catch {
    return null;
  }
}

function emptyState(installedVersion = null) {
  return {
    schemaVersion: 1,
    skillId: SKILL_ID,
    installedVersion,
    check: {
      nextCheckAt: null,
      consecutiveFailures: 0,
    },
    notification: {
      offeredDigests: [],
      acknowledgedDigests: [],
    },
  };
}

function encodeBoundedState(state) {
  const encoded = Buffer.from(`${JSON.stringify(state)}\n`, 'utf8');
  return encoded.length <= MAX_CACHE_STATE_BYTES ? encoded : null;
}

function stateAfterAcknowledgement(state, targetDigest) {
  const alreadyAcknowledged = state.notification.acknowledgedDigests.includes(targetDigest);
  return {
    ...state,
    notification: {
      offeredDigests: state.notification.offeredDigests
        .filter((digest) => digest !== targetDigest),
      acknowledgedDigests: alreadyAcknowledged
        ? [...state.notification.acknowledgedDigests]
        : [...state.notification.acknowledgedDigests, targetDigest],
    },
  };
}

function stateWithFailureCheck(state, nextCheckAt, consecutiveFailures, withdrawCandidate = false) {
  const failedState = {
    ...state,
    check: { nextCheckAt, consecutiveFailures },
  };
  if (withdrawCandidate) delete failedState.candidate;
  return failedState;
}

function encodeRecoverableState(state) {
  const encoded = encodeBoundedState(state);
  if (!encoded) return null;
  if (!encodeBoundedState(stateWithFailureCheck(state, LONGEST_ISO_TIMESTAMP, 2))) return null;
  let acknowledgementState = state;
  for (const targetDigest of state.notification.offeredDigests) {
    acknowledgementState = stateAfterAcknowledgement(acknowledgementState, targetDigest);
    if (!encodeBoundedState(acknowledgementState)
      || !encodeBoundedState(stateWithFailureCheck(
        acknowledgementState,
        LONGEST_ISO_TIMESTAMP,
        2,
      ))) return null;
  }
  return encoded;
}

function versionCacheDirectory(cacheDirectory, installedVersion) {
  const partition = crypto.createHash('sha256').update(installedVersion).digest('hex').slice(0, 24);
  return path.join(cacheDirectory, `version-${partition}`);
}

function normalizeState(value, installedVersion) {
  const effectiveInstalledVersion = installedVersion ?? value?.installedVersion ?? null;
  if (!isPlainObject(value) || value.schemaVersion !== 1 || value.skillId !== SKILL_ID
    || (installedVersion !== null && value.installedVersion !== installedVersion)
    || (value.installedVersion !== null && typeof value.installedVersion !== 'string')
    || !isPlainObject(value.check)
    || !(value.check.nextCheckAt === null || typeof value.check.nextCheckAt === 'string')
    || !Number.isSafeInteger(value.check.consecutiveFailures)
    || value.check.consecutiveFailures < 0
    || !isPlainObject(value.notification)) return null;
  if (effectiveInstalledVersion !== null) {
    try {
      parseSemver(effectiveInstalledVersion);
    } catch {
      return null;
    }
  }

  const state = emptyState(effectiveInstalledVersion);
  state.check.nextCheckAt = value.check.nextCheckAt;
  state.check.consecutiveFailures = value.check.consecutiveFailures;
  const { offeredDigests, acknowledgedDigests } = value.notification;
  if (!Array.isArray(offeredDigests)
    || offeredDigests.some((digest) => typeof digest !== 'string' || !DIGEST_PATTERN.test(digest))) {
    return null;
  }
  state.notification.offeredDigests = [...new Set(offeredDigests)];

  if (!Array.isArray(acknowledgedDigests)
    || acknowledgedDigests.some((digest) => typeof digest !== 'string' || !DIGEST_PATTERN.test(digest))) {
    return null;
  }
  state.notification.acknowledgedDigests = [...new Set(acknowledgedDigests)];
  const hasCandidate = Object.hasOwn(value, 'candidate');
  const candidate = validateCachedCandidate(value.candidate);
  if (hasCandidate && !candidate) return null;
  if (candidate) {
    if (effectiveInstalledVersion !== null
      && compareSemver(candidate.version, effectiveInstalledVersion) > 0
      && !state.notification.offeredDigests.includes(candidate.targetDigest)
      && !state.notification.acknowledgedDigests.includes(candidate.targetDigest)) return null;
    state.candidate = candidate;
  }
  return encodeRecoverableState(state) ? state : null;
}

function isSameFile(left, right) {
  if (left.dev !== right.dev || left.ino !== right.ino) return false;
  if (left.ino !== 0n) return true;
  if (left.birthtimeNs === 0n || right.birthtimeNs === 0n) return false;
  return left.birthtimeNs === right.birthtimeNs
    && left.mode === right.mode;
}

function assertBoundedRegularFile(metadata, maxBytes) {
  if (!metadata.isFile()
    || metadata.isSymbolicLink()
    || metadata.size > BigInt(maxBytes)) {
    throw new Error('unsafe JSON file');
  }
}

async function readJsonFile(target, maxBytes, expectedMetadata = null) {
  const pathMetadata = await fs.lstat(target, { bigint: true });
  if (expectedMetadata && !isSameFile(expectedMetadata, pathMetadata)) {
    throw new FileIdentityChangedError('JSON file identity changed');
  }
  assertBoundedRegularFile(pathMetadata, maxBytes);
  const flags = fsConstants.O_RDONLY
    | (fsConstants.O_NOFOLLOW ?? 0)
    | (fsConstants.O_NONBLOCK ?? 0);
  const handle = await fs.open(target, flags);
  try {
    const handleMetadata = await handle.stat({ bigint: true });
    const currentPathMetadata = await fs.lstat(target, { bigint: true });
    assertBoundedRegularFile(handleMetadata, maxBytes);
    assertBoundedRegularFile(currentPathMetadata, maxBytes);
    if (!isSameFile(pathMetadata, handleMetadata)
      || !isSameFile(currentPathMetadata, handleMetadata)) {
      throw new FileIdentityChangedError('JSON file changed while opening');
    }

    const chunks = [];
    let size = 0;
    while (size <= maxBytes) {
      const chunk = Buffer.allocUnsafe(Math.min(8 * 1_024, (maxBytes + 1) - size));
      const { bytesRead } = await handle.read(chunk, 0, chunk.length, size);
      if (bytesRead === 0) break;
      chunks.push(chunk.subarray(0, bytesRead));
      size += bytesRead;
    }
    if (size > maxBytes) throw new Error('JSON file is too large');
    return JSON.parse(Buffer.concat(chunks, size).toString('utf8'));
  } finally {
    await handle.close();
  }
}

function isWithinDirectory(directory, target) {
  const relative = path.relative(directory, target);
  return relative === ''
    || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`));
}

async function canonicalizeTrustedDirectoryPrefix(target) {
  const absoluteTarget = path.resolve(target);
  const trustedDirectories = [...new Set([os.homedir(), os.tmpdir()].map((directory) => (
    path.resolve(directory)
  )))].sort((left, right) => right.length - left.length);
  for (const trustedDirectory of trustedDirectories) {
    if (!isWithinDirectory(trustedDirectory, absoluteTarget)) continue;
    try {
      const canonicalDirectory = await fs.realpath(trustedDirectory);
      return path.resolve(canonicalDirectory, path.relative(trustedDirectory, absoluteTarget));
    } catch {
      // Fall back to validating the absolute path from its filesystem root.
    }
  }
  return target;
}

function assertSafeDirectory(metadata) {
  if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
    throw new Error('unsafe cache directory');
  }
}

class CachePathChangedError extends Error {}
class CacheOperationRaceError extends Error {}
class OperationFencedError extends Error {}

const preparedCacheDirectories = new Map();

function invalidateCacheToken(token, message, cause) {
  token.trusted = false;
  if (cause instanceof CachePathChangedError) return cause;
  const error = new CachePathChangedError(message);
  if (cause) error.cause = cause;
  return error;
}

async function verifyDirectorySnapshots(token, snapshots, description) {
  if (!token.trusted) throw new CachePathChangedError('cache directory is no longer trusted');
  try {
    for (const snapshot of snapshots) {
      const metadata = await fs.lstat(snapshot.directory, { bigint: true });
      assertSafeDirectory(metadata);
      if (!isSameFile(snapshot.metadata, metadata)) {
        throw new Error(`${description} changed`);
      }
    }
  } catch (error) {
    throw invalidateCacheToken(token, `${description} changed`, error);
  }
}

async function verifyCacheToken(token) {
  await verifyDirectorySnapshots(token, token.ancestorSnapshots, 'cache directory');
}

async function guardedCacheRead(token, read) {
  await verifyCacheToken(token);
  let result;
  try {
    result = await read();
  } catch (error) {
    await verifyCacheToken(token);
    throw error;
  }
  await verifyCacheToken(token);
  return result;
}

function cacheTokenFor(cacheDirectory) {
  const token = preparedCacheDirectories.get(path.resolve(cacheDirectory));
  if (!token || !token.trusted) {
    throw new CachePathChangedError('cache directory has not been prepared safely');
  }
  return token;
}

function resolveCacheTarget(token, target) {
  const resolved = path.resolve(target);
  if (resolved === token.directory || !isWithinDirectory(token.directory, resolved)) {
    throw invalidateCacheToken(token, 'cache mutation escaped its prepared directory');
  }
  return resolved;
}

async function captureMutationParentSnapshots(token, targets) {
  await verifyCacheToken(token);
  const snapshots = new Map();
  try {
    for (const target of targets) {
      const parent = path.dirname(resolveCacheTarget(token, target));
      const relative = path.relative(token.directory, parent);
      let current = token.directory;
      for (const component of relative.split(path.sep).filter(Boolean)) {
        current = path.join(current, component);
        if (snapshots.has(current)) continue;
        const metadata = await fs.lstat(current, { bigint: true });
        assertSafeDirectory(metadata);
        snapshots.set(current, { directory: current, metadata });
      }
    }
  } catch (error) {
    throw invalidateCacheToken(token, 'cache mutation parent changed', error);
  }
  const captured = [...snapshots.values()];
  await verifyMutationParentSnapshots(token, captured);
  await verifyCacheToken(token);
  return captured;
}

async function verifyMutationParentSnapshots(token, snapshots) {
  try {
    for (const snapshot of snapshots) {
      const metadata = await fs.lstat(snapshot.directory, { bigint: true });
      assertSafeDirectory(metadata);
      if (!isSameFile(snapshot.metadata, metadata)) {
        throw new Error('cache mutation parent changed');
      }
    }
  } catch (error) {
    const raced = new CacheOperationRaceError('cache mutation parent changed');
    raced.cause = error;
    throw raced;
  }
}

async function verifyMutationContext(token, parentSnapshots) {
  await verifyCacheToken(token);
  await verifyMutationParentSnapshots(token, parentSnapshots);
  await verifyCacheToken(token);
}

function assertSafeCacheEntry(metadata) {
  if (metadata.isSymbolicLink() || (!metadata.isDirectory() && !metadata.isFile())) {
    throw new Error('unsafe cache entry');
  }
}

function assertSafeRegularFile(metadata) {
  if (!metadata.isFile() || metadata.isSymbolicLink()) {
    throw new Error('unsafe cache file');
  }
}

function assertRenameableCacheEntry(metadata) {
  if (!metadata.isDirectory() && !metadata.isFile() && !metadata.isSymbolicLink()) {
    throw new Error('unsafe cache rename entry');
  }
}

async function stableCacheEntry(
  token,
  target,
  validate,
  expectedMetadata = null,
  missingIsOperationRace = false,
) {
  const resolved = resolveCacheTarget(token, target);
  try {
    const first = await fs.lstat(resolved, { bigint: true });
    validate(first);
    if (expectedMetadata && !isSameFile(expectedMetadata, first)) {
      throw new Error('cache entry identity changed');
    }
    await verifyCacheToken(token);
    const second = await fs.lstat(resolved, { bigint: true });
    validate(second);
    if (!isSameFile(first, second)
      || (expectedMetadata && !isSameFile(expectedMetadata, second))) {
      throw new Error('cache entry identity changed');
    }
    return second;
  } catch (error) {
    if (missingIsOperationRace && error?.code === 'ENOENT') {
      await verifyCacheToken(token);
      throw error;
    }
    throw invalidateCacheToken(token, 'cache mutation produced an untrusted object', error);
  }
}

async function guardedCacheEntryRead(
  token,
  target,
  validate,
  read,
  expectedMetadata = null,
) {
  const metadata = await stableCacheEntry(
    token,
    target,
    validate,
    expectedMetadata,
    true,
  );
  let result;
  try {
    result = await read(metadata);
  } catch (error) {
    await stableCacheEntry(token, target, validate, metadata, true);
    throw error;
  }
  await stableCacheEntry(token, target, validate, metadata, true);
  return result;
}

async function stablePreparedDirectory(token, target) {
  try {
    const first = await fs.lstat(target, { bigint: true });
    assertSafeDirectory(first);
    await verifyCacheToken(token);
    const second = await fs.lstat(target, { bigint: true });
    assertSafeDirectory(second);
    if (!isSameFile(first, second)) throw new Error('prepared directory identity changed');
    return second;
  } catch (error) {
    throw invalidateCacheToken(token, 'prepared directory could not be verified', error);
  }
}

async function assertCacheTargetAbsent(token, target) {
  const resolved = resolveCacheTarget(token, target);
  try {
    await fs.lstat(resolved, { bigint: true });
  } catch (error) {
    if (error?.code === 'ENOENT') return;
    throw invalidateCacheToken(token, 'cache mutation target could not be verified', error);
  }
  throw invalidateCacheToken(token, 'cache mutation left an unexpected object');
}

async function guardedCacheMutation(token, parentSnapshots, {
  mutate,
  verifyBefore = null,
  verifyAfter,
}) {
  await verifyMutationContext(token, parentSnapshots);
  if (verifyBefore) await verifyBefore();
  let result;
  try {
    result = await mutate();
  } catch (error) {
    await verifyMutationContext(token, parentSnapshots);
    throw error;
  }
  try {
    await verifyMutationContext(token, parentSnapshots);
    const verified = await verifyAfter(result);
    await verifyMutationContext(token, parentSnapshots);
    return verified ?? result;
  } catch (error) {
    if (error instanceof CacheOperationRaceError) throw error;
    throw invalidateCacheToken(token, 'cache mutation could not be verified', error);
  }
}

async function cacheMkdirWithToken(token, target, options, parentSnapshots = null) {
  const resolved = path.resolve(target);
  const parents = parentSnapshots ?? await captureMutationParentSnapshots(token, [resolved]);
  return guardedCacheMutation(token, parents, {
    mutate: () => fs.mkdir(resolved, options),
    verifyAfter: () => (parentSnapshots === null
      ? stableCacheEntry(token, resolved, assertSafeDirectory)
      : stablePreparedDirectory(token, resolved)),
  });
}

async function cacheMkdir(cacheDirectory, target, options) {
  return cacheMkdirWithToken(cacheTokenFor(cacheDirectory), target, options);
}

async function cacheWriteFile(cacheDirectory, target, data, options) {
  if (options?.flag !== 'wx') {
    throw new Error('cache files must be created exclusively');
  }
  const token = cacheTokenFor(cacheDirectory);
  const resolved = resolveCacheTarget(token, target);
  const parents = await captureMutationParentSnapshots(token, [resolved]);
  return guardedCacheMutation(token, parents, {
    mutate: async () => {
      const flags = fsConstants.O_WRONLY
        | fsConstants.O_CREAT
        | fsConstants.O_EXCL
        | (fsConstants.O_NOFOLLOW ?? 0);
      const handle = await fs.open(resolved, flags, options.mode);
      try {
        const openedMetadata = await handle.stat({ bigint: true });
        if (!openedMetadata.isFile() || openedMetadata.isSymbolicLink()) {
          throw new Error('cache write did not open a regular file');
        }
        await handle.writeFile(data, options.encoding ? { encoding: options.encoding } : undefined);
        const writtenMetadata = await handle.stat({ bigint: true });
        const expectedBytes = Buffer.isBuffer(data)
          ? data.length
          : Buffer.byteLength(data, options.encoding || 'utf8');
        if (!isSameFile(openedMetadata, writtenMetadata)
          || writtenMetadata.size !== BigInt(expectedBytes)) {
          throw new Error('cache write did not preserve the opened file identity and size');
        }
        return writtenMetadata;
      } finally {
        await handle.close();
      }
    },
    verifyAfter: (writtenMetadata) => stableCacheEntry(token, resolved, (metadata) => {
      if (!metadata.isFile() || metadata.isSymbolicLink()) {
        throw new Error('cache write did not create a regular file');
      }
    }, writtenMetadata),
  });
}

async function cacheRename(
  cacheDirectory,
  source,
  destination,
  validate,
  expectedSourceMetadata = null,
) {
  if (typeof validate !== 'function') {
    throw new TypeError('cache rename requires an explicit entry validator');
  }
  const token = cacheTokenFor(cacheDirectory);
  const resolvedSource = resolveCacheTarget(token, source);
  const resolvedDestination = resolveCacheTarget(token, destination);
  const parents = await captureMutationParentSnapshots(
    token,
    [resolvedSource, resolvedDestination],
  );
  const sourceMetadata = await stableCacheEntry(
    token,
    resolvedSource,
    validate,
    expectedSourceMetadata,
    true,
  );
  return guardedCacheMutation(token, parents, {
    verifyBefore: () => stableCacheEntry(
      token,
      resolvedSource,
      validate,
      sourceMetadata,
      true,
    ),
    mutate: () => fs.rename(resolvedSource, resolvedDestination),
    verifyAfter: async () => {
      await assertCacheTargetAbsent(token, resolvedSource);
      return stableCacheEntry(
        token,
        resolvedDestination,
        validate,
        sourceMetadata,
      );
    },
  });
}

async function cacheRm(cacheDirectory, target, options) {
  if (options?.recursive) {
    throw new Error('recursive cache removal is not allowed');
  }
  const token = cacheTokenFor(cacheDirectory);
  const resolved = resolveCacheTarget(token, target);
  const parents = await captureMutationParentSnapshots(token, [resolved]);
  let initialMetadata;
  try {
    initialMetadata = await fs.lstat(resolved, { bigint: true });
  } catch (error) {
    if (error?.code === 'ENOENT' && options?.force) {
      await verifyMutationContext(token, parents);
      return;
    }
    throw error;
  }
  try {
    assertSafeCacheEntry(initialMetadata);
  } catch (error) {
    throw invalidateCacheToken(token, 'cache removal target is unsafe', error);
  }
  const targetMetadata = await stableCacheEntry(
    token,
    resolved,
    assertSafeCacheEntry,
    initialMetadata,
  );
  return guardedCacheMutation(token, parents, {
    verifyBefore: () => stableCacheEntry(
      token,
      resolved,
      assertSafeCacheEntry,
      targetMetadata,
    ),
    mutate: () => fs.rm(resolved, options),
    verifyAfter: () => assertCacheTargetAbsent(token, resolved),
  });
}

async function prepareCacheDirectory(cacheDirectory) {
  const absoluteTarget = path.resolve(cacheDirectory);
  const target = await canonicalizeTrustedDirectoryPrefix(absoluteTarget);
  const parsed = path.parse(target);
  const components = target.slice(parsed.root.length).split(path.sep).filter(Boolean);
  let current = parsed.root;
  const token = {
    directory: target,
    ancestorSnapshots: [],
    trusted: true,
  };

  for (const component of components) {
    current = path.join(current, component);
    let metadata;
    try {
      metadata = await fs.lstat(current, { bigint: true });
    } catch (error) {
      if (error?.code !== 'ENOENT') throw error;
      try {
        await cacheMkdirWithToken(token, current, { mode: 0o700 }, []);
      } catch (mkdirError) {
        if (mkdirError?.code !== 'EEXIST') throw mkdirError;
      }
      metadata = await fs.lstat(current, { bigint: true });
    }
    assertSafeDirectory(metadata);
    token.ancestorSnapshots.push({ directory: current, metadata });
  }

  await verifyCacheToken(token);
  preparedCacheDirectories.set(target, token);
  return target;
}

function operationName(kind, generation) {
  return `${kind}-${generation.toString().padStart(20, '0')}`;
}

function parseOperationName(name) {
  const match = OPERATION_NAME.exec(name);
  if (!match) return null;
  return { kind: match[1], generation: BigInt(match[2]) };
}

async function listOperations(cacheDirectory, token = cacheTokenFor(cacheDirectory)) {
  return guardedCacheRead(token, async () => {
    try {
      const entries = await fs.readdir(cacheDirectory, { withFileTypes: true });
      return entries.flatMap((entry) => {
        const parsed = parseOperationName(entry.name);
        if (!parsed) return [];
        if (!entry.isDirectory() || entry.isSymbolicLink()) {
          throw invalidateCacheToken(
            token,
            `cache operation ${entry.name} is not a safe directory`,
          );
        }
        return [{ ...parsed, name: entry.name, directory: path.join(cacheDirectory, entry.name) }];
      });
    } catch (error) {
      if (error?.code === 'ENOENT') return [];
      throw error;
    }
  });
}

async function readState(cacheDirectory, installedVersion = null) {
  const token = cacheTokenFor(cacheDirectory);
  return guardedCacheRead(token, async () => {
    const operations = (await listOperations(cacheDirectory, token))
      .filter((operation) => operation.kind === 'committed')
      .sort((left, right) => (left.generation > right.generation ? -1 : 1));
    for (const operation of operations) {
      try {
        const state = await guardedCacheEntryRead(
          token,
          operation.directory,
          assertSafeDirectory,
          async () => normalizeState(
            await readJsonFile(
              path.join(operation.directory, OPERATION_STATE_FILE),
              MAX_CACHE_STATE_BYTES,
            ),
            installedVersion,
          ),
        );
        if (state) return state;
      } catch (error) {
        if (error instanceof FileIdentityChangedError
          || error instanceof CachePathChangedError
          || error instanceof CacheOperationRaceError) throw error;
        // An incomplete or corrupt generation is ignored in favor of the previous commit.
      }
    }
    return emptyState(installedVersion);
  });
}

async function operationIsActive(
  cacheDirectory,
  operation,
  token = cacheTokenFor(cacheDirectory),
) {
  try {
    return await guardedCacheEntryRead(
      token,
      operation.directory,
      assertSafeDirectory,
      async (directoryMetadata) => {
        try {
          const ownerMetadata = await fs.lstat(
            path.join(operation.directory, OPERATION_OWNER_FILE),
            { bigint: true },
          );
          if (!ownerMetadata.isFile() || ownerMetadata.isSymbolicLink()) return false;
          return metadataIsWithinLease(ownerMetadata);
        } catch (error) {
          if (error?.code !== 'ENOENT') throw error;
          return metadataIsWithinLease(directoryMetadata);
        }
      },
    );
  } catch (error) {
    if (error?.code === 'ENOENT') return false;
    throw error;
  }
}

function validateClaimOwner(value) {
  if (!isPlainObject(value)
    || !hasOnlyKeys(value, new Set(['pid', 'token', 'generation']))
    || !Number.isSafeInteger(value.pid)
    || value.pid <= 0
    || typeof value.token !== 'string'
    || !/^[a-f0-9]{32}$/.test(value.token)
    || typeof value.generation !== 'string'
    || !/^[1-9]\d*$/.test(value.generation)) return null;
  return { ...value, generation: BigInt(value.generation) };
}

function metadataIsWithinLease(metadata) {
  if (typeof metadata.mtimeNs === 'bigint') {
    const ageNs = (BigInt(Date.now()) * 1_000_000n) - metadata.mtimeNs;
    const absoluteAgeNs = ageNs < 0n ? -ageNs : ageNs;
    return absoluteAgeNs <= BigInt(LOCK_STALE_MS) * 1_000_000n;
  }
  return Math.abs(Date.now() - metadata.mtimeMs) <= LOCK_STALE_MS;
}

function corruptClaimStatus(directory, directoryMetadata, ownerMetadata = directoryMetadata) {
  if (metadataIsWithinLease(ownerMetadata)) {
    return { state: 'busy', directory, metadata: directoryMetadata, owner: null };
  }
  const fingerprint = crypto.createHash('sha256')
    .update(JSON.stringify({
      directory: {
        dev: directoryMetadata.dev.toString(),
        ino: directoryMetadata.ino.toString(),
        mode: directoryMetadata.mode.toString(),
        birthtimeNs: directoryMetadata.birthtimeNs.toString(),
      },
    }))
    .digest('hex')
    .slice(0, 24);
  return {
    state: 'releasable',
    directory,
    metadata: directoryMetadata,
    key: `corrupt-${fingerprint}`,
    owner: null,
  };
}

async function inspectActiveClaim(cacheDirectory) {
  const token = cacheTokenFor(cacheDirectory);
  return guardedCacheRead(token, async () => {
    const directory = path.join(cacheDirectory, ACTIVE_CLAIM_DIRECTORY);
    let directoryMetadata;
    try {
      directoryMetadata = await fs.lstat(directory, { bigint: true });
    } catch (error) {
      if (error?.code === 'ENOENT') return { state: 'absent', directory };
      throw error;
    }
    if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink()) {
      return corruptClaimStatus(directory, directoryMetadata);
    }
    return guardedCacheEntryRead(
      token,
      directory,
      assertSafeDirectory,
      async (stableDirectoryMetadata) => {
        const ownerPath = path.join(directory, OPERATION_OWNER_FILE);
        let ownerMetadata = stableDirectoryMetadata;
        let owner = null;
        try {
          ownerMetadata = await fs.lstat(ownerPath, { bigint: true });
          if (!ownerMetadata.isFile()
            || ownerMetadata.isSymbolicLink()
            || ownerMetadata.size > BigInt(MAX_CLAIM_OWNER_BYTES)) {
            return corruptClaimStatus(directory, stableDirectoryMetadata, ownerMetadata);
          }
          const ownerSource = await readJsonFile(
            ownerPath,
            MAX_CLAIM_OWNER_BYTES,
            ownerMetadata,
          );
          owner = validateClaimOwner(ownerSource);
        } catch (error) {
          if (error instanceof FileIdentityChangedError) throw error;
          // A malformed claim is recoverable only after the same hard lease as a crashed owner.
        }

        if (owner) {
          const operations = await listOperations(cacheDirectory, token);
          if (operations.some((operation) => operation.generation === owner.generation
            && ['committed', 'fenced', 'cancelled'].includes(operation.kind))) {
            return {
              state: 'releasable',
              directory,
              metadata: stableDirectoryMetadata,
              key: owner.generation.toString(),
              owner,
            };
          }
          const pending = operations.find((operation) => operation.kind === 'pending'
            && operation.generation === owner.generation);
          if (pending) {
            if (await operationIsActive(cacheDirectory, pending, token)) {
              return {
                state: 'busy',
                directory,
                metadata: stableDirectoryMetadata,
                owner,
              };
            }
            return {
              state: 'releasable',
              directory,
              metadata: stableDirectoryMetadata,
              key: owner.generation.toString(),
              owner,
            };
          }
        }

        return corruptClaimStatus(directory, stableDirectoryMetadata, ownerMetadata);
      },
      directoryMetadata,
    );
  });
}

async function retireActiveClaim(cacheDirectory, claim) {
  const retirement = path.join(cacheDirectory, `retired-claim-${claim.key}`);
  await cacheMkdir(cacheDirectory, retirement, { mode: 0o700 }).catch((error) => {
    if (error?.code !== 'EEXIST') throw error;
  });
  try {
    const retirementMetadata = await fs.lstat(retirement);
    if (!retirementMetadata.isDirectory() || retirementMetadata.isSymbolicLink()) return false;
  } catch (error) {
    if (error instanceof CacheOperationRaceError) return false;
    if (error?.code === 'ENOENT') return false;
    throw error;
  }
  try {
    const metadata = await fs.lstat(claim.directory);
    if (metadata.isDirectory() && !metadata.isSymbolicLink()) {
      await cacheWriteFile(
        cacheDirectory,
        path.join(claim.directory, '.retirement-guard'),
        `${claim.key}\n`,
        { encoding: 'utf8', flag: 'wx', mode: 0o600 },
      ).catch((error) => {
        if (error?.code !== 'EEXIST') throw error;
      });
    }
  } catch (error) {
    if (error instanceof CacheOperationRaceError) return false;
    if (error?.code === 'ENOENT') return false;
    throw error;
  }
  try {
    await cacheRename(
      cacheDirectory,
      claim.directory,
      path.join(retirement, 'active'),
      assertRenameableCacheEntry,
      claim.metadata,
    );
    return true;
  } catch (error) {
    if (error instanceof CacheOperationRaceError) return false;
    if (['ENOENT', 'EEXIST', 'ENOTEMPTY', 'EPERM'].includes(error?.code)) return false;
    throw error;
  }
}

async function prepareOperationClaim(cacheDirectory, operation) {
  const directory = path.join(cacheDirectory, operationName('claim', operation.generation));
  await cacheMkdir(cacheDirectory, directory, { mode: 0o700 });
  await cacheWriteFile(
    cacheDirectory,
    path.join(directory, OPERATION_OWNER_FILE),
    `${JSON.stringify({
      pid: process.pid,
      token: operation.token,
      generation: operation.generation.toString(),
    })}\n`,
    { encoding: 'utf8', flag: 'wx', mode: 0o600 },
  );
  operation.preparedClaimDirectory = directory;
}

async function discardPreparedClaim(cacheDirectory, operation) {
  if (!operation.preparedClaimDirectory) return;
  const discarded = path.join(
    cacheDirectory,
    `discarded-claim-${operation.generation.toString().padStart(20, '0')}-${operation.token}`,
  );
  try {
    await cacheRename(
      cacheDirectory,
      operation.preparedClaimDirectory,
      discarded,
      assertSafeDirectory,
    );
  } catch (error) {
    if (error?.code !== 'ENOENT') throw error;
  }
  delete operation.preparedClaimDirectory;
}

async function promoteOperationClaim(cacheDirectory, operation) {
  await prepareOperationClaim(cacheDirectory, operation);
  const activeDirectory = path.join(cacheDirectory, ACTIVE_CLAIM_DIRECTORY);
  for (let attempt = 0; attempt < 8; attempt += 1) {
    if (await operationIsSuperseded(cacheDirectory, operation)) {
      await discardPreparedClaim(cacheDirectory, operation);
      return false;
    }
    const active = await inspectActiveClaim(cacheDirectory);
    if (active.state === 'busy') {
      await discardPreparedClaim(cacheDirectory, operation);
      return false;
    }
    if (active.state === 'releasable') {
      if (active.owner?.generation > operation.generation) {
        await discardPreparedClaim(cacheDirectory, operation);
        return false;
      }
      await retireActiveClaim(cacheDirectory, active);
      continue;
    }
    try {
      await cacheRename(
        cacheDirectory,
        operation.preparedClaimDirectory,
        activeDirectory,
        assertSafeDirectory,
      );
      delete operation.preparedClaimDirectory;
      if (!await operationIsActive(cacheDirectory, operation)) return false;
      return true;
    } catch (promotionError) {
      const raced = await inspectActiveClaim(cacheDirectory);
      if (raced.state === 'absent') {
        if (['EEXIST', 'ENOTEMPTY', 'EPERM'].includes(promotionError?.code)) continue;
        await discardPreparedClaim(cacheDirectory, operation);
        throw promotionError;
      }
      continue;
    }
  }
  await discardPreparedClaim(cacheDirectory, operation);
  return false;
}

async function operationOwnsActiveClaim(cacheDirectory, operation) {
  const active = await inspectActiveClaim(cacheDirectory);
  return active.state === 'busy'
    && active.owner?.generation === operation.generation
    && active.owner.token === operation.token;
}

async function hasActivePendingOperation(cacheDirectory) {
  const token = cacheTokenFor(cacheDirectory);
  return guardedCacheRead(token, async () => {
    const pending = (await listOperations(cacheDirectory, token))
      .filter((operation) => operation.kind === 'pending');
    return (await Promise.all(pending.map(
      (operation) => operationIsActive(cacheDirectory, operation, token),
    ))).some(Boolean);
  });
}

async function reserveOperation(cacheDirectory) {
  cacheDirectory = await prepareCacheDirectory(cacheDirectory);
  const operations = await listOperations(cacheDirectory);
  const occupied = new Set(operations.map((operation) => operation.generation.toString()));
  let generation = 1n;
  while (true) {
    while (occupied.has(generation.toString())) generation += 1n;
    if (generation > MAX_OPERATION_GENERATION) throw new Error('operation generation space exhausted');
    const reservation = path.join(cacheDirectory, operationName('reserved', generation));
    try {
      await cacheMkdir(cacheDirectory, reservation, { mode: 0o700 });
    } catch (error) {
      if (error?.code !== 'EEXIST') throw error;
      occupied.add(generation.toString());
      generation += 1n;
      continue;
    }
    const directory = path.join(cacheDirectory, operationName('pending', generation));
    try {
      await cacheMkdir(cacheDirectory, directory, { mode: 0o700 });
      const operation = {
        generation,
        token: crypto.randomBytes(16).toString('hex'),
        directory,
      };
      try {
        await cacheWriteFile(
          cacheDirectory,
          path.join(directory, OPERATION_OWNER_FILE),
          `${JSON.stringify({ pid: process.pid, token: operation.token })}\n`,
          { encoding: 'utf8', flag: 'wx', mode: 0o600 },
        );
      } catch (error) {
        if (await operationWasFenced(cacheDirectory, operation)) {
          const fencedError = new OperationFencedError('operation was fenced while reserving');
          fencedError.cause = error;
          throw fencedError;
        }
        await cacheRename(
          cacheDirectory,
          directory,
          path.join(cacheDirectory, operationName('cancelled', generation)),
          assertSafeDirectory,
        ).catch(() => {});
        throw error;
      }
      return operation;
    } catch (error) {
      if (error?.code !== 'EEXIST') throw error;
      occupied.add(generation.toString());
      generation += 1n;
    }
  }
}

async function operationIsSuperseded(cacheDirectory, operation) {
  return (await listOperations(cacheDirectory))
    .some((candidate) => candidate.generation > operation.generation);
}

async function operationWasFenced(cacheDirectory, operation) {
  const token = cacheTokenFor(cacheDirectory);
  return guardedCacheRead(token, async () => {
    try {
      await fs.lstat(operation.directory, { bigint: true });
      return false;
    } catch (error) {
      if (error?.code !== 'ENOENT') throw error;
    }
    const fencedDirectory = path.join(
      cacheDirectory,
      operationName('fenced', operation.generation),
    );
    try {
      await stableCacheEntry(
        token,
        fencedDirectory,
        assertSafeDirectory,
        null,
        true,
      );
      return true;
    } catch (error) {
      if (error?.code === 'ENOENT') return false;
      throw error;
    }
  });
}

async function transitionOperation(cacheDirectory, operation, kind) {
  const destination = path.join(cacheDirectory, operationName(kind, operation.generation));
  await cacheRename(cacheDirectory, operation.directory, destination, assertSafeDirectory);
}

async function cancelOperation(cacheDirectory, operation) {
  try {
    await transitionOperation(cacheDirectory, operation, 'cancelled');
  } catch (error) {
    // A higher generation may already have fenced this unique pending directory.
    if (error?.code !== 'ENOENT') throw error;
  }
}

async function acquireOperation(cacheDirectory) {
  cacheDirectory = await prepareCacheDirectory(cacheDirectory);
  if ((await inspectActiveClaim(cacheDirectory)).state === 'busy') return null;
  if (await hasActivePendingOperation(cacheDirectory)) return null;
  let operation;
  try {
    operation = await reserveOperation(cacheDirectory);
  } catch (error) {
    if (error instanceof OperationFencedError) return null;
    throw error;
  }
  if (!await operationIsSuperseded(cacheDirectory, operation)
    && await promoteOperationClaim(cacheDirectory, operation)) return operation;
  await cancelOperation(cacheDirectory, operation);
  return null;
}

async function fenceLowerOperations(cacheDirectory, operation) {
  const lower = (await listOperations(cacheDirectory))
    .filter((candidate) => candidate.kind === 'pending'
      && candidate.generation < operation.generation)
    .sort((left, right) => (left.generation < right.generation ? -1 : 1));
  for (const candidate of lower) {
    try {
      await cacheRename(
        cacheDirectory,
        candidate.directory,
        path.join(cacheDirectory, operationName('fenced', candidate.generation)),
        assertSafeDirectory,
      );
    } catch (error) {
      if (error?.code !== 'ENOENT') throw error;
    }
  }
}

async function commitOperation(cacheDirectory, operation, installedVersion, mutate) {
  if (!await operationOwnsActiveClaim(cacheDirectory, operation)) {
    await cancelOperation(cacheDirectory, operation);
    return { fenced: true, result: silent('check-in-progress') };
  }
  await fenceLowerOperations(cacheDirectory, operation);
  if (!await operationOwnsActiveClaim(cacheDirectory, operation)) {
    await cancelOperation(cacheDirectory, operation);
    return { fenced: true, result: silent('check-in-progress') };
  }
  const state = await readState(cacheDirectory, installedVersion);
  const decision = mutate(state);
  if (!decision.state) {
    await cancelOperation(cacheDirectory, operation);
    return { fenced: false, result: decision.result };
  }
  let committedState = decision.state;
  let committedResult = decision.result;
  let encodedState = encodeRecoverableState(committedState);
  if (!encodedState && decision.capacityFallback) {
    committedState = decision.capacityFallback.state;
    committedResult = decision.capacityFallback.result;
    encodedState = encodeRecoverableState(committedState);
  }
  if (!encodedState) {
    await cancelOperation(cacheDirectory, operation);
    return { fenced: false, result: silent('cache-unavailable') };
  }
  const temporary = path.join(
    operation.directory,
    `.state.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`,
  );
  try {
    await cacheWriteFile(cacheDirectory, temporary, encodedState, {
      flag: 'wx',
      mode: 0o600,
    });
    await cacheRename(
      cacheDirectory,
      temporary,
      path.join(operation.directory, OPERATION_STATE_FILE),
      assertSafeRegularFile,
    );
    await transitionOperation(cacheDirectory, operation, 'committed');
    return { fenced: false, result: committedResult };
  } catch (error) {
    if (await operationWasFenced(cacheDirectory, operation)) {
      return { fenced: true, result: silent('check-in-progress') };
    }
    await cacheRm(cacheDirectory, temporary, { force: true }).catch(() => {});
    throw error;
  }
}

function nextSuccessfulCheck(nowMs, random) {
  const boundedRandom = Math.min(1, Math.max(0, Number(random()) || 0));
  const multiplier = 0.8 + (boundedRandom * 0.4);
  return new Date(nowMs + Math.round(CHECK_TTL_MS * multiplier)).toISOString();
}

function nextFailedCheck(nowMs, failures) {
  const delay = failures <= 1 ? FIRST_FAILURE_DELAY_MS : LATER_FAILURE_DELAY_MS;
  return new Date(nowMs + delay).toISOString();
}

function stateAfterFailedCheck(state, nowMs, withdrawCandidate = false) {
  const failures = Math.min(state.check.consecutiveFailures + 1, 2);
  return stateWithFailureCheck(
    state,
    nextFailedCheck(nowMs, failures),
    failures,
    withdrawCandidate,
  );
}

function eventKeyForDigest(targetDigest) {
  return `${SKILL_ID}@${targetDigest}`;
}

function digestForEventKey(eventKey) {
  const prefix = `${SKILL_ID}@`;
  if (typeof eventKey !== 'string' || !eventKey.startsWith(prefix)) return null;
  const digest = eventKey.slice(prefix.length);
  return DIGEST_PATTERN.test(digest) ? digest : null;
}

function notification(localRelease, candidate) {
  return {
    status: 'update_available',
    eventKey: eventKeyForDigest(candidate.targetDigest),
    installedVersion: localRelease.version,
    latestVersion: candidate.version,
    targetDigest: candidate.targetDigest,
    severity: candidate.severity,
    summary: `Archify ${candidate.version} is available; see the official release notes for details.`,
    releaseNotes: candidate.releaseNotes,
  };
}

function resultForCandidate(localRelease, state) {
  if (!state.candidate) return silent('cache-valid');
  const comparison = compareSemver(state.candidate.version, localRelease.version);
  if (comparison <= 0) return silent('current');
  if (state.notification.acknowledgedDigests.includes(state.candidate.targetDigest)) {
    return silent('already-notified');
  }
  return notification(localRelease, state.candidate);
}

async function readBoundedBody(response) {
  const contentLength = Number(response.headers.get('content-length'));
  if (Number.isFinite(contentLength) && contentLength > MAX_RESPONSE_BYTES) {
    await cancelResponseBody(response);
    throw new UpdateContractError('manifest response is too large');
  }
  if (!response.body || typeof response.body.getReader !== 'function') {
    throw new UpdateContractError('manifest response has no bounded stream');
  }
  const reader = response.body.getReader();
  const chunks = [];
  let size = 0;
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      size += value.byteLength;
      if (size > MAX_RESPONSE_BYTES) {
        await reader.cancel().catch(() => {});
        throw new UpdateContractError('manifest response is too large');
      }
      chunks.push(value);
    }
  } finally {
    reader.releaseLock();
  }
  const joined = new Uint8Array(size);
  let offset = 0;
  for (const chunk of chunks) {
    joined.set(chunk, offset);
    offset += chunk.byteLength;
  }
  try {
    return new TextDecoder('utf-8', { fatal: true }).decode(joined);
  } catch {
    throw new UpdateContractError('manifest is not valid UTF-8');
  }
}

async function cancelResponseBody(response) {
  try {
    if (response.body && typeof response.body.cancel === 'function') {
      await response.body.cancel();
    }
  } catch {
    // Network cleanup is best-effort; the original response classification wins.
  }
}

async function fetchCandidate({ fetchImpl, manifestUrl, timeoutMs }) {
  if (manifestUrl !== DEFAULT_MANIFEST_URL) throw new UpdateContractError('unexpected manifest URL');
  const controller = new AbortController();
  let timer;
  const timeout = new Promise((_resolve, reject) => {
    timer = setTimeout(() => {
      controller.abort();
      reject(new Error('update check timed out'));
    }, timeoutMs);
  });
  const request = (async () => {
    const headers = { accept: 'application/json' };
    const response = await fetchImpl(manifestUrl, {
      method: 'GET',
      headers,
      redirect: 'error',
      signal: controller.signal,
    });
    if (response.status !== 200) {
      await cancelResponseBody(response);
      throw new Error(`manifest returned HTTP ${response.status}`);
    }
    const mediaType = response.headers.get('content-type')?.split(';', 1)[0].trim().toLowerCase();
    if (mediaType !== 'application/json') {
      await cancelResponseBody(response);
      throw new UpdateContractError('manifest is not JSON');
    }
    const source = await readBoundedBody(response);
    let decoded;
    try {
      decoded = JSON.parse(source);
    } catch {
      throw new UpdateContractError('manifest is not valid bounded JSON');
    }
    return { candidate: validateManifest(decoded) };
  })();
  try {
    return await Promise.race([request, timeout]);
  } finally {
    clearTimeout(timer);
  }
}

function defaultCacheDirectory() {
  const homeDirectory = os.homedir();
  if (process.platform === 'win32') {
    const localData = process.env.LOCALAPPDATA;
    return path.join(localData && path.isAbsolute(localData) ? localData : path.join(homeDirectory, 'AppData', 'Local'), 'archify-skill');
  }
  if (process.platform === 'darwin') return path.join(homeDirectory, 'Library', 'Caches', 'archify-skill');
  const xdgCache = process.env.XDG_CACHE_HOME;
  return path.join(xdgCache && path.isAbsolute(xdgCache) ? xdgCache : path.join(homeDirectory, '.cache'), 'archify-skill');
}

function freshStateResult(localRelease, state, nowMs) {
  const nextCheckAt = Date.parse(state.check.nextCheckAt || '');
  if (Number.isFinite(nextCheckAt) && nextCheckAt > nowMs
    && nextCheckAt <= nowMs + MAX_CACHE_HORIZON_MS) {
    return resultForCandidate(localRelease, state);
  }
  return null;
}

async function resultAfterLosingOperation(cacheDirectory, operation, localRelease) {
  await cancelOperation(cacheDirectory, operation);
  const cached = await readState(cacheDirectory, localRelease.version);
  if (cached.candidate
    && cached.notification.offeredDigests.includes(cached.candidate.targetDigest)) {
    return resultForCandidate(localRelease, cached);
  }
  return silent('check-in-progress');
}

async function waitUntilRetry(deadline, monotonicNow) {
  let remaining;
  try {
    remaining = deadline - Number(monotonicNow());
  } catch {
    return false;
  }
  if (!Number.isFinite(remaining) || remaining <= 0) return false;
  await new Promise((resolve) => setTimeout(
    resolve,
    Math.min(LOCK_RETRY_DELAY_MS, remaining),
  ));
  return true;
}

export async function checkForUpdate({
  releasePath = defaultReleasePath,
  cacheDirectory = defaultCacheDirectory(),
  fetchImpl = globalThis.fetch,
  now = Date.now,
  random = Math.random,
  timeoutMs = DEFAULT_TIMEOUT_MS,
} = {}) {
  if (typeof fetchImpl !== 'function') return silent('runtime-unavailable');
  let localRelease;
  try {
    localRelease = validateLocalRelease(await readJsonFile(releasePath, MAX_LOCAL_RELEASE_BYTES));
  } catch {
    return silent('invalid-local-release');
  }

  const nowMs = Number(now());
  if (!Number.isFinite(nowMs)) return silent('invalid-clock');
  let stateDirectory = versionCacheDirectory(cacheDirectory, localRelease.version);
  let state;
  try {
    stateDirectory = await prepareCacheDirectory(stateDirectory);
    state = await readState(stateDirectory, localRelease.version);
  } catch {
    return silent('cache-unavailable');
  }
  const cachedResult = freshStateResult(localRelease, state, nowMs);
  if (cachedResult) return cachedResult;

  let operation;
  try {
    operation = await acquireOperation(stateDirectory);
  } catch {
    return silent('cache-unavailable');
  }
  if (!operation) {
    try {
      const cached = await readState(stateDirectory, localRelease.version);
      if (cached.candidate
        && cached.notification.offeredDigests.includes(cached.candidate.targetDigest)) {
        return resultForCandidate(localRelease, cached);
      }
    } catch {
      return silent('cache-unavailable');
    }
    return silent('check-in-progress');
  }

  try {
    state = await readState(stateDirectory, localRelease.version);
    const racedResult = freshStateResult(localRelease, state, nowMs);
    if (racedResult) {
      await cancelOperation(stateDirectory, operation);
      return racedResult;
    }

    if (!await operationOwnsActiveClaim(stateDirectory, operation)) {
      return await resultAfterLosingOperation(stateDirectory, operation, localRelease);
    }

    await verifyCacheToken(cacheTokenFor(stateDirectory));
    if (!await operationOwnsActiveClaim(stateDirectory, operation)) {
      return await resultAfterLosingOperation(stateDirectory, operation, localRelease);
    }
    let fetched;
    try {
      fetched = await fetchCandidate({
        fetchImpl,
        manifestUrl: localRelease.updateManifestUrl,
        timeoutMs,
      });
    } catch (error) {
      const reason = error instanceof UpdateContractError ? 'invalid-manifest' : 'check-failed';
      try {
        const committed = await commitOperation(
          stateDirectory,
          operation,
          localRelease.version,
          (current) => ({
            state: stateAfterFailedCheck(current, nowMs),
            result: silent(reason),
          }),
        );
        return committed.result;
      } catch {
        await cancelOperation(stateDirectory, operation).catch(() => {});
        return silent('cache-unavailable');
      }
    }

    try {
      const committed = await commitOperation(
        stateDirectory,
        operation,
        localRelease.version,
        (current) => {
          const next = {
            ...current,
            candidate: fetched.candidate,
            check: {
              nextCheckAt: nextSuccessfulCheck(nowMs, random),
              consecutiveFailures: 0,
            },
            notification: {
              offeredDigests: [...current.notification.offeredDigests],
              acknowledgedDigests: [...current.notification.acknowledgedDigests],
            },
          };
          const result = resultForCandidate(localRelease, next);
          if (result.status === 'update_available') {
            if (!next.notification.offeredDigests.includes(next.candidate.targetDigest)) {
              next.notification.offeredDigests.push(next.candidate.targetDigest);
            }
          }
          return {
            state: next,
            result,
            capacityFallback: {
              state: stateAfterFailedCheck(current, nowMs, true),
              result: silent('cache-unavailable'),
            },
          };
        },
      );
      return committed.result;
    } catch {
      await cancelOperation(stateDirectory, operation).catch(() => {});
      return silent('cache-unavailable');
    }
  } catch {
    await cancelOperation(stateDirectory, operation).catch(() => {});
    return silent('cache-unavailable');
  }
}

export async function acknowledgeUpdate({
  releasePath = defaultReleasePath,
  cacheDirectory = defaultCacheDirectory(),
  eventKey,
  monotonicNow = () => performance.now(),
} = {}) {
  const targetDigest = digestForEventKey(eventKey);
  if (!targetDigest || typeof monotonicNow !== 'function') {
    return silent('invalid-acknowledgement');
  }
  let localRelease;
  try {
    localRelease = validateLocalRelease(await readJsonFile(releasePath, MAX_LOCAL_RELEASE_BYTES));
  } catch {
    return silent('invalid-local-release');
  }
  let stateDirectory = versionCacheDirectory(cacheDirectory, localRelease.version);
  try {
    stateDirectory = await prepareCacheDirectory(stateDirectory);
  } catch {
    return silent('cache-unavailable');
  }
  let deadline;
  try {
    deadline = Number(monotonicNow()) + ACK_LOCK_WAIT_MS;
  } catch {
    return silent('invalid-acknowledgement');
  }
  if (!Number.isFinite(deadline)) return silent('invalid-acknowledgement');
  while (true) {
    let operation;
    try {
      operation = await acquireOperation(stateDirectory);
    } catch {
      return silent('cache-unavailable');
    }
    if (!operation) {
      if (!await waitUntilRetry(deadline, monotonicNow)) return silent('check-in-progress');
      continue;
    }
    try {
      const committed = await commitOperation(
        stateDirectory,
        operation,
        localRelease.version,
        (state) => {
          const wasOffered = state.notification.offeredDigests.includes(targetDigest);
          const wasAcknowledged = state.notification.acknowledgedDigests.includes(targetDigest);
          if (!wasOffered && !wasAcknowledged) {
            return { state: null, result: silent('invalid-acknowledgement') };
          }
          return {
            state: stateAfterAcknowledgement(state, targetDigest),
            result: { status: 'acknowledged', eventKey },
          };
        },
      );
      if (!committed.fenced) return committed.result;
      if (!await waitUntilRetry(deadline, monotonicNow)) return silent('check-in-progress');
    } catch {
      await cancelOperation(stateDirectory, operation).catch(() => {});
      return silent('cache-unavailable');
    }
  }
}

async function runCli() {
  if (process.env.ARCHIFY_UPDATE_CHECK_DISABLED === '1') return silent('disabled');
  const argumentsList = process.argv.slice(2);
  if (argumentsList.length === 0) return checkForUpdate();
  if (argumentsList.length === 2 && argumentsList[0] === '--ack') {
    return acknowledgeUpdate({ eventKey: argumentsList[1] });
  }
  return silent('invalid-arguments');
}

async function isMainModule() {
  if (!process.argv[1]) return false;
  try {
    const [entryPath, modulePath] = await Promise.all([
      fs.realpath(path.resolve(process.argv[1])),
      fs.realpath(fileURLToPath(import.meta.url)),
    ]);
    return entryPath === modulePath;
  } catch {
    return path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
  }
}

if (await isMainModule()) {
  let result;
  try {
    result = await runCli();
  } catch {
    result = silent('check-failed');
  }
  process.stdout.write(`${JSON.stringify(result)}\n`);
}
```

## scripts/generate-brand-marks.mjs

```js
#!/usr/bin/env node

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as simpleIcons from 'simple-icons';

const here = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(here, '..');
const catalogPath = path.join(root, 'brand-marks', 'catalog.json');
const outputPath = path.join(root, 'renderers', 'shared', 'generated-brand-marks.mjs');
const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
const simpleIconsVersion = JSON.parse(fs.readFileSync(
  path.join(root, 'node_modules', 'simple-icons', 'package.json'),
  'utf8',
)).version;
const simpleBySlug = new Map(Object.values(simpleIcons)
  .filter((icon) => icon && typeof icon === 'object' && icon.slug && icon.path)
  .map((icon) => [icon.slug, icon]));

function normalizedList(value) {
  return [...new Set((Array.isArray(value) ? value : [])
    .map((item) => String(item).trim())
    .filter(Boolean))];
}

function lookupForms(value) {
  const raw = String(value ?? '').trim().toLocaleLowerCase('en-US');
  if (!raw) return [];
  return [...new Set([
    raw,
    raw.replace(/[\s_]+/g, '-'),
    raw.replace(/[\s_.-]+/g, ''),
  ])];
}

function fail(message) {
  console.error(`brand catalog: ${message}`);
  process.exit(1);
}

if (catalog.schemaVersion !== 1 || !Array.isArray(catalog.marks) || catalog.marks.length === 0) {
  fail('catalog.json must contain a non-empty schemaVersion 1 marks array');
}

const ids = new Set();
const lookupKeys = new Map();
const domains = new Map();
const generated = catalog.marks.map((entry, index) => {
  if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.id || '')) fail(`marks[${index}] has an invalid id`);
  if (ids.has(entry.id)) fail(`duplicate id ${entry.id}`);
  ids.add(entry.id);

  const aliases = normalizedList(entry.aliases);
  const entryDomains = normalizedList(entry.domains).map((domain) => domain.toLowerCase());
  for (const key of [entry.id, ...aliases]) {
    for (const form of lookupForms(key)) {
      if (lookupKeys.has(form) && lookupKeys.get(form) !== entry.id) {
        fail(`lookup key ${JSON.stringify(key)} is shared by ${lookupKeys.get(form)} and ${entry.id}`);
      }
      lookupKeys.set(form, entry.id);
    }
  }
  for (const domain of entryDomains) {
    if (domains.has(domain) && domains.get(domain) !== entry.id) {
      fail(`domain ${domain} is shared by ${domains.get(domain)} and ${entry.id}`);
    }
    domains.set(domain, entry.id);
  }

  let mark;
  if (entry.simpleIcon) {
    const icon = simpleBySlug.get(entry.simpleIcon);
    if (!icon) fail(`${entry.id} references missing Simple Icons slug ${entry.simpleIcon}`);
    mark = {
      id: entry.id,
      title: entry.title || icon.title,
      category: entry.category,
      aliases,
      domains: entryDomains,
      viewBox: 24,
      hex: icon.hex,
      path: icon.path,
      provenance: {
        provider: 'Simple Icons',
        providerVersion: simpleIconsVersion,
        source: icon.source,
        ...(icon.guidelines ? { guidelines: icon.guidelines } : {}),
        ...(icon.license ? { license: icon.license } : {}),
      },
    };
  } else if (entry.custom) {
    const custom = entry.custom;
    if (!entry.title || !custom.path || !custom.source || !/^[0-9A-F]{6}$/i.test(custom.hex || '')) {
      fail(`${entry.id} custom mark requires title, path, source, and six-digit hex`);
    }
    mark = {
      id: entry.id,
      title: entry.title,
      category: entry.category,
      aliases,
      domains: entryDomains,
      viewBox: custom.viewBox || 24,
      hex: custom.hex.toUpperCase(),
      path: custom.path,
      provenance: {
        provider: 'Official brand asset',
        source: custom.source,
        ...(custom.guidelines ? { guidelines: custom.guidelines } : {}),
      },
    };
  } else {
    fail(`${entry.id} must provide simpleIcon or custom`);
  }
  if (!mark.category || !mark.title) fail(`${entry.id} is missing category or title`);
  for (const form of lookupForms(mark.title)) {
    if (lookupKeys.has(form) && lookupKeys.get(form) !== entry.id) {
      fail(`title ${JSON.stringify(mark.title)} is shared by ${lookupKeys.get(form)} and ${entry.id}`);
    }
    lookupKeys.set(form, entry.id);
  }
  return mark;
}).sort((left, right) => left.id.localeCompare(right.id));

const banner = `// Generated by scripts/generate-brand-marks.mjs from brand-marks/catalog.json.\n// Simple Icons ${simpleIconsVersion}. Do not edit by hand.\n`;
const source = `${banner}export const BRAND_MARKS = Object.freeze(${JSON.stringify(generated, null, 2)});\n`;

if (process.argv.includes('--check')) {
  const current = fs.existsSync(outputPath)
    ? fs.readFileSync(outputPath, 'utf8').replace(/\r\n?/g, '\n')
    : '';
  if (current !== source) {
    console.error('generated brand marks are stale — run npm run generate:brand-marks');
    process.exit(1);
  }
} else {
  const temporary = `${outputPath}.${process.pid}.tmp`;
  fs.writeFileSync(temporary, source);
  fs.renameSync(temporary, outputPath);
  console.log(`generated ${path.relative(root, outputPath)} (${generated.length} marks)`);
}
```

## scripts/generate-validators.mjs

```js
#!/usr/bin/env node

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import Ajv2020 from 'ajv/dist/2020.js';
import standaloneCode from 'ajv/dist/standalone/index.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(__dirname, '..');
const schemasDir = path.join(root, 'schemas');
const output = path.join(root, 'renderers/shared/generated-validators.mjs');
const diagramTypes = ['workflow', 'sequence', 'dataflow', 'lifecycle', 'architecture'];

const ajv = new Ajv2020({
  allErrors: true,
  strict: true,
  code: { source: true, esm: true },
});
ajv.addSchema(JSON.parse(fs.readFileSync(path.join(schemasDir, 'common.schema.json'), 'utf8')));

const schemaIds = {};
for (const type of diagramTypes) {
  const schema = JSON.parse(fs.readFileSync(path.join(schemasDir, `${type}.schema.json`), 'utf8'));
  ajv.addSchema(schema);
  schemaIds[type] = schema.$id;
}

const banner = '// Generated by scripts/generate-validators.mjs. Do not edit by hand.\n';
const ajvUcs2Import = 'require("ajv/dist/runtime/ucs2length").default';
const inlineUcs2Length = `function ucs2length(str) {
  const len = str.length;
  let length = 0;
  let pos = 0;
  while (pos < len) {
    length += 1;
    const value = str.charCodeAt(pos++);
    if (value >= 0xd800 && value <= 0xdbff && pos < len
      && (str.charCodeAt(pos) & 0xfc00) === 0xdc00) pos += 1;
  }
  return length;
}`;
let validatorCode = standaloneCode(ajv, schemaIds);
if (!validatorCode.includes(ajvUcs2Import)) {
  throw new Error('AJV standalone output no longer contains the expected ucs2length helper');
}
validatorCode = validatorCode.replaceAll(ajvUcs2Import, inlineUcs2Length);
if (validatorCode.includes('require(')) {
  throw new Error('AJV standalone output contains an unexpected runtime dependency');
}
const generated = `${banner}${validatorCode}\n`;

if (process.argv.includes('--check')) {
  const current = fs.existsSync(output)
    ? fs.readFileSync(output, 'utf8').replace(/\r\n?/g, '\n')
    : '';
  if (current !== generated) {
    console.error('generated validators are stale — run npm run generate:validators');
    process.exit(1);
  }
} else {
  const temporary = `${output}.${process.pid}.tmp`;
  fs.writeFileSync(temporary, generated);
  fs.renameSync(temporary, output);
  console.log(`generated ${path.relative(root, output)}`);
}
```

## scripts/render-examples.mjs

```js
// Re-render every bundled example from its JSON IR. Installed skills keep HTML
// beside the JSON examples; the development script passes the golden directory.

import { execFileSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const outputRoot = path.resolve(process.argv[2] || path.join(skillRoot, 'examples'));

const TARGETS = [
  ['workflow', 'agent-tool-call.workflow.json', 'workflow-agent-tool-call-rendered.html'],
  ['sequence', 'cache-miss-request.sequence.json', 'sequence-cache-miss-request.html'],
  ['dataflow', 'product-analytics.dataflow.json', 'dataflow-product-analytics.html'],
  ['lifecycle', 'agent-run.lifecycle.json', 'lifecycle-agent-run.html'],
  ['architecture', 'web-app.architecture.json', 'web-app-rendered.html'],
];

for (const [mode, input, output] of TARGETS) {
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', input),
    path.join(outputRoot, output),
  ], { stdio: 'inherit' });
}
```

## scripts/update-contract.mjs

```js
export const SKILL_ID = 'archify';
export const EXPECTED_REPOSITORY = 'https://github.com/tt-a1i/archify';
export const DEFAULT_MANIFEST_URL = 'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json';

const CONTROL_OR_BIDI = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u;
const HEX_40 = /^[a-f0-9]{40}$/;
const HEX_64 = /^[a-f0-9]{64}$/;
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
const UTC_SECONDS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;

export class UpdateContractError extends Error {
  constructor(message) {
    super(message);
    this.name = 'UpdateContractError';
  }
}

function isPlainObject(value) {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}

function hasExactKeys(value, expected) {
  return isPlainObject(value)
    && Object.keys(value).sort().join('\0') === [...expected].sort().join('\0');
}

export function parseSemver(value) {
  if (typeof value !== 'string' || value.length > 128) {
    throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`);
  }
  const match = SEMVER.exec(value);
  if (!match) throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`);
  const prerelease = match[4]?.split('.') ?? null;
  if (prerelease?.some((identifier) => /^\d+$/.test(identifier)
    && identifier.length > 1 && identifier.startsWith('0'))) {
    throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`);
  }
  return {
    core: match.slice(1, 4),
    prerelease,
    build: match[5]?.split('.') ?? null,
  };
}

function compareNumericIdentifiers(left, right) {
  if (left.length !== right.length) return left.length < right.length ? -1 : 1;
  if (left === right) return 0;
  return left < right ? -1 : 1;
}

function comparePrerelease(left, right) {
  if (left === null && right === null) return 0;
  if (left === null) return 1;
  if (right === null) return -1;
  const length = Math.max(left.length, right.length);
  for (let index = 0; index < length; index += 1) {
    if (left[index] === undefined) return -1;
    if (right[index] === undefined) return 1;
    if (left[index] === right[index]) continue;
    const leftNumeric = /^\d+$/.test(left[index]);
    const rightNumeric = /^\d+$/.test(right[index]);
    if (leftNumeric && rightNumeric) return compareNumericIdentifiers(left[index], right[index]);
    if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
    return left[index] < right[index] ? -1 : 1;
  }
  return 0;
}

export function compareSemver(leftValue, rightValue) {
  const left = parseSemver(leftValue);
  const right = parseSemver(rightValue);
  for (let index = 0; index < left.core.length; index += 1) {
    const comparison = compareNumericIdentifiers(left.core[index], right.core[index]);
    if (comparison !== 0) return comparison;
  }
  return comparePrerelease(left.prerelease, right.prerelease);
}

export function releaseChannelForVersion(value) {
  return parseSemver(value).prerelease ? 'development' : 'stable';
}

export function isStableCoreVersion(value) {
  try {
    const parsed = parseSemver(value);
    return parsed.prerelease === null && parsed.build === null;
  } catch {
    return false;
  }
}

export function validateCanonicalUtcTimestamp(value) {
  if (typeof value !== 'string' || !UTC_SECONDS.test(value)) {
    throw new UpdateContractError('publication time must use YYYY-MM-DDTHH:mm:ssZ');
  }
  const timestamp = Date.parse(value);
  if (!Number.isFinite(timestamp)
    || new Date(timestamp).toISOString().replace('.000Z', 'Z') !== value) {
    throw new UpdateContractError('publication time is not a real UTC calendar instant');
  }
  return value;
}

export function validateLocalRelease(value) {
  if (!hasExactKeys(value, [
    'schemaVersion', 'skillId', 'channel', 'version', 'source', 'updateManifestUrl',
  ])
    || value.schemaVersion !== 1
    || value.skillId !== SKILL_ID
    || !hasExactKeys(value.source, ['repository'])
    || value.source.repository !== EXPECTED_REPOSITORY
    || value.updateManifestUrl !== DEFAULT_MANIFEST_URL) {
    throw new UpdateContractError('invalid local release identity');
  }
  const expectedChannel = releaseChannelForVersion(value.version);
  if (value.channel !== expectedChannel) {
    throw new UpdateContractError('local release channel does not match its version');
  }
  return {
    schemaVersion: value.schemaVersion,
    skillId: value.skillId,
    channel: value.channel,
    version: value.version,
    source: { repository: value.source.repository },
    updateManifestUrl: value.updateManifestUrl,
  };
}

export function validateReleaseNotesUrl(value, version) {
  if (!isStableCoreVersion(version)) {
    throw new UpdateContractError('release notes require a stable core version');
  }
  const expected = `https://github.com/tt-a1i/archify/releases/tag/v${version}`;
  if (value !== expected) {
    throw new UpdateContractError('release notes URL is outside the exact trusted release path');
  }
  return value;
}

export function validateStableUpdateManifest(value) {
  if (!hasExactKeys(value, [
    'schemaVersion', 'skillId', 'channel', 'version', 'publishedAt', 'source',
    'artifact', 'summary', 'releaseNotes', 'severity',
  ])
    || value.schemaVersion !== 1
    || value.skillId !== SKILL_ID
    || value.channel !== 'stable'
    || !isStableCoreVersion(value.version)
    || !hasExactKeys(value.source, ['repository', 'ref', 'treeSha'])
    || value.source.repository !== EXPECTED_REPOSITORY
    || value.source.ref !== `v${value.version}`
    || !HEX_40.test(value.source.treeSha)
    || !hasExactKeys(value.artifact, ['sha256'])
    || !HEX_64.test(value.artifact.sha256)) {
    throw new UpdateContractError('invalid immutable stable release identity');
  }
  validateCanonicalUtcTimestamp(value.publishedAt);
  if (typeof value.summary !== 'string' || value.summary.length < 1 || value.summary.length > 160
    || CONTROL_OR_BIDI.test(value.summary)) {
    throw new UpdateContractError('invalid release summary');
  }
  validateReleaseNotesUrl(value.releaseNotes, value.version);
  if (!['normal', 'security'].includes(value.severity)) {
    throw new UpdateContractError('invalid update severity');
  }
  return {
    schemaVersion: value.schemaVersion,
    skillId: value.skillId,
    channel: value.channel,
    version: value.version,
    publishedAt: value.publishedAt,
    source: {
      repository: value.source.repository,
      ref: value.source.ref,
      treeSha: value.source.treeSha,
    },
    artifact: { sha256: value.artifact.sha256 },
    summary: value.summary,
    releaseNotes: value.releaseNotes,
    severity: value.severity,
  };
}
```

## skill-release.json

```json
{
  "schemaVersion": 1,
  "skillId": "archify",
  "channel": "development",
  "version": "2.17.0-dev.1",
  "source": {
    "repository": "https://github.com/tt-a1i/archify"
  },
  "updateManifestUrl": "https://tt-a1i.github.io/archify/skill-updates/archify/stable.json"
}
```

## test

```

```

## test/adaptive-reader-layout.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
  DESKTOP_READABILITY_VIEWPORT,
  DESKTOP_READER_DIAGRAM_WIDTH,
  DESKTOP_READER_HORIZONTAL_CHROME,
  DESKTOP_READER_MIN_WIDTH,
  MIN_PROJECTED_NODE_TEXT_PX,
  minimumReadableSourceTextPx,
  projectedNodeTextPx,
} from '../renderers/shared/desktop-readability.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const skill = fs.readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8');
const reader = template.slice(
  template.indexOf('Adaptive Reader Shell'),
  template.indexOf('Archify.view = (function ()'),
);

test('wide desktop diagrams use one height-budgeted reader shell instead of breakpoint jumps', () => {
  assert.match(template, /max-width: var\(--archify-reader-width, 1440px\)/);
  assert.doesNotMatch(template, /@media \(min-width: 1680px\)[\s\S]{0,180}\.container/);
  assert.doesNotMatch(template, /@media \(min-width: 1920px\)[\s\S]{0,180}\.container/);
  assert.match(reader, /var WIDE_RATIO = 1\.55/);
  assert.match(reader, /var MAX_READER_WIDTH = 1920/);
  assert.match(reader, /var availableSvgHeight = Math\.max\(1, window\.innerHeight - fixedHeight\)/);
  assert.match(reader, /var desiredWidth = availableSvgHeight \* ratio \+ chrome\.diagramX/);
  assert.match(reader, /html\.style\.setProperty\('--archify-reader-width', rounded \+ 'px'\)/);
});

test('desktop readability budget matches the minimum adaptive reader at 1440 by 900', () => {
  assert.deepEqual(DESKTOP_READABILITY_VIEWPORT, { width: 1440, height: 900 });
  assert.equal(DESKTOP_READER_MIN_WIDTH, 960);
  assert.equal(DESKTOP_READER_HORIZONTAL_CHROME, 30);
  assert.equal(DESKTOP_READER_DIAGRAM_WIDTH, 930);
  assert.match(reader, new RegExp(`var MIN_READER_WIDTH = ${DESKTOP_READER_MIN_WIDTH}`));
  assert.match(template, /html\[data-nav-stage-rail="true"\] body \{ padding-block: 0\.375rem; \}/);
  assert.match(template, /@media \(min-width: 768px\) and \(max-height: 1100px\)[\s\S]*?\.diagram-container \{[\s\S]*?padding: 0\.875rem;[\s\S]*?padding-bottom: calc\(0\.875rem \+ var\(--archify-nav-reserve\)\);/);
  assert.match(template, /@media \(min-width: 768px\) and \(max-height: 920px\)[\s\S]*?body \{ padding-block: 1\.25rem; \}/);
  assert.match(template, /\.diagram-container \{[\s\S]*?border: 1px solid var\(--panel-border\)/);
});

test('desktop readability source floor is the inverse of the projected-size gate', () => {
  const sourceFloor = minimumReadableSourceTextPx(1376);
  assert.ok(Math.abs(sourceFloor - 8.87741935483871) < 1e-12);
  assert.ok(Math.abs(projectedNodeTextPx(sourceFloor, 1376) - MIN_PROJECTED_NODE_TEXT_PX) < 1e-12);
  assert.equal(minimumReadableSourceTextPx(DESKTOP_READER_DIAGRAM_WIDTH), MIN_PROJECTED_NODE_TEXT_PX);
  assert.equal(minimumReadableSourceTextPx(700), MIN_PROJECTED_NODE_TEXT_PX);
  assert.ok(Number.isNaN(minimumReadableSourceTextPx(0)));
});

test('adaptive width preserves canonical SVG geometry and yields to specialized viewer modes', () => {
  assert.match(reader, /window\.innerWidth >= MIN_DESKTOP_WIDTH/);
  assert.match(reader, /html\.getAttribute\('data-embed'\) !== 'true'/);
  assert.match(reader, /html\.getAttribute\('data-present'\) !== 'true'/);
  assert.match(reader, /window\.matchMedia\('print'\)\.matches/);
  assert.doesNotMatch(reader, /svg\.setAttribute\(['"](?:viewBox|width|height)/);
  assert.doesNotMatch(reader, /svg\.style\.(?:width|height)/);
  assert.doesNotMatch(reader, /overflow\s*=\s*['"]hidden/);
});

test('reader remeasures real content and reduces width before allowing desktop page overflow', () => {
  assert.match(reader, /document\.fonts\.ready\.then\(schedule\)/);
  assert.match(reader, /new ResizeObserver\(schedule\)/);
  assert.match(reader, /new MutationObserver\(schedule\)/);
  assert.match(reader, /document\.documentElement\.scrollHeight/);
  assert.match(reader, /lastWidth - overflow \* ratio - 4/);
  assert.match(skill, /1440×900, 1600×1000, and 1920×1080/);
  assert.match(skill, /2048×1320/);
  assert.match(skill, /Generate one responsive artifact for laptops and external displays/);
  assert.match(skill, /preserve the authored SVG\/viewBox, proportions, semantic geometry/);
});

test('reader exposes an explicit stable-dimensions contract for browser evidence', () => {
  assert.match(reader, /function stableSnapshot\(\)/);
  assert.match(reader, /function whenStable\(\)/);
  assert.match(reader, /document\.fonts && document\.fonts\.ready/);
  assert.match(reader, /Math\.ceil\(document\.body\.scrollHeight\)/);
  assert.match(reader, /stableFrames >= 3/);
  assert.match(reader, /whenStable: whenStable/);
});
```

## test/animation.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-animation-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

const NODE_COLLECTION = {
  architecture: 'components',
  workflow: 'nodes',
  sequence: 'participants',
  dataflow: 'nodes',
  lifecycle: 'states',
};

function render(mode, example, animation = 'trace', visualPreset) {
  const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8'));
  if (animation) doc.meta = { ...doc.meta, animation };
  else delete doc.meta.animation;
  if (visualPreset) doc.meta.visual_preset = visualPreset;
  else if (visualPreset === null) delete doc.meta.visual_preset;
  const suffix = `${animation || 'static'}-${visualPreset || 'default'}`;
  const input = path.join(tmp, `${mode}-${suffix}.json`);
  const output = path.join(tmp, `${mode}-${suffix}.html`);
  fs.writeFileSync(input, JSON.stringify(doc));
  execFileSync('node', [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, output], {
    stdio: ['ignore', 'ignore', 'pipe'],
  });
  return fs.readFileSync(output, 'utf8');
}

function svgBlock(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('static output omits animation attributes', () => {
  const svg = svgBlock(render('workflow', CASES.workflow, null, null));
  assert.doesNotMatch(svg, /data-animation=/);
  assert.doesNotMatch(svg, /data-animate=/);
});

test('classic preset remains the default for existing diagrams', () => {
  const html = render('architecture', CASES.architecture, null, null);
  assert.match(html, /<html lang="en" data-theme="dark" data-preset="classic">/);
  assert.match(svgBlock(html), /data-preset="classic"/);
});

test('signal-flow preset reaches the page, SVG, and motion export surface', () => {
  const html = render('workflow', CASES.workflow, 'trace', 'signal-flow');
  assert.match(html, /<html lang="en" data-theme="dark" data-preset="signal-flow">/);
  assert.match(svgBlock(html), /data-preset="signal-flow"/);
  assert.match(html, /content: attr\(data-preset-badge-signal-flow\)/);
  assert.match(html, /data-preset-badge-signal-flow="SIGNAL FLOW"/);
  assert.match(html, /data-format="webm"/);
  assert.match(html, /data-last-motion-bytes/);
  assert.match(html, /Archify\.motion = \{ canRecord: canRecordMotion, recordWebm: recordWebm \}/);
  assert.match(html, /recorder\.requestData\(\)/);
  assert.match(html, /aria-label="Diagram view controls"/);
  assert.match(html, /Archify\.focus = \(function \(\)/);
  assert.match(html, /Archify\.view = \(function \(\)/);
  assert.match(html, /clone\.style\.removeProperty\('transform'\)/);
  assert.match(html, /clone\.removeAttribute\('data-view-scale'\)/);
  assert.match(html, /data-last-export-canonical/);
  assert.match(html, /data-last-export-error-format/);
  assert.match(html, /data-last-export-error/);
  assert.match(html, /WebM unavailable in this browser/);
  assert.match(html, /Motion capture unavailable in this browser/);
  assert.match(html, /canonicalStateClean: canonicalStateClean/);
  assert.match(html, /recordExportReceipt\('svg', blob, d\.canonicalStateClean\)/);
});

test('webm renders an explicit time-varying canvas scene instead of replaying one cached SVG bitmap', () => {
  const html = render('architecture', CASES.architecture, 'trace', 'signal-flow');
  const recordBlock = html.match(/function recordWebm\(options\) \{[\s\S]*?\n      var menu =/)?.[0] || '';

  assert.match(recordBlock, /var motionScene = createMotionScene\(svg\)/);
  assert.match(recordBlock, /drawMotionFrame\(ctx, backgroundImage, motionScene, elapsed\)/);
  assert.match(recordBlock, /getPointAtLength/);
  assert.match(recordBlock, /performance\.now\(\)/);
  assert.doesNotMatch(
    recordBlock,
    /function draw\(\) \{[\s\S]*?ctx\.drawImage\(img, 0, 0, canvas\.width, canvas\.height\);[\s\S]*?requestAnimationFrame\(draw\)/,
  );
});

test('blueprint preset reaches every visual surface without changing the default', () => {
  const html = render('architecture', CASES.architecture, null, 'blueprint');
  assert.match(html, /<html lang="en" data-theme="dark" data-preset="blueprint">/);
  assert.match(svgBlock(html), /data-preset="blueprint"/);
  assert.match(html, /content: attr\(data-preset-badge-blueprint\)/);
  assert.match(html, /data-preset-badge-blueprint="BLUEPRINT \/ REV 01"/);
  assert.match(html, /\[data-preset="blueprint"\]\[data-theme="dark"\]/);
  assert.match(html, /svg\[data-preset="blueprint"\] \.c-grid/);
  assert.match(html, /html\[data-preset="blueprint"\] \.guided-views/);
  assert.match(html, /html\[data-preset="blueprint"\] \.card/);
});

test('blueprint preset is accepted by all five typed renderers', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example, null, 'blueprint');
    assert.match(html, /data-preset="blueprint"/, mode);
    assert.match(svgBlock(html), /data-preset="blueprint"/, mode);
  }
});

test('editorial preset reaches every visual surface and all five typed renderers', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example, null, 'editorial');
    assert.match(html, /<html lang="en" data-theme="dark" data-preset="editorial">/, mode);
    assert.match(svgBlock(html), /data-preset="editorial"/, mode);
    assert.match(html, /content: attr\(data-preset-badge-editorial\)/, mode);
    assert.match(html, /data-preset-badge-editorial="EDITORIAL \/ FIELD NOTE"/, mode);
    assert.match(html, /content: attr\(data-preset-badge-editorial-plate\)/, mode);
    assert.match(html, /data-preset-badge-editorial-plate="ARCHIFY \/ PLATE 04"/, mode);
    assert.match(html, /\[data-preset="editorial"\]\[data-theme="dark"\]/, mode);
    assert.match(html, /html\[data-preset="editorial"\] \.diagram-container/, mode);
    assert.match(html, /svg\[data-preset="editorial"\] \.story-trail-flow/, mode);
  }
});

test('all five renderers add one geometry-neutral semantic sigil per primary node', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8'));
    const expected = source[NODE_COLLECTION[mode]].length;
    const staticHtml = render(mode, example, null, 'classic');
    const traceHtml = render(mode, example, 'trace', 'classic');
    const staticSvg = svgBlock(staticHtml);
    const traceSvg = svgBlock(traceHtml);
    const sigils = (svg) => [...svg.matchAll(/<g aria-hidden="true" data-semantic-sigil="[^"]+"[\s\S]*?<\/g>/g)].map((match) => match[0]);

    assert.equal(sigils(staticSvg).length, expected, mode);
    assert.deepEqual(sigils(traceSvg), sigils(staticSvg), `${mode} trace must not change sigil geometry`);
    assert.match(staticHtml, /svg \.semantic-sigil \{/i, mode);
    assert.match(staticHtml, /svg \.s-database\s+\{ color: var\(--database-stroke\); \}/, mode);
  }
});

test('unknown visual presets are rejected by schema validation', () => {
  assert.throws(
    () => render('architecture', CASES.architecture, null, 'hologram'),
    /visual_preset/,
  );
});

for (const [mode, example] of Object.entries(CASES)) {
  test(`${mode}: trace animation annotates svg, edges, and nodes`, () => {
    const svg = svgBlock(render(mode, example));
    assert.match(svg, /<svg[^>]+data-animation="trace"/);
    assert.match(svg, /data-animate="edge" style="--step:0"/);
    assert.match(svg, /data-animate="node" style="--step:0"/);
    assert.match(svg, /aria-labelledby="archify-diagram-title archify-diagram-description"/);
    assert.match(svg, /<title id="archify-diagram-title">[^<]+<\/title>/);
    assert.match(svg, /<desc id="archify-diagram-description">[^<]+<\/desc>/);
    assert.match(svg, /id="node-[^"]+" data-node-id="[^"]+"[^>]+role="button"[^>]+aria-pressed="false"/);
    assert.match(svg, /data-edge-from="[^"]+" data-edge-to="[^"]+"/);
  });
}

test('semantic SVG identity is deterministic for unchanged input', () => {
  const first = svgBlock(render('workflow', CASES.workflow));
  const second = svgBlock(render('workflow', CASES.workflow));
  const hooks = (svg) => [...svg.matchAll(/(?:id="node-|data-edge-from=")[^>]+/g)].map((match) => match[0]);
  assert.deepEqual(hooks(first), hooks(second));
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/architecture-delta.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
  ArchitectureDeltaError,
  architectureDeltaChangeRows,
  canonicalArchitectureJson,
  compareArchitecture,
  validateArchitectureDeltaHtml,
} from '../delta/architecture-delta.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const cli = path.join(skillRoot, 'bin/archify.mjs');
const baseFixture = path.join(skillRoot, 'examples/checkout-platform.base.architecture.json');
const headFixture = path.join(skillRoot, 'examples/checkout-platform.head.architecture.json');
const checkedArtifact = path.resolve(skillRoot, '../examples/checkout-platform-delta.html');
const checkedReceipt = path.resolve(skillRoot, '../examples/checkout-platform-delta.receipt.json');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-delta-'));

const read = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
const run = (args) => spawnSync(process.execPath, [cli, ...args], { cwd: skillRoot, encoding: 'utf8' });

test('architecture compare classifies authored facts separately from geometry and presentation', () => {
  const receipt = compareArchitecture(read(baseFixture), read(headFixture));
  assert.equal(receipt.command, 'compare');
  assert.equal(receipt.completeness, 'complete');
  assert.equal(receipt.proofLevel, 'authored');
  assert.deepEqual(receipt.summary.components, {
    added: 1,
    changed: 1,
    evidenceChanged: 0,
    removed: 1,
    moved: 1,
  });
  assert.deepEqual(receipt.summary.connections, {
    added: 1,
    changed: 2,
    removed: 1,
    rerouted: 1,
  });
  assert.equal(receipt.summary.presentationChanged, true);

  const checkout = receipt.changes.components.find((change) => change.id === 'checkout');
  assert.equal(checkout.status, 'changed');
  assert.deepEqual(checkout.classifications, ['semantic']);
  assert.deepEqual(checkout.changedFields, ['/sublabel']);

  const queue = receipt.changes.components.find((change) => change.id === 'queue');
  assert.equal(queue.status, 'moved');
  assert.deepEqual(queue.classifications, ['geometry']);
  assert.deepEqual(queue.changedFields, ['/pos']);

  const authorization = receipt.changes.connections.find((change) => change.id === 'authorize-payment');
  assert.equal(authorization.status, 'changed');
  assert.deepEqual(authorization.classifications, ['geometry', 'topology']);
  assert.deepEqual(authorization.changedFields, ['/from', '/fromSide', '/toSide', '/via']);

  assert.deepEqual(receipt.changes.connections.find((change) => change.status === 'added').classifications, ['topology']);

  const headWithBoundary = read(headFixture);
  headWithBoundary.boundaries.push({ kind: 'region', label: 'Fraud edge', wraps: ['fraud'] });
  const boundaryReceipt = compareArchitecture(read(baseFixture), headWithBoundary);
  assert.deepEqual(boundaryReceipt.changes.boundaries.find((change) => change.label === 'Fraud edge').classifications, ['scope']);
});

test('legend-only changes are presentation changes and never topology changes', () => {
  const base = read(baseFixture);
  const head = read(baseFixture);
  head.meta.legend = {
    entries: {
      security: { label: 'Trust boundary', visible: true },
      database: { visible: false },
    },
  };

  const receipt = compareArchitecture(base, head);
  assert.equal(receipt.summary.presentationChanged, true);
  assert.deepEqual(receipt.summary.components, {
    added: 0,
    changed: 0,
    evidenceChanged: 0,
    removed: 0,
    moved: 0,
  });
  assert.deepEqual(receipt.summary.connections, {
    added: 0,
    changed: 0,
    removed: 0,
    rerouted: 0,
  });
  assert.deepEqual(receipt.changes, { components: [], connections: [], boundaries: [] });
});

test('canonical architecture ignores formatting, entity order, and set-like order', () => {
  const original = read(baseFixture);
  const reordered = JSON.parse(JSON.stringify(original));
  reordered.components.reverse();
  reordered.connections.reverse();
  reordered.boundaries.reverse();
  reordered.boundaries.forEach((boundary) => boundary.wraps.reverse());
  assert.equal(canonicalArchitectureJson(reordered), canonicalArchitectureJson(original));
});

test('change navigator order is exact-ID based, complete, unique, and stable', () => {
  const receipt = compareArchitecture(read(baseFixture), read(headFixture));
  const rows = architectureDeltaChangeRows(receipt);
  assert.deepEqual(rows.map((row) => row.key), [
    'component:fraud',
    'relationship:fraud-check',
    'boundary:region:Production region',
    'boundary:security-group:Checkout trust zone',
    'component:checkout',
    'relationship:authorize-payment',
    'relationship:persist-order',
    'component:queue',
    'component:cache',
    'relationship:session-read',
    'relationship:publish-order',
  ]);
  assert.equal(new Set(rows.map((row) => row.key)).size, rows.length);
  assert.equal(rows.length, receipt.changes.components.length + receipt.changes.connections.length + receipt.changes.boundaries.length);
});

test('exact identity fails closed instead of guessing relationships or unrelated systems', () => {
  const base = read(baseFixture);
  const missingRelationship = read(headFixture);
  delete missingRelationship.connections[0].id;
  assert.throws(
    () => compareArchitecture(base, missingRelationship),
    (error) => error instanceof ArchitectureDeltaError
      && error.code === 'delta/relationship-id-required'
      && error.details.paths.includes('/connections/0/id'),
  );

  const unrelated = read(headFixture);
  unrelated.components = unrelated.components.map((component, index) => ({ ...component, id: `other${index}` }));
  unrelated.connections = [];
  unrelated.boundaries = [];
  assert.throws(
    () => compareArchitecture(base, unrelated),
    (error) => error instanceof ArchitectureDeltaError && error.code === 'delta/no-shared-component-id',
  );
});

test('evidence-only component changes keep an enabled exact review contract', () => {
  const base = read(baseFixture);
  const head = read(baseFixture);
  base.components[0].sources = [{ path: 'src/entry.js', line: 1, label: 'baseline' }];
  head.components[0].sources = [{ path: 'src/entry.js', line: 2, label: 'head' }];
  const receipt = compareArchitecture(base, head);
  assert.equal(receipt.changes.components.length, 1);
  assert.equal(receipt.changes.components[0].status, 'evidence-changed');
  assert.deepEqual(receipt.changes.components[0].classifications, ['evidence']);
  const runtime = fs.readFileSync(path.join(skillRoot, 'delta/architecture-delta.mjs'), 'utf8');
  assert.match(runtime, /statuses: \['added', 'changed', 'evidence-changed', 'removed', 'moved'\]/);
});

test('mixed semantic and geometry component changes retain both exact forms', () => {
  const head = read(headFixture);
  head.components.find((component) => component.id === 'queue').sublabel = 'durable queue v2';
  const headPath = path.join(tmp, 'mixed-component-head.json');
  const output = path.join(tmp, 'mixed-component-delta.html');
  fs.writeFileSync(headPath, JSON.stringify(head));

  const result = run(['compare', 'architecture', baseFixture, headPath, output, '--json']);
  assert.equal(result.status, 0, result.stderr);
  const receipt = JSON.parse(result.stdout);
  const queue = receipt.changes.components.find((change) => change.id === 'queue');
  assert.equal(queue.status, 'changed');
  assert.deepEqual(queue.classifications, ['geometry', 'semantic']);
  const html = fs.readFileSync(output, 'utf8');
  assert.match(html, /data-change-key="component:queue"[^>]+data-change-target-signature="g:changed:geometry,semantic\|g:moved-from:geometry,semantic"/);
  assert.deepEqual(validateArchitectureDeltaHtml(html, receipt), { ok: true, checksPassed: 10, checkCount: 10 });
});

test('mixed semantic and geometry relationship changes retain both exact routes', () => {
  const head = read(headFixture);
  head.connections.find((connection) => connection.id === 'publish-order').label = 'accepted event';
  const headPath = path.join(tmp, 'mixed-relationship-head.json');
  const output = path.join(tmp, 'mixed-relationship-delta.html');
  fs.writeFileSync(headPath, JSON.stringify(head));

  const result = run(['compare', 'architecture', baseFixture, headPath, output, '--json']);
  assert.equal(result.status, 0, result.stderr);
  const receipt = JSON.parse(result.stdout);
  const publishOrder = receipt.changes.connections.find((change) => change.id === 'publish-order');
  assert.equal(publishOrder.status, 'changed');
  assert.deepEqual(publishOrder.classifications, ['geometry', 'semantic']);
  const html = fs.readFileSync(output, 'utf8');
  assert.match(html, /data-change-key="relationship:publish-order"[^>]+data-change-target-signature="g:changed:geometry,semantic\|g:moved-from:geometry,semantic\|path:changed:geometry,semantic\|path:moved-from:geometry,semantic\|text:changed:\|text:moved-from:"/);
  assert.deepEqual(validateArchitectureDeltaHtml(html, receipt), { ok: true, checksPassed: 10, checkCount: 10 });
});

test('baseline boundary title masks stay below current components and carry delta identity', () => {
  const documentAt = (pos, pad) => ({
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Boundary mask z-order', quality_profile: 'standard', viewBox: [600, 400] },
    components: [{ id: 'node', type: 'backend', label: 'Current node', pos, size: [120, 60] }],
    connections: [],
    boundaries: [{ kind: 'region', label: 'Boundary label', wraps: ['node'], pad }],
  });
  const basePath = path.join(tmp, 'boundary-mask.base.json');
  const headPath = path.join(tmp, 'boundary-mask.head.json');
  const output = path.join(tmp, 'boundary-mask.delta.html');
  fs.writeFileSync(basePath, JSON.stringify(documentAt([250, 200], 30)));
  fs.writeFileSync(headPath, JSON.stringify(documentAt([224, 180], 40)));

  const result = run(['compare', 'architecture', basePath, headPath, output, '--json']);
  assert.equal(result.status, 0, result.stderr);
  const html = fs.readFileSync(output, 'utf8');
  const delta = html.match(/<section class="canvas" data-view="delta">([\s\S]*?)<\/section>/)?.[1] || '';
  const currentComponents = delta.indexOf('<!-- Components -->');
  const currentNode = delta.indexOf('data-node-id="node"', currentComponents);
  const phantomMask = delta.match(
    /<rect data-graph-role="structural-frame-label-mask"[^>]*data-delta-state="moved-from"[^>]*data-delta-boundary-state="moved-from"[^>]*data-delta-boundary-mask-key="region:Boundary label"[^>]*\/>/,
  )?.[0];
  const currentNodeRect = delta.slice(currentNode).match(/<rect\b[^>]*\/>/)?.[0];
  assert.ok(phantomMask && currentNodeRect, 'expected the phantom mask and current component rect');
  const rect = (tag) => Object.fromEntries(
    [...tag.matchAll(/\b(x|y|width|height)="([^"]+)"/g)].map((match) => [match[1], Number(match[2])]),
  );
  const maskBox = rect(phantomMask);
  const nodeBox = rect(currentNodeRect);
  const overlaps = maskBox.x < nodeBox.x + nodeBox.width
    && maskBox.x + maskBox.width > nodeBox.x
    && maskBox.y < nodeBox.y + nodeBox.height
    && maskBox.y + maskBox.height > nodeBox.y;
  assert.equal(overlaps, true, `expected overlap: ${JSON.stringify({ maskBox, nodeBox })}`);
  assert.ok(delta.indexOf(phantomMask) < currentNode, 'phantom mask must paint below the current component');
});

test('same-label node id changes remain one removal plus one addition', () => {
  const base = read(baseFixture);
  const head = read(baseFixture);
  const cache = head.components.find((component) => component.id === 'cache');
  cache.id = 'session-store';
  head.boundaries.forEach((boundary) => {
    boundary.wraps = boundary.wraps.map((id) => (id === 'cache' ? 'session-store' : id));
  });
  head.connections.find((connection) => connection.id === 'session-read').to = 'session-store';

  const receipt = compareArchitecture(base, head);
  assert.equal(receipt.changes.components.find((change) => change.id === 'cache').status, 'removed');
  assert.equal(receipt.changes.components.find((change) => change.id === 'session-store').status, 'added');
  assert.equal(receipt.changes.components.filter((change) => change.headLabel === 'Session Cache' || change.baseLabel === 'Session Cache').length, 2);
});

test('repository mismatch fails and verified matching revisions remain evidence-bounded', () => {
  const base = read(baseFixture);
  const head = read(headFixture);
  base.meta.repository = { url: 'https://github.com/example/one', revision: 'a'.repeat(40) };
  head.meta.repository = { url: 'https://github.com/example/two', revision: 'b'.repeat(40) };
  assert.throws(
    () => compareArchitecture(base, head),
    (error) => error instanceof ArchitectureDeltaError && error.code === 'delta/repository-mismatch',
  );

  head.meta.repository.url = 'https://github.com/EXAMPLE/ONE.git/';
  const receipt = compareArchitecture(base, head, { baseVerified: true, headVerified: true });
  assert.equal(receipt.proofLevel, 'revision-pinned');
  assert.equal(receipt.summary.provenanceChanged, true);
});

test('portable compare retains link settings and uses the same repository identity rules', () => {
  const base = read(baseFixture);
  const head = read(headFixture);
  base.meta.repository = { url: 'https://git.internal/Team/Services/repo.git', revision: 'a'.repeat(40), link_mode: 'local-only' };
  head.meta.repository = { url: 'https://git.internal:443/Team/Services/repo.git', revision: 'b'.repeat(40), link_mode: 'local-only' };
  const canonical = JSON.parse(canonicalArchitectureJson(base));
  assert.equal(canonical.meta.repository.link_mode, 'local-only');
  assert.equal(canonical.meta.repository.url, 'https://git.internal/Team/Services/repo.git');
  assert.equal(compareArchitecture(base, head, { baseVerified: true, headVerified: true }).proofLevel, 'revision-pinned');
  head.meta.repository.url = 'https://git.internal/team/Services/repo.git';
  assert.throws(() => compareArchitecture(base, head), (error) => error.code === 'delta/repository-mismatch');
  base.meta.repository = { url: 'https://gitee.com/Team/repo', revision: 'a'.repeat(40), provider: 'gitee' };
  assert.equal(JSON.parse(canonicalArchitectureJson(base)).meta.repository.provider, 'gitee');
});

test('portable compare preserves literal SCP paths and rejects different Git locations', () => {
  const base = read(baseFixture);
  const head = read(headFixture);
  for (const [url, other] of [
    ['git@git.internal:Team/repo', 'ssh://git@git.internal/Team/repo'],
    ['git@git.internal:Team/repo%41', 'git@git.internal:Team/repoA'],
    ['git@git.internal:Team/repo.git.git', 'git@git.internal:Team/repo.git'],
  ]) {
    base.meta.repository = { url, revision: 'a'.repeat(40), link_mode: 'local-only' };
    head.meta.repository = { url: other, revision: 'b'.repeat(40), link_mode: 'local-only' };
    const canonical = canonicalArchitectureJson(base);
    assert.equal(JSON.parse(canonical).meta.repository.url, url);
    assert.equal(canonicalArchitectureJson(JSON.parse(canonical)), canonical);
    assert.throws(() => compareArchitecture(base, head), (error) => error.code === 'delta/repository-mismatch');
    head.meta.repository.url = url;
    assert.equal(compareArchitecture(base, head, { baseVerified: true, headVerified: true }).proofLevel, 'revision-pinned');
  }
});

test('compare CLI writes a deterministic three-state artifact and complete sidecar receipt', () => {
  const first = path.join(tmp, 'first.html');
  const second = path.join(tmp, 'second.html');
  const result = run(['compare', 'architecture', baseFixture, headFixture, first, '--json']);
  assert.equal(result.status, 0, result.stderr);
  const repeat = run(['compare', 'architecture', baseFixture, headFixture, second, '--json']);
  assert.equal(repeat.status, 0, repeat.stderr);

  const firstHtml = fs.readFileSync(first, 'utf8');
  const secondHtml = fs.readFileSync(second, 'utf8');
  assert.equal(firstHtml, secondHtml);
  assert.equal((firstHtml.match(/<section class="canvas" data-view=/g) || []).length, 3);
  assert.match(firstHtml, /data-view="delta">/);
  assert.match(firstHtml, /data-node-id="cache"[^>]+data-delta-state="removed"/);
  assert.match(firstHtml, /data-node-id="fraud"[^>]+data-delta-state="added"/);
  assert.match(firstHtml, /data-node-id="queue"[^>]+data-delta-state="moved-from"/);
  assert.match(firstHtml, /aria-label="Authored change review"/);
  assert.equal((firstHtml.match(/class="change-row"/g) || []).length, 11);
  assert.match(firstHtml, /data-change-key="component:fraud"/);
  assert.match(firstHtml, /data-change-key="relationship:authorize-payment"/);
  assert.match(firstHtml, /data-change-key="boundary:region:Production region"/);
  assert.match(firstHtml, /data-change-target-signature="[^"]+"/);
  assert.match(firstHtml, /data-delta-boundary-key="region:Production region"/);
  assert.equal((firstHtml.match(/class="snapshot-frame"/g) || []).length, 2);
  assert.match(firstHtml, /title="Before architecture explorer"/);
  assert.match(firstHtml, /title="After architecture explorer"/);
  assert.match(firstHtml, /id="export-svg"[^>]*>Export SVG</);
  assert.match(firstHtml, /id="share-card"[^>]*>Share Card</);
  assert.match(firstHtml, /window\.Archify\.deltaExport = \{ canonicalSvg: canonicalDeltaSvg, shareCard/);
  assert.match(firstHtml, /canvas\.width = 1200;[\s\S]*canvas\.height = 630;/);
  assert.match(firstHtml, /structural-frame.*stroke:var\(--delta\)!important/);
  assert.match(firstHtml, /structural-frame.*data-delta-state="changed".*stroke-dasharray:2 3!important/);
  assert.match(firstHtml, /data-delta-boundary-state="added".*fill:#34d399!important/);
  assert.match(firstHtml, /delta-boundary-marker\[data-delta-state\]\{color:var\(--delta\)\}/);
  assert.match(firstHtml, /No authored architecture changes ·.*movementSummary/);
  assert.match(firstHtml, /font-family:"JetBrains Mono",ui-monospace/);
  assert.doesNotMatch(firstHtml, /font-family:Inter|body\{min-width:1080px/);
  assert.match(firstHtml, /@media\(max-width:760px\)/);
  assert.match(firstHtml, /\.canvas svg\{min-width:720px;max-height:none\}/);
  assert.match(firstHtml, /\.changes\{overflow-x:auto\}/);
  assert.match(firstHtml, /const REVIEW_DWELL_MS = 1400;/);
  assert.match(firstHtml, /prefers-reduced-motion: reduce/);
  assert.match(firstHtml, /:not\(\[data-delta-review-current\]\)/);
  assert.match(firstHtml, /--review-same-opacity:1;--review-change-opacity:1/);
  assert.match(firstHtml, /--d-focus:#006b8f/);
  assert.match(firstHtml, /document\.querySelectorAll\('#archify-compare-receipt'\)\.length !== 1/);
  assert.match(firstHtml, /targetsMatch\(reviewSources\[index\], row, matches\)/);
  assert.match(firstHtml, /document\.addEventListener\('visibilitychange'/);
  assert.match(firstHtml, /window\.addEventListener\('beforeprint', overview\)/);
  assert.match(firstHtml, /aria-current', 'step'/);
  assert.match(firstHtml, /event\.key === 'Enter' \|\| event\.key === ' '/);
  const deltaShell = firstHtml.replace(/<iframe\b[^>]*><\/iframe>/g, '');
  assert.doesNotMatch(deltaShell, /localStorage|sessionStorage|history\.(?:pushState|replaceState)/);
  assert.doesNotMatch(deltaShell, /setInterval\(/);
  assert.doesNotMatch(deltaShell, /\b(?:SAFE|LOW RISK|MERGEABLE|NO IMPACT|VERIFIED PR)\b/i);

  const receipt = JSON.parse(result.stdout);
  const sidecar = read(path.join(tmp, 'first.receipt.json'));
  assert.deepEqual(sidecar, receipt);
  assert.equal(receipt.artifact.sha256, JSON.parse(repeat.stdout).artifact.sha256);
  assert.equal(receipt.validation.checksPassed, receipt.validation.checkCount);
  assert.equal(receipt.completeness, 'complete');
  assert.equal(JSON.stringify(receipt).includes(tmp), false);
  assert.deepEqual(validateArchitectureDeltaHtml(firstHtml, receipt), { ok: true, checksPassed: 10, checkCount: 10 });
});

test('checked-in Checkout compare artifact is reproducible from its authoritative inputs', () => {
  const artifact = path.join(tmp, 'checked-artifact.html');
  const receipt = path.join(tmp, 'checked-artifact.receipt.json');
  const result = run([
    'compare',
    'architecture',
    baseFixture,
    headFixture,
    artifact,
    '--receipt',
    receipt,
    '--quality',
    'showcase',
    '--json',
  ]);
  assert.equal(result.status, 0, result.stderr);
  assert.equal(fs.readFileSync(artifact, 'utf8'), fs.readFileSync(checkedArtifact, 'utf8'));
  assert.deepEqual(read(receipt), read(checkedReceipt));
});

test('artifact validation fails closed on missing, duplicate, or self-blessed review identity', () => {
  const output = path.join(tmp, 'review-identity.html');
  const result = run(['compare', 'architecture', baseFixture, headFixture, output, '--json']);
  assert.equal(result.status, 0, result.stderr);
  const receipt = JSON.parse(result.stdout);
  const html = fs.readFileSync(output, 'utf8');
  const deltaSection = html.match(/<section class="canvas" data-view="delta">([\s\S]*?)<\/section>/)?.[1];
  assert.ok(deltaSection);
  const fraudTag = deltaSection.match(/<g\s+[^>]*\bdata-node-id="fraud"[^>]*>/)?.[0];
  assert.ok(fraudTag);

  const missing = html.replace(fraudTag, fraudTag.replace('data-node-id="fraud"', 'data-node-id="tampered"'));
  assert.throws(
    () => validateArchitectureDeltaHtml(missing, receipt),
    (error) => error instanceof ArchitectureDeltaError
      && error.code === 'delta/artifact-invalid'
      && error.details.failures.includes('ambiguous Delta identity component:fraud'),
  );

  const duplicate = html.replace(fraudTag, `${fraudTag}${fraudTag}`);
  assert.throws(
    () => validateArchitectureDeltaHtml(duplicate, receipt),
    (error) => error instanceof ArchitectureDeltaError
      && error.code === 'delta/artifact-invalid'
      && error.details.failures.includes('ambiguous Delta identity component:fraud'),
  );

  const relationshipGroup = deltaSection.match(/<g\s+[^>]*\bdata-edge-id="fraud-check"[^>]*>[\s\S]*?<\/g>/)?.[0];
  assert.ok(relationshipGroup);
  const duplicateCompanion = html.replace(relationshipGroup, `${relationshipGroup}${relationshipGroup}`);
  assert.throws(
    () => validateArchitectureDeltaHtml(duplicateCompanion, receipt),
    (error) => error instanceof ArchitectureDeltaError
      && error.code === 'delta/artifact-invalid'
      && error.details.failures.includes('ambiguous Delta target signature relationship:fraud-check'),
  );

  const duplicateRowTag = duplicateCompanion.match(/<button class="change-row"[^>]*data-change-key="relationship:fraud-check"[^>]*>/)?.[0];
  const storedSignature = duplicateRowTag?.match(/data-change-target-signature="([^"]+)"/)?.[1];
  assert.ok(duplicateRowTag && storedSignature);
  const selfBlessedSignature = [...storedSignature.split('|'), 'g:added:topology'].sort().join('|');
  const selfBlessed = duplicateCompanion.replace(
    duplicateRowTag,
    duplicateRowTag.replace(`data-change-target-signature="${storedSignature}"`, `data-change-target-signature="${selfBlessedSignature}"`),
  );
  assert.throws(
    () => validateArchitectureDeltaHtml(selfBlessed, receipt),
    (error) => error instanceof ArchitectureDeltaError
      && error.code === 'delta/artifact-invalid'
      && error.details.failures.includes('ambiguous Delta target signature relationship:fraud-check'),
  );

  const missingCompanionState = html.replace(
    relationshipGroup,
    relationshipGroup.replace(/\sdata-delta-state="[^"]+"/, ''),
  );
  assert.throws(
    () => validateArchitectureDeltaHtml(missingCompanionState, receipt),
    (error) => error instanceof ArchitectureDeltaError
      && error.code === 'delta/artifact-invalid'
      && error.details.failures.includes('missing Delta target state relationship:fraud-check'),
  );

  const receiptNode = html.match(/<script id="archify-compare-receipt"[\s\S]*?<\/script>/)?.[0];
  assert.ok(receiptNode);
  const duplicateReceipt = html.replace(receiptNode, `${receiptNode}${receiptNode}`);
  assert.throws(
    () => validateArchitectureDeltaHtml(duplicateReceipt, receipt),
    (error) => error instanceof ArchitectureDeltaError
      && error.code === 'delta/artifact-invalid'
      && error.details.failures.includes('expected exactly one embedded compare receipt'),
  );

  const extraDeltaSvg = html.replace(
    '<section class="canvas" data-view="delta">',
    '<section class="canvas" data-view="delta"><svg viewBox="0 0 1 1"></svg>',
  );
  assert.throws(
    () => validateArchitectureDeltaHtml(extraDeltaSvg, receipt),
    (error) => error instanceof ArchitectureDeltaError
      && error.code === 'delta/artifact-invalid'
      && error.details.failures.includes('expected exactly one root SVG in the Delta canvas'),
  );
});

test('formatting-only input changes raw proof but not semantic hash or artifact bytes', () => {
  const reorderedPath = path.join(tmp, 'reordered-base.json');
  const reordered = read(baseFixture);
  reordered.components.reverse();
  reordered.connections.reverse();
  reordered.boundaries.forEach((boundary) => boundary.wraps.reverse());
  fs.writeFileSync(reorderedPath, JSON.stringify(reordered, null, 4));

  const originalOut = path.join(tmp, 'canonical-original.html');
  const reorderedOut = path.join(tmp, 'canonical-reordered.html');
  const original = run(['compare', 'architecture', baseFixture, headFixture, originalOut, '--json']);
  const changed = run(['compare', 'architecture', reorderedPath, headFixture, reorderedOut, '--json']);
  assert.equal(original.status, 0, original.stderr);
  assert.equal(changed.status, 0, changed.stderr);
  const originalReceipt = JSON.parse(original.stdout);
  const changedReceipt = JSON.parse(changed.stdout);
  assert.notEqual(originalReceipt.base.rawSha256, changedReceipt.base.rawSha256);
  assert.equal(originalReceipt.base.semanticSha256, changedReceipt.base.semanticSha256);
  assert.equal(fs.readFileSync(originalOut, 'utf8'), fs.readFileSync(reorderedOut, 'utf8'));
  assert.equal(originalReceipt.artifact.sha256, changedReceipt.artifact.sha256);
});

test('compare failure preserves an existing trusted artifact', () => {
  const invalid = read(headFixture);
  delete invalid.connections[0].id;
  const invalidPath = path.join(tmp, 'invalid-head.json');
  const output = path.join(tmp, 'preserved.html');
  fs.writeFileSync(invalidPath, JSON.stringify(invalid));
  fs.writeFileSync(output, 'trusted artifact');

  const result = run(['compare', 'architecture', baseFixture, invalidPath, output, '--json']);
  assert.notEqual(result.status, 0);
  assert.equal(fs.readFileSync(output, 'utf8'), 'trusted artifact');
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.ok, false);
  assert.equal(receipt.diagnostics[0].code, 'delta/relationship-id-required');
  assert.equal(fs.existsSync(path.join(tmp, 'preserved.receipt.json')), false);
});

test('compare validates raw snapshots before canonicalization can discard invalid fields', () => {
  const invalid = read(baseFixture);
  invalid.unknown_top_level_fact = true;
  const invalidPath = path.join(tmp, 'invalid-raw-base.json');
  const output = path.join(tmp, 'invalid-raw-base.html');
  fs.writeFileSync(invalidPath, JSON.stringify(invalid));

  const result = run(['compare', 'architecture', invalidPath, headFixture, output, '--json']);
  assert.notEqual(result.status, 0);
  assert.equal(fs.existsSync(output), false);
  assert.equal(fs.existsSync(path.join(tmp, 'invalid-raw-base.receipt.json')), false);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.ok, false);
  assert.equal(receipt.diagnostics[0].code, 'schema/additionalProperties');
  assert.equal(receipt.diagnostics[0].subject.side, 'base');
  assert.equal(receipt.diagnostics[0].subject.path, '/');
  assert.equal(receipt.diagnostics[0].evidence.additionalProperty, 'unknown_top_level_fact');
});

test('compare commit preflights both targets before replacing a trusted pair', () => {
  const caseRoot = fs.mkdtempSync(path.join(tmp, 'pair-target-'));
  const output = path.join(caseRoot, 'review.html');
  const receiptPath = path.join(caseRoot, 'review.receipt.json');
  fs.writeFileSync(output, 'trusted html');
  fs.mkdirSync(receiptPath);

  const result = run([
    'compare', 'architecture', baseFixture, headFixture, output,
    '--receipt', receiptPath, '--json',
  ]);

  assert.notEqual(result.status, 0);
  assert.equal(fs.readFileSync(output, 'utf8'), 'trusted html');
  assert.equal(fs.statSync(receiptPath).isDirectory(), true);
  const failure = JSON.parse(result.stdout);
  assert.equal(failure.stage, 'commit');
  assert.equal(failure.diagnostics[0].code, 'delta/commit-target');
  assert.equal(failure.diagnostics[0].evidence.targetType, 'directory');
});
```

## test/artifact-receipt-flush.test.mjs

```js
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { after, test } from 'node:test';
import { fileURLToPath } from 'node:url';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const checker = path.join(skillRoot, 'scripts/check-render-output.mjs');
const cli = path.join(skillRoot, 'bin/archify.mjs');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-receipt-flush-'));
after(() => fs.rmSync(tmp, { recursive: true, force: true }));

function run(script, args, options = {}) {
  return spawnSync(process.execPath, [script, ...args], {
    cwd: skillRoot,
    encoding: 'utf8',
    timeout: 30_000,
    ...options,
  });
}

for (const profile of ['standard', 'showcase']) {
  test(`artifact checker flushes a large ${profile} receipt to a pipe`, () => {
    // Distinct relationships sharing one corridor produce a real, large
    // composition receipt: warnings in standard, errors in showcase.
    const count = 24;
    const arrows = Array.from({ length: count }, (_, index) => (
      `<path data-edge-id="edge-${index}" data-edge-from="source-${index}" data-edge-to="target-${index}" d="M 20 40 L 220 40" class="a-default" marker-end="url(#arrowhead)"/>`
    )).join('\n');
    const html = path.join(tmp, `${profile}.html`);
    fs.writeFileSync(html, `<svg viewBox="0 0 240 160" data-quality-profile="${profile}">${arrows}</svg>`);

    // A regular file is the synchronous-output control. The pipe must carry
    // exactly the same bytes, including the final diagnostic and closing JSON.
    const reference = path.join(tmp, `${profile}.json`);
    const descriptor = fs.openSync(reference, 'w');
    let fileRun;
    try {
      fileRun = run(checker, [html], { stdio: ['ignore', descriptor, 'pipe'] });
    } finally {
      fs.closeSync(descriptor);
    }
    const expectedCode = profile === 'standard' ? 0 : 1;
    assert.equal(fileRun.status, expectedCode, fileRun.stderr);
    const expected = fs.readFileSync(reference, 'utf8');
    assert.ok(Buffer.byteLength(expected) > 64 * 1024, 'fixture must exceed a 64 KiB pipe buffer');
    const receipt = JSON.parse(expected);
    assert.equal(receipt.ok, profile === 'standard');
    assert.equal(receipt.composition.issues.length, count * (count - 1) / 2);
    assert.ok(receipt.composition.issues.every((issue) => (
      issue.code === 'composition/ambiguous-corridor'
      && issue.severity === (profile === 'standard' ? 'warning' : 'error')
    )));

    const piped = run(checker, [html]);
    assert.equal(piped.status, expectedCode, piped.stderr);
    assert.equal(piped.stderr, '');
    assert.equal(Buffer.byteLength(piped.stdout), Buffer.byteLength(expected), 'pipe must not truncate the receipt');
    assert.equal(piped.stdout, expected);
    assert.deepEqual(JSON.parse(piped.stdout), receipt);
  });
}

function denseArchitecture() {
  const count = 20;
  const components = [];
  const connections = [];
  for (let index = 0; index < count; index += 1) {
    const sourceX = 40 + index * 160;
    const targetX = 40 + (count - 1 - index) * 160;
    components.push(
      { id: `source-${index}`, type: 'backend', label: `Source ${index}`, pos: [sourceX, 40], size: [120, 60] },
      { id: `target-${index}`, type: 'database', label: `Target ${index}`, pos: [targetX, 400], size: [120, 60] },
    );
    connections.push({
      id: `edge-${index}`,
      from: `source-${index}`,
      to: `target-${index}`,
      fromSide: 'bottom',
      toSide: 'top',
      via: [[sourceX + 60, 240], [targetX + 60, 240]],
    });
  }
  return {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Large receipt regression', quality_profile: 'standard' },
    components,
    connections,
  };
}

for (const command of ['validate', 'deliver', 'compare']) {
  test(`${command} consumes a complete artifact receipt larger than 64 KiB`, () => {
    const source = denseArchitecture();
    const input = path.join(tmp, `${command}.architecture.json`);
    const output = path.join(tmp, `${command}.html`);
    fs.writeFileSync(input, JSON.stringify(source));
    const args = command === 'validate' ? [input]
      : command === 'deliver' ? [input, output]
        : [input, input, output];
    const result = run(cli, [command, 'architecture', ...args, '--json']);
    assert.equal(result.status, 0, result.stderr || result.stdout);
    assert.equal(result.stderr, '');
    const receipt = JSON.parse(result.stdout);
    assert.equal(receipt.ok, true);
    assert.equal(receipt.command, command);
    if (command === 'validate') {
      assert.ok(Buffer.byteLength(result.stdout) > 64 * 1024);
      assert.ok(receipt.checks.every((check) => check.ok));
      assert.ok(receipt.composition.issues.length > 100);
    } else {
      const artifact = fs.readFileSync(output);
      assert.ok(artifact.length > 0);
      if (command === 'deliver') {
        assert.equal(receipt.artifact.sha256, createHash('sha256').update(artifact).digest('hex'));
      } else {
        assert.equal(receipt.completeness, 'complete');
        assert.equal(receipt.proofLevel, 'authored');
      }
    }
  });
}
```

## test/authored-reachability.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import vm from 'node:vm';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-authored-reach-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

function reachabilityFunction() {
  const start = template.indexOf('function computeReachability(');
  const end = template.indexOf('\n      function reachabilityFor(', start);
  assert.ok(start >= 0 && end > start, 'template exposes one extractable reachability function');
  return vm.runInNewContext(`(${template.slice(start, end)})`);
}

test('authored reachability is available in every typed artifact without entering canonical SVG', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="focus-reach" hidden/);
    assert.match(html, /id="btn-reach-upstream"[^>]+aria-pressed="false"/);
    assert.match(html, /id="btn-reach-downstream"[^>]+aria-pressed="false"/);
    assert.match(html, /function computeReachability\(originId, direction, relationships\)/);
    assert.match(html, /svg\.setAttribute\('data-reach-active', direction\)/);
    assert.doesNotMatch(canonicalSvg(html), /data-reach-(?:active|match|origin|depth)/, mode);
  }
});

test('reachability uses stable breadth-first depth, supports cycles, and deduplicates edge fragments', () => {
  const compute = reachabilityFunction();
  const relationships = [
    { key: 'a-b', from: 'a', to: 'b' },
    { key: 'a-c', from: 'a', to: 'c' },
    { key: 'b-d', from: 'b', to: 'd' },
    { key: 'c-d', from: 'c', to: 'd' },
    { key: 'd-b', from: 'd', to: 'b' },
    { key: 'x-a', from: 'x', to: 'a' },
    { key: 'a-b', from: 'a', to: 'b' },
  ];

  const downstream = compute('a', 'downstream', relationships);
  assert.deepEqual(Array.from(downstream.nodeIds), ['a', 'b', 'c', 'd']);
  assert.deepEqual({ ...downstream.depths }, { a: 0, b: 1, c: 1, d: 2 });
  assert.deepEqual(Array.from(downstream.edgeKeys), ['a-b', 'a-c', 'b-d', 'c-d', 'd-b']);
  assert.equal(downstream.maxDepth, 2);

  const upstream = compute('d', 'upstream', relationships);
  assert.deepEqual(Array.from(upstream.nodeIds), ['d', 'b', 'c', 'a', 'x']);
  assert.deepEqual({ ...upstream.depths }, { d: 0, b: 1, c: 1, a: 2, x: 3 });
  assert.equal(upstream.maxDepth, 3);
  assert.equal(compute('a', 'sideways', relationships), null);
});

test('reachability stays explicit, deep-linkable, keyboard reachable, and export-clean', () => {
  assert.match(template, /Authored Reachability is a bounded graph query over the relationships/);
  assert.match(template, /direction !== 'upstream' && direction !== 'downstream'/);
  assert.match(template, /encodeURIComponent\(activeIds\[0\]\) \+ '&reach=' \+ direction/);
  assert.match(template, /applyReachability\(reach, \{ updateUrl: false, toggle: false, reveal: false \}\)/);
  assert.match(template, /upstreamBtn\.addEventListener\('click'/);
  assert.match(template, /downstreamBtn\.addEventListener\('click'/);
  assert.match(template, /clone\.removeAttribute\('data-reach-active'\)/);
  assert.match(template, /clone\.querySelectorAll\('\[data-reach-match\], \[data-reach-origin\], \[data-reach-depth\]'/);
  assert.match(template, /!clone\.hasAttribute\('data-reach-active'\)/);
  assert.match(template, /svg\[data-preset="blueprint"\]\[data-reach-active\]/);
  assert.match(template, /\.diagram-container svg\[data-reach-active\] \[data-node-id\]/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/authoring-safety-contract.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const skill = fs.readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8');
const authoringContract = fs.readFileSync(
  path.join(skillRoot, 'references', 'authoring-contract.md'),
  'utf8',
);
const schemaReadme = fs.readFileSync(path.join(skillRoot, 'schemas', 'README.md'), 'utf8');

test('semantic relationship labels are preserved and deletion is not a geometry repair', () => {
  for (const [name, source] of [['SKILL.md', skill], ['authoring contract', authoringContract]]) {
    assert.match(source, /Relationship labels are semantic data/i, name);
    assert.match(source, /move the label[\s\S]*adjust the route or spacing[\s\S]*shorten/i, name);
    assert.match(source, /protocol[\s\S]*action[\s\S]*direction[\s\S]*synchronous[\s\S]*asynchronous[\s\S]*cross-boundary mechanism/i, name);
    assert.match(source, /Omit only wording[\s\S]*fully implied by both endpoints/i, name);
    assert.match(source, /Preserve every meaningful label/i, name);
    assert.match(source, /deleting it is not\s+a (?:geometry|spacing) repair/i, name);
  }
});

test('schema policy documents the workflow v1/v2 compatibility boundary', () => {
  assert.match(schemaReadme, /Workflow[^\n]*schema versions? 1 and 2/i);
  assert.match(schemaReadme, /other four[^\n]*schema_version[^\n]*1/i);
  assert.doesNotMatch(schemaReadme, /schema_version` is `"const": 1`/);
});

test('deployment ownership stays explicit, fact-backed, and cannot be removed to pass', () => {
  assert.match(skill, /Omit `meta\.engineering_profile` by default/);
  assert.match(skill, /Region.*cluster.*security boundar.*do not.*enable/i);
  assert.match(skill, /production deployment topology.*ownership.*fail-closed deployment review/i);
  assert.match(skill, /must not remove.*engineering profile.*pass validation/i);
});

test('visual-check stays a pending sidecar receipt instead of a polish claim', () => {
  const deliveryContract = fs.readFileSync(
    path.join(skillRoot, 'references', 'delivery-contract.md'),
    'utf8',
  );
  assert.match(skill, /visual-check <output\.html> --json/);
  assert.match(skill, /automated browser evidence[\s\S]*perceptual visual review/i);
  assert.match(skill, /references\/delivery-contract\.md/);
  assert.match(skill, /without (?:rerendering or )?modifying/i);

  assert.match(deliveryContract, /visual-check <output\.html> --json/);
  assert.match(deliveryContract, /1440×900[\s\S]*1600×1000[\s\S]*1920×1080[\s\S]*2048×1320/);
  assert.match(deliveryContract, /visualReview: "pending"/);
  assert.match(deliveryContract, /never changes.*delivered|without (?:rerendering or )?modifying/i);
});
```

## test/automatic-port-spread.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');

function render(mode, doc) {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-port-spread-'));
  const input = path.join(tmp, 'input.json');
  const output = path.join(tmp, 'output.html');
  fs.writeFileSync(input, JSON.stringify(doc));
  try {
    execFileSync('node', [
      path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      input,
      output,
    ], { stdio: ['ignore', 'ignore', 'pipe'] });
    return fs.readFileSync(output, 'utf8');
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
}

function connectionPoints(html, id) {
  const pattern = new RegExp(`data-edge-id="${id}"[^>]+data-composition-points="([^"]+)"`);
  const match = html.match(pattern);
  assert.ok(match, `missing rendered connection ${id}`);
  return match[1].split(';').map((point) => point.split(',').map(Number));
}

function fanOutArchitecture(connections) {
  return {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Automatic port spread' },
    components: [
      { id: 'hub', type: 'backend', label: 'Hub', pos: [100, 280], size: [120, 60] },
      { id: 'upper', type: 'external', label: 'Upper', pos: [500, 100], size: [120, 60] },
      { id: 'middle', type: 'database', label: 'Middle', pos: [500, 280], size: [120, 60] },
      { id: 'lower', type: 'cloud', label: 'Lower', pos: [500, 460], size: [120, 60] },
    ],
    connections,
  };
}

test('architecture: automatic fan-out uses distinct symmetric ports with corner clearance', () => {
  const html = render('architecture', fanOutArchitecture([
    { id: 'to-upper', from: 'hub', to: 'upper' },
    { id: 'to-middle', from: 'hub', to: 'middle' },
    { id: 'to-lower', from: 'hub', to: 'lower' },
  ]));

  assert.deepEqual(connectionPoints(html, 'to-upper')[0], [220, 296]);
  assert.deepEqual(connectionPoints(html, 'to-middle')[0], [220, 310]);
  assert.deepEqual(connectionPoints(html, 'to-lower')[0], [220, 324]);
});

test('architecture: automatic port assignment is stable when relationship input order changes', () => {
  const connections = [
    { id: 'to-upper', from: 'hub', to: 'upper' },
    { id: 'to-middle', from: 'hub', to: 'middle' },
    { id: 'to-lower', from: 'hub', to: 'lower' },
  ];
  const forward = render('architecture', fanOutArchitecture(connections));
  const reversed = render('architecture', fanOutArchitecture([...connections].reverse()));

  for (const connection of connections) {
    assert.deepEqual(
      connectionPoints(forward, connection.id),
      connectionPoints(reversed, connection.id),
      `${connection.id} moved after input reordering`,
    );
  }
});

test('architecture: a singly spread near-aligned vertical relationship keeps one direct axis', () => {
  const html = render('architecture', {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Near-aligned fan-out' },
    components: [
      { id: 'api', type: 'backend', label: 'API', pos: [675, 300], size: [120, 60] },
      { id: 'auth', type: 'security', label: 'Auth', pos: [570, 120], size: [100, 60] },
      { id: 'cache', type: 'database', label: 'Cache', pos: [685, 120], size: [100, 60] },
    ],
    connections: [
      { id: 'verify', from: 'api', to: 'auth', fromSide: 'top', toSide: 'bottom' },
      { id: 'read', from: 'api', to: 'cache', fromSide: 'top', toSide: 'bottom' },
    ],
  });

  assert.deepEqual(connectionPoints(html, 'read'), [[742, 300], [742, 180]]);
  assert.notDeepEqual(connectionPoints(html, 'verify')[0], connectionPoints(html, 'read')[0]);
});

test('architecture: a singly spread near-aligned horizontal relationship keeps one direct axis', () => {
  const html = render('architecture', {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Near-aligned horizontal fan-out' },
    components: [
      { id: 'hub', type: 'backend', label: 'Hub', pos: [100, 100], size: [120, 60] },
      { id: 'direct', type: 'database', label: 'Direct', pos: [500, 107], size: [120, 60] },
      { id: 'branch', type: 'cloud', label: 'Branch', pos: [500, 300], size: [120, 60] },
    ],
    connections: [
      { id: 'hub-direct', from: 'hub', to: 'direct', fromSide: 'right', toSide: 'left' },
      { id: 'hub-branch', from: 'hub', to: 'branch', fromSide: 'right', toSide: 'left' },
    ],
  });

  assert.deepEqual(connectionPoints(html, 'hub-direct'), [[220, 123], [500, 123]]);
  assert.notDeepEqual(connectionPoints(html, 'hub-branch')[0], connectionPoints(html, 'hub-direct')[0]);
});

test('architecture: a shared bottom port keeps its aligned child relationship straight', () => {
  const doc = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Vertical child relationship' },
    components: [
      { id: 'parent', type: 'backend', label: 'Parent Session', pos: [300, 100], size: [200, 60] },
      { id: 'terminal', type: 'backend', label: 'Background terminal', pos: [300, 300], size: [200, 60] },
      { id: 'workflow', type: 'backend', label: 'Workflow', pos: [560, 300], size: [180, 60] },
    ],
    connections: [
      { id: 'parent-terminal', from: 'parent', to: 'terminal', fromSide: 'bottom', toSide: 'top' },
      { id: 'parent-workflow', from: 'parent', to: 'workflow', fromSide: 'bottom', toSide: 'top' },
    ],
  };
  const forward = render('architecture', doc);
  const reversed = render('architecture', {
    ...doc,
    connections: [...doc.connections].reverse(),
  });

  assert.deepEqual(connectionPoints(forward, 'parent-terminal'), [[393, 160], [393, 300]]);
  assert.notDeepEqual(
    connectionPoints(forward, 'parent-workflow')[0],
    connectionPoints(forward, 'parent-terminal')[0],
  );
  for (const connection of doc.connections) {
    assert.deepEqual(
      connectionPoints(forward, connection.id),
      connectionPoints(reversed, connection.id),
      `${connection.id} moved after input reordering`,
    );
  }
});

test('architecture: incoming and outgoing relationships keep distinct bottom ports while the direct child stays straight', () => {
  const html = render('architecture', {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Shared incoming and outgoing side' },
    components: [
      { id: 'workflow', type: 'backend', label: 'Workflow', pos: [80, 320], size: [180, 60] },
      { id: 'child', type: 'security', label: 'Child boundary', pos: [300, 100], size: [200, 60] },
      { id: 'footer', type: 'frontend', label: 'Footer', pos: [300, 320], size: [200, 60] },
    ],
    connections: [
      { id: 'workflow-child', from: 'workflow', to: 'child', fromSide: 'right', toSide: 'bottom' },
      { id: 'child-footer', from: 'child', to: 'footer', fromSide: 'bottom', toSide: 'top' },
    ],
  });

  assert.deepEqual(connectionPoints(html, 'child-footer'), [[407, 160], [407, 320]]);
  assert.notDeepEqual(
    connectionPoints(html, 'workflow-child').at(-1),
    connectionPoints(html, 'child-footer')[0],
  );
});

test('architecture: a near-aligned relationship keeps the outside bridge when both endpoints are spread', () => {
  const html = render('architecture', {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Two-sided port competition' },
    components: [
      { id: 'source', type: 'backend', label: 'Source', pos: [300, 320], size: [160, 60] },
      { id: 'source-peer', type: 'backend', label: 'Source peer', pos: [80, 320], size: [160, 60] },
      { id: 'target', type: 'database', label: 'Target', pos: [300, 100], size: [160, 60] },
      { id: 'target-peer', type: 'database', label: 'Target peer', pos: [560, 100], size: [160, 60] },
    ],
    connections: [
      { id: 'source-target', from: 'source', to: 'target', fromSide: 'top', toSide: 'bottom' },
      { id: 'source-peer-target', from: 'source-peer', to: 'target', fromSide: 'top', toSide: 'bottom' },
      { id: 'source-target-peer', from: 'source', to: 'target-peer', fromSide: 'top', toSide: 'bottom' },
    ],
  });

  const points = connectionPoints(html, 'source-target');
  assert.ok(points.length > 2);
  assert.notEqual(points[0][0], points.at(-1)[0]);
});

test('architecture: a singly spread near-aligned relationship keeps the bridge when its direct axis is blocked', () => {
  const html = render('architecture', {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Blocked vertical axis' },
    components: [
      { id: 'parent', type: 'backend', label: 'Parent', pos: [300, 100], size: [200, 60] },
      { id: 'terminal', type: 'backend', label: 'Terminal', pos: [300, 400], size: [200, 60] },
      { id: 'workflow', type: 'backend', label: 'Workflow', pos: [560, 400], size: [180, 60] },
      { id: 'obstacle', type: 'external', label: 'X', pos: [382, 245], size: [26, 60] },
    ],
    connections: [
      { id: 'parent-terminal', from: 'parent', to: 'terminal', fromSide: 'bottom', toSide: 'top' },
      { id: 'parent-workflow', from: 'parent', to: 'workflow', fromSide: 'bottom', toSide: 'top' },
    ],
  });

  const points = connectionPoints(html, 'parent-terminal');
  assert.ok(points.length > 2);
  assert.notEqual(points[0][0], points.at(-1)[0]);
});

test('architecture: single and explicitly positioned relationships keep legacy anchors', () => {
  const doc = fanOutArchitecture([
    { id: 'single', from: 'hub', to: 'middle' },
    { id: 'via', from: 'hub', to: 'upper', via: [[300, 310], [300, 130]] },
    { id: 'fixed-route', from: 'hub', to: 'lower', route: 'orthogonal-h' },
    { id: 'fixed-label', from: 'hub', to: 'upper', label: 'contract', labelAt: [360, 200] },
  ]);
  const html = render('architecture', doc);

  assert.deepEqual(connectionPoints(html, 'single'), [[220, 310], [500, 310]]);
  assert.deepEqual(connectionPoints(html, 'via'), [[220, 310], [300, 310], [300, 130], [500, 130]]);
  assert.deepEqual(connectionPoints(html, 'fixed-route'), [[220, 310], [360, 310], [360, 490], [500, 490]]);
  assert.deepEqual(connectionPoints(html, 'fixed-label'), [[220, 310], [360, 310], [360, 130], [500, 130]]);
});

test('architecture: an unspread near-aligned connection shares one horizontal axis', () => {
  const html = render('architecture', {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Near-aligned single connection' },
    components: [
      { id: 'console', type: 'frontend', label: 'Console', pos: [260, 300], size: [170, 64] },
      { id: 'controlplane', type: 'backend', label: 'Control plane', pos: [500, 300], size: [190, 72] },
    ],
    connections: [
      { id: 'console-controlplane', from: 'console', to: 'controlplane', label: 'REST /api', variant: 'emphasis', labelDy: -36 },
    ],
  });

  assert.deepEqual(connectionPoints(html, 'console-controlplane'), [[430, 332], [500, 332]]);
});

test('workflow: automatic cross-lane fan-out selects distinct perpendicular source sides', () => {
  const html = render('workflow', {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Workflow port spread' },
    lanes: [
      { id: 'upper-lane', label: 'Upper' },
      { id: 'hub-lane', label: 'Hub' },
      { id: 'lower-lane', label: 'Lower' },
    ],
    nodes: [
      { id: 'hub', lane: 'hub-lane', col: 0, type: 'backend', label: 'Hub' },
      { id: 'upper', lane: 'upper-lane', col: 3, type: 'external', label: 'Upper' },
      { id: 'middle', lane: 'hub-lane', col: 3, type: 'database', label: 'Middle' },
      { id: 'lower', lane: 'lower-lane', col: 3, type: 'cloud', label: 'Lower' },
    ],
    edges: [
      { id: 'to-upper', from: 'hub', to: 'upper' },
      { id: 'to-middle', from: 'hub', to: 'middle' },
      { id: 'to-lower', from: 'hub', to: 'lower' },
    ],
  });

  assert.deepEqual(connectionPoints(html, 'to-upper')[0], [88, 217]);
  assert.deepEqual(connectionPoints(html, 'to-middle')[0], [134, 243]);
  assert.deepEqual(connectionPoints(html, 'to-lower')[0], [88, 269]);
});

test('dataflow: automatic fan-out spreads flows without changing their authored topology', () => {
  const html = render('dataflow', {
    schema_version: 1,
    diagram_type: 'dataflow',
    meta: { title: 'Data-flow port spread' },
    stages: [{ label: 'Source' }, { label: 'Transform' }, { label: 'Sinks' }],
    nodes: [
      { id: 'hub', type: 'backend', label: 'Hub', stage: 0, row: 2 },
      { id: 'upper', type: 'external', label: 'Upper', stage: 2, row: 0 },
      { id: 'middle', type: 'database', label: 'Middle', stage: 2, row: 2 },
      { id: 'lower', type: 'cloud', label: 'Lower', stage: 2, row: 4 },
    ],
    flows: [
      { id: 'to-upper', from: 'hub', to: 'upper', label: 'upper feed' },
      { id: 'to-middle', from: 'hub', to: 'middle', label: 'middle feed' },
      { id: 'to-lower', from: 'hub', to: 'lower', label: 'lower feed' },
    ],
  });

  assert.deepEqual(connectionPoints(html, 'to-upper')[0], [156, 372]);
  assert.deepEqual(connectionPoints(html, 'to-middle')[0], [156, 385]);
  assert.deepEqual(connectionPoints(html, 'to-lower')[0], [156, 398]);
});

test('lifecycle: automatic fan-out spreads transitions across lifecycle bands', () => {
  const html = render('lifecycle', {
    schema_version: 1,
    diagram_type: 'lifecycle',
    meta: { title: 'Lifecycle port spread' },
    lanes: [
      { id: 'main', label: 'Main' },
      { id: 'event', label: 'Events' },
      { id: 'terminal', label: 'Outcomes' },
    ],
    states: [
      { id: 'hub', type: 'active', label: 'Hub', lane: 'event', col: 0 },
      { id: 'upper', type: 'waiting', label: 'Upper', lane: 'main', col: 4 },
      { id: 'middle', type: 'success', label: 'Middle', lane: 'event', col: 2 },
      { id: 'lower', type: 'failure', label: 'Lower', lane: 'terminal', col: 2 },
    ],
    transitions: [
      { id: 'to-upper', from: 'hub', to: 'upper' },
      { id: 'to-middle', from: 'hub', to: 'middle' },
      { id: 'to-lower', from: 'hub', to: 'lower' },
    ],
  });

  assert.deepEqual(connectionPoints(html, 'to-upper')[0], [465, 294]);
  assert.deepEqual(connectionPoints(html, 'to-middle')[0], [465, 307]);
  assert.deepEqual(connectionPoints(html, 'to-lower')[0], [465, 320]);
});

test('lifecycle: same-band port spread remains orthogonal', () => {
  const html = render('lifecycle', {
    schema_version: 1,
    diagram_type: 'lifecycle',
    meta: { title: 'Orthogonal same-band spread' },
    lanes: [{ id: 'main', label: 'Main' }],
    states: [
      { id: 'hub', type: 'active', label: 'Hub', lane: 'main', col: 0 },
      { id: 'upper', type: 'waiting', label: 'Upper', lane: 'main', col: 2, yOffset: -50 },
      { id: 'lower', type: 'success', label: 'Lower', lane: 'main', col: 4, yOffset: 50 },
    ],
    transitions: [
      { id: 'to-upper', from: 'hub', to: 'upper' },
      { id: 'to-lower', from: 'hub', to: 'lower' },
    ],
  });

  assert.deepEqual(connectionPoints(html, 'to-upper'), [
    [153, 150], [248, 150], [248, 107], [343, 107],
  ]);
  assert.deepEqual(connectionPoints(html, 'to-lower'), [
    [153, 164], [402, 164], [402, 207], [651, 207],
  ]);
});

test('skill and READMEs describe automatic port spread as bounded default behavior', () => {
  const skill = fs.readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8');
  assert.match(skill, /Automatic Port Spread is a default renderer behavior/);
  assert.match(skill, /single relationship|single relationships/);
  assert.match(skill, /explicit `via`.*`channelX`.*`channelY`.*`labelAt`/);
  assert.match(skill, /facing automatic ports \(`left`\/`right` or `top`\/`bottom`\).*one shared axis/);

  const authoringContract = fs.readFileSync(path.join(skillRoot, 'references/authoring-contract.md'), 'utf8');
  assert.match(authoringContract, /unobstructed facing ports.*may share one horizontal or vertical axis/);

  const repoRoot = path.resolve(skillRoot, '..');
  for (const file of ['README.md', 'README_EN.md']) {
    assert.match(fs.readFileSync(path.join(repoRoot, file), 'utf8'), /shared automatic endpoints spread deterministically/);
  }
  assert.match(fs.readFileSync(path.join(repoRoot, 'README_ZH.md'), 'utf8'), /共享的自动端点会确定性展开/);
});
```

## test/base-input-compatibility.test.mjs

```js
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-base-input-compatibility-'));

function renderBaseFixture(type, name) {
  const input = path.join(skillRoot, 'test', 'fixtures', 'v1-baseline', name);
  const output = path.join(tmp, `${name}.html`);
  return spawnSync(process.execPath, [
    path.join(skillRoot, 'bin', 'archify.mjs'),
    'render',
    type,
    input,
    output,
  ], {
    cwd: skillRoot,
    encoding: 'utf8',
  });
}

test('base named-route fixtures remain valid without redundant authored endpoint sides', () => {
  for (const [type, name] of [
    ['dataflow', 'event-stream.dataflow.json'],
    ['architecture', 'production-deployment.architecture.json'],
  ]) {
    const result = renderBaseFixture(type, name);
    assert.equal(
      result.status,
      0,
      `${type} base input must remain valid:\n${result.stdout || result.stderr}`,
    );
  }
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/brand-marks.test.mjs

```js
import assert from 'node:assert/strict';
import { spawn, spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import http from 'node:http';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { BRAND_MARKS } from '../renderers/shared/generated-brand-marks.mjs';
import { isPrivateBrandAddress, prepareDiagramBrandMarks } from '../renderers/shared/brand-marks.mjs';
import {
  THIRD_PARTY_NOTICE_DISCLOSURE_COUNT,
  validateThirdPartyNotices,
} from '../../scripts/third-party-notices-contract.mjs';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const cli = path.join(skillRoot, 'bin', 'archify.mjs');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-brand-marks-'));
const cases = {
  architecture: ['web-app.architecture.json', 'components'],
  workflow: ['agent-tool-call.workflow.json', 'nodes'],
  sequence: ['cache-miss-request.sequence.json', 'participants'],
  dataflow: ['product-analytics.dataflow.json', 'nodes'],
  lifecycle: ['agent-run.lifecycle.json', 'states'],
};

test('third-party notices cover every recorded individual mark license', () => {
  const notices = fs.readFileSync(path.join(skillRoot, 'THIRD_PARTY_NOTICES.md'), 'utf8');
  const licensedMarks = BRAND_MARKS.filter((mark) => mark.provenance?.license);

  assert.equal(THIRD_PARTY_NOTICE_DISCLOSURE_COUNT, 39, 'notice contract changed without review');
  assert.deepEqual(validateThirdPartyNotices(notices), { ok: true, missing: [] });
  assert.equal(licensedMarks.length, 8, 'pinned Simple Icons license inventory changed');
  for (const mark of licensedMarks) {
    assert.match(notices, new RegExp(`\\| ${mark.title.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')} \\|`));
    assert.ok(notices.includes(mark.provenance.source), `${mark.id} source must be disclosed`);
    assert.ok(notices.includes(mark.provenance.license.type), `${mark.id} license must be disclosed`);
  }
  assert.match(notices, /OpenAI brand guidelines/);
  assert.match(notices, /does not state or imply endorsement by OpenAI/);
  assert.match(notices, /does not imply sponsorship, endorsement, partnership/);
  assert.match(notices, /commercial, promotional, or redistributive use/);
  assert.match(notices, /does not grant rights\s+that Archify does not hold/);
});

function writeFixture(type, name, brand, customize) {
  const [example, collection] = cases[type];
  const value = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8'));
  value[collection][0].brand = brand;
  customize?.(value, value[collection][0]);
  const file = path.join(tmp, `${name}.${type}.json`);
  fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
  return file;
}

function renderSync(type, input, name, env = {}) {
  const output = path.join(tmp, `${name}.html`);
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${type}/render-${type}.mjs`),
    input,
    output,
  ], {
    cwd: skillRoot,
    encoding: 'utf8',
    env: { ...process.env, ...env },
  });
  return { result, output, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

function renderAsync(type, input, name, env = {}) {
  const output = path.join(tmp, `${name}.html`);
  return new Promise((resolve) => {
    const child = spawn(process.execPath, [
      path.join(skillRoot, `renderers/${type}/render-${type}.mjs`),
      input,
      output,
    ], {
      cwd: skillRoot,
      env: { ...process.env, ...env },
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    let stdout = '';
    let stderr = '';
    child.stdout.setEncoding('utf8');
    child.stderr.setEncoding('utf8');
    child.stdout.on('data', (chunk) => { stdout += chunk; });
    child.stderr.on('data', (chunk) => { stderr += chunk; });
    child.on('close', (status) => resolve({
      status,
      stdout,
      stderr,
      output,
      html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '',
    }));
  });
}

function runCliAsync(args, env = {}) {
  return new Promise((resolve) => {
    const child = spawn(process.execPath, [cli, ...args], {
      cwd: skillRoot,
      env: { ...process.env, ...env },
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    let stdout = '';
    let stderr = '';
    child.stdout.setEncoding('utf8');
    child.stderr.setEncoding('utf8');
    child.stdout.on('data', (chunk) => { stdout += chunk; });
    child.stderr.on('data', (chunk) => { stderr += chunk; });
    child.on('close', (status) => resolve({ status, stdout, stderr }));
  });
}

function nodeBlock(html, id) {
  const startToken = `<g id="node-${id}"`;
  const start = html.indexOf(startToken);
  if (start === -1) return '';
  const candidates = [
    html.indexOf('\n        <g id="node-', start + startToken.length),
    html.indexOf('\n        <!-- Connection labels', start + startToken.length),
    html.indexOf('\n        <!-- Transition labels', start + startToken.length),
    html.indexOf('\n        <!-- Message labels', start + startToken.length),
  ].filter((value) => value !== -1);
  return html.slice(start, candidates.length ? Math.min(...candidates) : html.length);
}

test('generated catalog exposes a substantial, unique, provenance-backed preset library', () => {
  assert.equal(BRAND_MARKS.length, 107);
  assert.equal(new Set(BRAND_MARKS.map((mark) => mark.id)).size, BRAND_MARKS.length);
  for (const mark of BRAND_MARKS) {
    assert.match(mark.id, /^[a-z0-9]+(?:-[a-z0-9]+)*$/);
    assert.ok(mark.title);
    assert.ok(mark.category);
    assert.match(mark.hex, /^[0-9A-F]{6}$/i);
    assert.match(mark.path, /^[Mm]/);
    assert.ok(mark.provenance?.source);
  }
});

test('brand discovery resolves model names, aliases, domains, and Chinese channel aliases', () => {
  for (const [query, expected] of [
    ['GPT', 'openai'],
    ['Gemini', 'google-gemini'],
    ['github.com', 'github'],
    ['微信', 'wechat'],
  ]) {
    const result = spawnSync(process.execPath, [cli, 'brands', query, '--json'], {
      cwd: skillRoot,
      encoding: 'utf8',
    });
    assert.equal(result.status, 0, result.stderr);
    const receipt = JSON.parse(result.stdout);
    assert.equal(receipt.ok, true);
    assert.ok(receipt.marks.some((mark) => mark.id === expected), query);
  }
});

test('all five renderers keep the semantic sigil and add one export-safe brand badge', () => {
  for (const type of Object.keys(cases)) {
    const input = writeFixture(type, `preset-${type}`, 'openai', (_diagram, node) => {
      if (type === 'lifecycle') node.step = node.step || '01';
    });
    const { result, html } = renderSync(type, input, `preset-${type}`);
    assert.equal(result.status, 0, `${type}: ${result.stderr || result.stdout}`);
    assert.match(html, /data-node-brand="OpenAI"/i, type);
    assert.match(html, /data-brand-mark="openai"[^>]+data-brand-status="preset"/i, type);
    assert.match(html, /class="semantic-sigil /, type);
    assert.match(html, /<title>[^<]*OpenAI<\/title>/i, type);

    const [, collection] = cases[type];
    const diagram = JSON.parse(fs.readFileSync(input, 'utf8'));
    const block = nodeBlock(html, diagram[collection][0].id);
    const frame = block.match(/<rect x="([-\d.]+)" y="([-\d.]+)" width="([-\d.]+)" height="([-\d.]+)" rx="[^"]+" class="c-mask"\/>/);
    const semantic = block.match(/data-semantic-sigil[^>]+translate\(([-\d.]+) ([-\d.]+)\)/);
    const brand = block.match(/data-brand-mark="openai"[^>]+translate\(([-\d.]+) ([-\d.]+)\)">\s*<rect width="([-\d.]+)" height="([-\d.]+)" rx="([-\d.]+)" class="brand-mark-badge"\/>/);
    assert.ok(frame && semantic && brand, `${type}: expected node frame, semantic sigil, and brand badge`);

    const [frameX, frameY, frameWidth] = frame.slice(1, 4).map(Number);
    const [, semanticY] = semantic.slice(1, 3).map(Number);
    const [brandX, brandY, brandWidth, brandHeight, brandRadius] = brand.slice(1, 6).map(Number);
    assert.equal(brandWidth, 16, `${type}: brand badge width`);
    assert.equal(brandHeight, 16, `${type}: brand badge height`);
    assert.equal(brandRadius, 4, `${type}: brand badge radius`);
    assert.equal(brandY - frameY, 6, `${type}: brand badge top inset`);
    assert.equal(frameX + frameWidth - (brandX + brandWidth), 6, `${type}: brand badge right inset`);
    assert.equal(brandY, semanticY, `${type}: brand and semantic marks share a top rail`);
  }
});

test('a branded node fails before its semantic sigil, label, and brand badge can overlap', () => {
  const input = writeFixture('workflow', 'narrow-brand-rail', 'openai', (_diagram, node) => {
    node.label = 'A';
    delete node.sublabel;
    node.width = 32;
  });
  const { result, html } = renderSync('workflow', input, 'narrow-brand-rail');

  assert.equal(result.status, 1, result.stderr || result.stdout);
  assert.match(result.stderr, /brand top rail/i);
  assert.equal(html, '');
});

test('every renderer enforces the same collision-free brand top rail', () => {
  for (const type of ['architecture', 'sequence', 'dataflow', 'lifecycle']) {
    const input = writeFixture(type, `narrow-brand-rail-${type}`, 'openai', (diagram, node) => {
      node.label = type === 'sequence' ? 'ABCDEFGHI' : 'A';
      delete node.sublabel;
      delete node.tag;
      if (type === 'architecture') node.size = [32, 60];
      if (type === 'sequence') diagram.meta.column_fit = 'fixed';
      if (type === 'dataflow' || type === 'lifecycle') node.width = 48;
    });
    const { result, html } = renderSync(type, input, `narrow-brand-rail-${type}`);
    assert.equal(result.status, 1, `${type}: ${result.stderr || result.stdout}`);
    assert.match(result.stderr, /brand top rail/i, type);
    assert.equal(html, '', type);
  }
});

test('branded lifecycle states move the semantic stamp left and keep the brand at upper right', () => {
  const input = writeFixture('lifecycle', 'lifecycle-placement', 'openai', (_diagram, node) => {
    node.step = '01';
  });
  const { result, html } = renderSync('lifecycle', input, 'lifecycle-placement');
  assert.equal(result.status, 0, result.stderr || result.stdout);
  const id = JSON.parse(fs.readFileSync(input, 'utf8')).states[0].id;
  const block = nodeBlock(html, id);
  const semanticX = Number(block.match(/data-semantic-sigil[^>]+translate\(([-\d.]+)/)?.[1]);
  const brandX = Number(block.match(/data-brand-mark[^>]+translate\(([-\d.]+)/)?.[1]);
  assert.ok(Number.isFinite(semanticX) && Number.isFinite(brandX) && semanticX < brandX, block);
  assert.match(block, /data-detail="fine"[^>]+>01<\/text>/);
});

test('known-brand URLs use the bundled vector instead of the network', () => {
  const input = writeFixture('architecture', 'known-domain', 'https://github.com/tt-a1i/archify');
  const { result, html } = renderSync('architecture', input, 'known-domain');
  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.match(html, /data-brand-mark="github"[^>]+data-brand-status="preset"/);
  assert.doesNotMatch(html, /data-brand-status="captured"/);
});

test('unknown URL strings fail closed until an exact captured digest is authored', () => {
  const input = writeFixture('architecture', 'unpinned-link', 'https://brand.example.invalid/');
  const result = spawnSync(process.execPath, [cli, 'validate', 'architecture', input, '--json'], {
    cwd: skillRoot,
    encoding: 'utf8',
  });

  assert.equal(result.status, 1, result.stderr || result.stdout);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.ok, false);
  assert.ok(receipt.diagnostics.some((entry) => entry.code === 'brand/unpinned-url'));
  assert.ok(receipt.diagnostics.some((entry) => entry.supportedFixes.some((fix) => fix.includes('brands capture'))));
});

test('capture command returns a digest-pinned brand object that renders reproducibly', async () => {
  let pageHits = 0;
  let iconHits = 0;
  const icon = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
  const server = http.createServer((request, response) => {
    if (request.url === '/mark.png') {
      iconHits += 1;
      response.writeHead(200, { 'content-type': 'image/png' });
      response.end(icon);
      return;
    }
    pageHits += 1;
    response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
    response.end('<!doctype html><title>Example Studio</title><link rel="icon" type="image/png" href="/mark.png"><h1>Example Studio</h1>');
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const url = `http://127.0.0.1:${address.port}/studio`;
    const capture = await runCliAsync(['brands', 'capture', url, '--json'], { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' });
    assert.equal(capture.status, 0, capture.stderr || capture.stdout);
    const receipt = JSON.parse(capture.stdout);
    assert.equal(receipt.ok, true);
    assert.deepEqual(receipt.brand, {
      url,
      sha256: createHash('sha256').update(icon).digest('hex'),
    });

    const input = writeFixture('architecture', 'captured-link', receipt.brand);
    const rendered = await renderAsync('architecture', input, 'captured-link', { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' });
    assert.equal(rendered.status, 0, rendered.stderr || rendered.stdout);
    assert.equal(pageHits, 2);
    assert.equal(iconHits, 2);
    assert.match(rendered.html, /data-brand-status="captured"/);
    assert.match(rendered.html, /data:image\/png;base64,/);
    assert.match(rendered.html, new RegExp(`data-brand-sha256="${receipt.brand.sha256}"`));
    assert.ok(!rendered.html.includes('http://127.0.0.1') || rendered.html.includes('data-node-brand-source='));
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('a pinned brand fails closed when the remote icon digest changes', async () => {
  const firstIcon = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
  const changedIcon = Buffer.from(firstIcon);
  changedIcon[45] ^= 1;
  let iconHits = 0;
  const server = http.createServer((request, response) => {
    if (request.url === '/mark.png') {
      iconHits += 1;
      response.writeHead(200, { 'content-type': 'image/png' });
      response.end(iconHits === 1 ? firstIcon : changedIcon);
      return;
    }
    response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
    response.end('<!doctype html><title>Changing site</title><link rel="icon" type="image/png" href="/mark.png">');
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const url = `http://127.0.0.1:${address.port}/`;
    const capture = await runCliAsync(['brands', 'capture', url, '--json'], { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' });
    assert.equal(capture.status, 0, capture.stderr || capture.stdout);
    const brand = JSON.parse(capture.stdout).brand;
    const input = writeFixture('architecture', 'changed-digest', brand);
    const result = await runCliAsync(['validate', 'architecture', input, '--json'], { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' });
    assert.equal(result.status, 1, result.stderr || result.stdout);
    const receipt = JSON.parse(result.stdout);
    assert.equal(receipt.diagnostics.filter((entry) => entry.code === 'brand/digest-mismatch').length, 1);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('a pinned brand keeps identical artifact metadata when the remote page title changes', async () => {
  const icon = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
  let pageHits = 0;
  const server = http.createServer((request, response) => {
    if (request.url === '/mark.png') {
      response.writeHead(200, { 'content-type': 'image/png' });
      response.end(icon);
      return;
    }
    pageHits += 1;
    response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
    response.end(`<!doctype html><title>Title ${pageHits}</title><link rel="icon" type="image/png" href="/mark.png">`);
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const url = `http://127.0.0.1:${address.port}/`;
    const capture = await runCliAsync(['brands', 'capture', url, '--json'], { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' });
    assert.equal(capture.status, 0, capture.stderr || capture.stdout);
    const input = writeFixture('architecture', 'stable-title', JSON.parse(capture.stdout).brand);
    const first = await renderAsync('architecture', input, 'stable-title-first', { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' });
    const second = await renderAsync('architecture', input, 'stable-title-second', { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' });
    assert.equal(first.status, 0, first.stderr || first.stdout);
    assert.equal(second.status, 0, second.stderr || second.stdout);
    assert.equal(first.html, second.html);
    assert.match(first.html, /data-brand-title="127\.0\.0\.1"/);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('each prepare call rechecks pinned remote bytes instead of trusting a process-wide cache', async () => {
  const firstIcon = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
  const changedIcon = Buffer.from(firstIcon);
  changedIcon[45] ^= 1;
  let iconHits = 0;
  const server = http.createServer((request, response) => {
    if (request.url === '/mark.png') {
      iconHits += 1;
      response.writeHead(200, { 'content-type': 'image/png' });
      response.end(iconHits === 1 ? firstIcon : changedIcon);
      return;
    }
    response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
    response.end('<!doctype html><title>Changing site</title><link rel="icon" type="image/png" href="/mark.png">');
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  const priorAllowPrivate = process.env.ARCHIFY_BRAND_ALLOW_PRIVATE;
  process.env.ARCHIFY_BRAND_ALLOW_PRIVATE = '1';
  try {
    const address = server.address();
    const diagram = {
      components: [{
        id: 'remote',
        label: 'Remote',
        brand: {
          url: `http://127.0.0.1:${address.port}/`,
          sha256: createHash('sha256').update(firstIcon).digest('hex'),
        },
      }],
    };
    await prepareDiagramBrandMarks('architecture', diagram);
    await assert.rejects(
      prepareDiagramBrandMarks('architecture', diagram),
      /brand digest changed/i,
    );
    assert.equal(iconHits, 2);
  } finally {
    if (priorAllowPrivate === undefined) delete process.env.ARCHIFY_BRAND_ALLOW_PRIVATE;
    else process.env.ARCHIFY_BRAND_ALLOW_PRIVATE = priorAllowPrivate;
    await new Promise((resolve) => server.close(resolve));
  }
});

test('capture blocks IPv4-mapped IPv6 loopback and metadata destinations before connecting', async () => {
  for (const url of [
    'http://[::ffff:127.0.0.1]/',
    'http://[::ffff:169.254.169.254]/',
    'http://[::192.168.1.1]/',
    'http://[64:ff9b::c0a8:101]/',
    'http://[2002:c0a8:0101::]/',
    'http://[ff02::1]/',
    'http://192.0.2.1/',
    'http://198.51.100.1/',
    'http://203.0.113.1/',
  ]) {
    const capture = await runCliAsync(['brands', 'capture', url, '--json']);
    assert.notEqual(capture.status, 0, `${url}: ${capture.stderr || capture.stdout}`);
    assert.match(capture.stderr, /private brand links are not fetched/i, url);
  }
});

test('address classification blocks exact reserved ranges without rejecting adjacent public IPv4 space', () => {
  for (const address of ['192.0.2.1', '192.88.99.1', '198.51.100.1', '203.0.113.1']) {
    assert.equal(isPrivateBrandAddress(address), true, address);
  }
  for (const address of ['192.2.1.1', '192.88.98.1', '198.51.99.1', '203.0.112.1']) {
    assert.equal(isPrivateBrandAddress(address), false, address);
  }
});

test('capture requires the standard port for the selected web protocol', async () => {
  for (const url of [
    'http://brand.example.invalid:443/',
    'https://brand.example.invalid:80/',
    'https://github.com:80/',
  ]) {
    const capture = await runCliAsync(['brands', 'capture', url, '--json']);
    assert.notEqual(capture.status, 0, capture.stdout);
    assert.match(capture.stderr, /standard web port/i, url);
  }
});

test('capture rejects credentials even when the URL domain matches a bundled preset', async () => {
  const capture = await runCliAsync(['brands', 'capture', 'https://user:secret@github.com/', '--json']);
  assert.notEqual(capture.status, 0, capture.stdout);
  assert.match(capture.stderr, /cannot contain credentials/i);
});

test('rendering many pinned brands limits concurrent remote capture work', async () => {
  const icon = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
  const sha256 = createHash('sha256').update(icon).digest('hex');
  let active = 0;
  let maximumActive = 0;
  const server = http.createServer((request, response) => {
    active += 1;
    maximumActive = Math.max(maximumActive, active);
    setTimeout(() => {
      if (request.url.endsWith('.png')) {
        response.writeHead(200, { 'content-type': 'image/png' });
        active -= 1;
        response.end(icon);
      } else {
        const suffix = request.url.replace(/^\/site-/, '');
        response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
        active -= 1;
        response.end(`<!doctype html><title>Site ${suffix}</title><link rel="icon" type="image/png" href="/mark-${suffix}.png">`);
      }
    }, 40);
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const input = writeFixture('architecture', 'bounded-capture', 'openai', (diagram) => {
      diagram.components.forEach((node, index) => {
        node.brand = {
          url: `http://127.0.0.1:${address.port}/site-${index}`,
          sha256,
        };
      });
    });
    const rendered = await renderAsync('architecture', input, 'bounded-capture', { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' });
    assert.equal(rendered.status, 0, rendered.stderr || rendered.stdout);
    assert.ok(maximumActive <= 3, `expected at most 3 concurrent requests, observed ${maximumActive}`);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('rendering many pinned brands shares one diagram capture deadline', async () => {
  const icon = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
  const sha256 = createHash('sha256').update(icon).digest('hex');
  const server = http.createServer((_request, response) => {
    setTimeout(() => {
      response.writeHead(200, { 'content-type': 'image/png' });
      response.end(icon);
    }, 80);
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const input = writeFixture('architecture', 'diagram-deadline', 'openai', (diagram) => {
      diagram.components.forEach((node, index) => {
        node.brand = {
          url: `http://127.0.0.1:${address.port}/mark-${index}.png`,
          sha256,
        };
      });
    });
    const rendered = await renderAsync('architecture', input, 'diagram-deadline', {
      ARCHIFY_BRAND_ALLOW_PRIVATE: '1',
      ARCHIFY_BRAND_CAPTURE_TIMEOUT_MS: '100',
    });
    assert.notEqual(rendered.status, 0, rendered.stdout);
    assert.match(rendered.stderr, /abort|timed? ?out|timeout/i);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('capture applies one total deadline across the page and icon requests', async () => {
  const icon = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
  const server = http.createServer((request, response) => {
    setTimeout(() => {
      if (request.url === '/mark.png') {
        response.writeHead(200, { 'content-type': 'image/png' });
        response.end(icon);
      } else {
        response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
        response.end('<!doctype html><title>Slow site</title><link rel="icon" type="image/png" href="/mark.png">');
      }
    }, 100);
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const capture = await runCliAsync(
      ['brands', 'capture', `http://127.0.0.1:${address.port}/`, '--json'],
      {
        ARCHIFY_BRAND_ALLOW_PRIVATE: '1',
        ARCHIFY_BRAND_CAPTURE_TIMEOUT_MS: '150',
      },
    );
    assert.notEqual(capture.status, 0, capture.stdout);
    assert.match(capture.stderr, /abort|timed? ?out|timeout/i);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('capture rejects remote SVG even when the document appears passive', async () => {
  const server = http.createServer((request, response) => {
    if (request.url === '/mark.svg') {
      response.writeHead(200, { 'content-type': 'image/svg+xml' });
      response.end('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="24" height="24"/></svg>');
      return;
    }
    if (request.url === '/favicon.ico') {
      response.writeHead(404);
      response.end();
      return;
    }
    response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
    response.end('<!doctype html><title>SVG mark</title><link rel="icon" type="image/svg+xml" href="/mark.svg">');
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const capture = await runCliAsync(
      ['brands', 'capture', `http://127.0.0.1:${address.port}/studio`, '--json'],
      { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' },
    );
    assert.notEqual(capture.status, 0, capture.stdout);
    assert.match(capture.stderr, /unsupported brand image type image\/svg\+xml/i);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('unsupported SVG declarations cannot crowd out the favicon fallback', async () => {
  const icon = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
  let fallbackHits = 0;
  const server = http.createServer((request, response) => {
    if (request.url === '/favicon.ico') {
      fallbackHits += 1;
      response.writeHead(200, { 'content-type': 'image/png' });
      response.end(icon);
      return;
    }
    if (request.url?.endsWith('.svg')) {
      response.writeHead(200, { 'content-type': 'image/svg+xml' });
      response.end('<svg xmlns="http://www.w3.org/2000/svg"/>');
      return;
    }
    response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
    response.end(`<!doctype html><title>Fallback mark</title>${Array.from(
      { length: 6 },
      (_, index) => `<link rel="icon" type="image/svg+xml" href="/mark-${index}.svg">`,
    ).join('')}`);
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const capture = await runCliAsync(
      ['brands', 'capture', `http://127.0.0.1:${address.port}/`, '--json'],
      { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' },
    );
    assert.equal(capture.status, 0, capture.stderr || capture.stdout);
    const receipt = JSON.parse(capture.stdout);
    assert.equal(receipt.evidence.contentType, 'image/png');
    assert.equal(fallbackHits, 1);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('capture rejects an image whose bytes do not match its declared media type', async () => {
  const server = http.createServer((_request, response) => {
    response.writeHead(200, { 'content-type': 'image/png' });
    response.end('<html>not a png</html>');
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const capture = await runCliAsync(
      ['brands', 'capture', `http://127.0.0.1:${address.port}/mark.png`, '--json'],
      { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' },
    );
    assert.notEqual(capture.status, 0, capture.stdout);
    assert.match(capture.stderr, /do(?:es)? not match image\/png/i);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('capture rejects a truncated PNG that contains only its signature', async () => {
  const server = http.createServer((_request, response) => {
    response.writeHead(200, { 'content-type': 'image/png' });
    response.end(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  try {
    const address = server.address();
    const capture = await runCliAsync(
      ['brands', 'capture', `http://127.0.0.1:${address.port}/mark.png`, '--json'],
      { ARCHIFY_BRAND_ALLOW_PRIVATE: '1' },
    );
    assert.notEqual(capture.status, 0, capture.stdout);
    assert.match(capture.stderr, /do(?:es)? not match image\/png/i);
  } finally {
    await new Promise((resolve) => server.close(resolve));
  }
});

test('unknown preset names fail with a repairable public CLI diagnostic', () => {
  const input = writeFixture('architecture', 'unknown-preset', 'open-aii');
  const result = spawnSync(process.execPath, [cli, 'validate', 'architecture', input, '--json'], {
    cwd: skillRoot,
    encoding: 'utf8',
  });
  assert.equal(result.status, 1, result.stderr || result.stdout);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.ok, false);
  assert.ok(receipt.diagnostics.some((entry) => entry.code === 'brand/unknown'));
  assert.ok(receipt.diagnostics.some((entry) => entry.supportedFixes.some((fix) => fix.includes('archify brands'))));
});

test('viewer exposes brand identity to Passport and Finder while keeping source beacons clear', () => {
  const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
  assert.match(template, /id="focus-brand" data-passport="brand" hidden/);
  assert.match(template, /node\.getAttribute\('data-node-brand'\)/);
  assert.match(template, /brandOffset = node\.hasAttribute\('data-node-brand'\) \? 24 : 0/);
  assert.match(template, /sourceSearch \+ ' ' \+ text\)\.toLowerCase\(\) \+ ' ' \+ brand\.toLowerCase\(\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/chapter-delta-preview.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-chapter-delta-preview-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

const PROOF_CASES = [
  'agent-tool-call.workflow.json',
  'production-deployment.architecture.json',
  'cache-miss-request.sequence.json',
  'release-delivery.workflow.json',
  'incident-response.workflow.json',
  'product-analytics.dataflow.json',
  'async-job-roundtrip.sequence.json',
  'event-stream.dataflow.json',
  'agent-run.lifecycle.json',
  'deployment-release.lifecycle.json',
  'web-app.architecture.json',
];

function render(mode) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', CASES[mode]),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

function delta(previous, destination) {
  const previousIds = new Set(previous);
  const destinationIds = new Set(destination);
  return {
    stay: previous.filter((id) => destinationIds.has(id)),
    enter: destination.filter((id) => !previousIds.has(id)),
    leave: previous.filter((id) => !destinationIds.has(id)),
  };
}

test('all five renderers inherit one viewer-only static Chapter Delta Preview', () => {
  for (const mode of Object.keys(CASES)) {
    const html = render(mode);
    assert.match(html, /function chapterDelta\(previous, destination\)/, mode);
    assert.match(html, /className = 'guided-view-chapter-delta'/, mode);
    assert.match(html, /svg\.setAttribute\('data-chapter-preview', views\[index\]\.id\)/, mode);
    assert.match(html, /data-chapter-preview-role/, mode);
    assert.match(html, /transition: none !important/, mode);
    assert.doesNotMatch(canonicalSvg(html), /data-chapter-preview|data-chapter-preview-role/, mode);
  }
});

test('exact stable-ID set math powers truthful counts and the existing handoff', () => {
  const html = render('workflow');
  assert.match(html, /stay: previousFocus\.filter\(function \(id\) \{ return destinationIds\[id\]; \}\)/);
  assert.match(html, /enter: destinationFocus\.filter\(function \(id\) \{ return !previousIds\[id\]; \}\)/);
  assert.match(html, /leave: previousFocus\.filter\(function \(id\) \{ return !destinationIds\[id\]; \}\)/);
  assert.match(html, /var delta = chapterDelta\(previous, destination\);[\s\S]*?chapterAnchor\(previous, destination, outgoingBeatIndex, delta\)/);
  assert.match(html, /var compact = '=' \+ delta\.stay\.length \+ ' \+' \+ delta\.enter\.length \+ ' \\u2212' \+ delta\.leave\.length/);
  assert.match(html, /viewerText\('viewer\.guided\.chapter\.delta\.aria'/);
  assert.doesNotMatch(html, /inferChapterDelta|matchChapterLabel|nearestKind/);

  let adjacent = 0;
  let shared = 0;
  for (const file of PROOF_CASES) {
    const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', file), 'utf8'));
    const views = doc.meta.views;
    for (let index = 1; index < views.length; index += 1) {
      adjacent += 1;
      if (delta(views[index - 1].focus, views[index].focus).stay.length) shared += 1;
    }
  }
  assert.equal(adjacent, 22);
  assert.equal(shared, 19);

  const workflow = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', CASES.workflow), 'utf8'));
  const [first, second, third] = workflow.meta.views;
  assert.deepEqual(delta(first.focus, second.focus), {
    stay: ['router', 'approval'],
    enter: ['blocked', 'retry'],
    leave: ['user', 'chat', 'planner', 'tool', 'external', 'final'],
  });
  assert.deepEqual(delta(second.focus, third.focus), {
    stay: [],
    enter: ['external', 'store', 'trace'],
    leave: ['router', 'approval', 'blocked', 'retry'],
  });
});

test('pointer and keyboard inspect while touch and native activation still commit directly', () => {
  const html = render('architecture');
  assert.match(html, /chapterList\.addEventListener\('pointerover'/);
  assert.match(html, /!hoverCapable\(\) \|\| event\.pointerType === 'touch'/);
  assert.match(html, /setChapterPreviewIntent\('pointer', chapterButtons\.indexOf\(button\)\)/);
  assert.match(html, /chapterIndex\.addEventListener\('focusin',[\s\S]*?setChapterPreviewIntent\('focus'/);
  assert.match(html, /event\.key === 'Escape' && activePreviewIndex >= 0[\s\S]*?event\.stopImmediatePropagation\(\)[\s\S]*?clearChapterPreview\(\{ clearIntents: true \}\)/);
  assert.match(html, /chapterList\.addEventListener\('click',[\s\S]*?activateById/);
  assert.match(html, /button\.type = 'button'/);
  assert.doesNotMatch(html, /firstTap|secondTap|longpress|long-press/);

  const previewRuntime = html.slice(
    html.indexOf('function chapterPreviewBlocked()'),
    html.indexOf('function sharePlaybackRequested()'),
  );
  assert.doesNotMatch(previewRuntime, /Archify\.view\.|updateUrl\(|Archify\.focus\.|renderStoryTrail\(/);
});

test('latest intent, stronger owners, playback, and lifecycle cleanup remain bounded', () => {
  const html = render('lifecycle');
  assert.match(html, /var previewGeneration = 0/);
  assert.match(html, /right\.generation - left\.generation/);
  assert.match(html, /\[pointerPreviewIntent, focusPreviewIntent\]/);
  assert.match(html, /document\.hidden \|\| playing \|\| currentHandoff/);
  assert.match(html, /data-route-picking'[\s\S]*?data-route-active/);
  assert.match(html, /data-lens-active'[\s\S]*?data-legend-preview-active/);
  assert.match(html, /data-relationship-preview-active'[\s\S]*?data-intent-trace-active/);
  assert.match(html, /if \(playing\) pausePlayback\(\)/);
  assert.match(html, /Archify\.motionGovernor\.claim\('chapter-preview'/);
  assert.match(html, /Archify\.motionGovernor\.release\(token\)/);
  assert.match(html, /handoff\.resolve[\s\S]*?syncChapterPreview\(\)/);
  assert.match(html, /visibilitychange'[\s\S]*?clearChapterPreview/);
  assert.match(html, /beforeprint'[\s\S]*?clearChapterPreview[\s\S]*?settleHandoff/);
});

test('mobile, Still, embed, print, and canonical exports keep the preview viewer-only', () => {
  const html = render('sequence');
  assert.match(html, /\.guided-view-chapter \{ min-height: 2\.75rem; \}/);
  assert.match(html, /flex: 0 0 min\(14rem, 78vw\)/);
  assert.match(html, /\.guided-view-chapter-delta\[hidden\] \{ display: none; \}/);
  assert.match(html, /document\.documentElement\.getAttribute\('data-embed'\) === 'true'/);
  assert.match(html, /prefers-reduced-motion: reduce[\s\S]*?svg\[data-chapter-preview\]/);
  assert.match(html, /svg\[data-chapter-preview\] \[data-node-id\],[\s\S]*?opacity: 1 !important; filter: none !important/);
  assert.match(html, /clone\.removeAttribute\('data-chapter-preview'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-chapter-preview-role\]'\)/);
  assert.match(html, /!clone\.hasAttribute\('data-chapter-preview'\)/);
  assert.match(html, /canonicalStateClean/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/chapter-handoff.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-chapter-handoff-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', CASES[mode]),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one viewer-only Shared Anchor Chapter Handoff', () => {
  for (const mode of Object.keys(CASES)) {
    const html = render(mode);
    assert.match(html, /id="guided-view-handoff" hidden aria-hidden="true"/, mode);
    assert.match(html, /function beginHandoff\(previousIndex, nextIndex, previous, destination, outgoingBeatIndex, reason\)/, mode);
    assert.match(html, /data-chapter-handoff-overlay/, mode);
    assert.match(html, /ring\.setAttribute\('class', 'chapter-handoff-anchor'\)/, mode);
    assert.doesNotMatch(canonicalSvg(html), /data-chapter-handoff|data-chapter-role|chapter-handoff-anchor/, mode);
  }
});

test('anchor selection uses exact stable-id intersection with deterministic outgoing priority', () => {
  const html = render('workflow');
  assert.match(html, /function chapterDelta\(previous, destination\)/);
  assert.match(html, /function chapterAnchor\(previous, destination, outgoingBeatIndex, delta\)/);
  assert.match(html, /delta\.stay\.forEach\(function \(id\) \{ stayIds\[id\] = true; \}\)/);
  assert.match(html, /var activeBeat = outgoingBeatIndex >= 0 \? previous\.focus\[outgoingBeatIndex\] : ''/);
  assert.match(html, /activeBeat && stayIds\[activeBeat\]/);
  assert.match(html, /for \(var index = previous\.focus\.length - 1; index >= 0; index -= 1\)/);
  assert.match(html, /if \(stayIds\[previous\.focus\[index\]\]\) return previous\.focus\[index\]/);
  assert.doesNotMatch(html, /inferChapterAnchor|matchChapterLabel|nearestKind/);
});

test('handoff holds one truthful anchor then settles through one finite camera transaction', () => {
  const html = render('architecture');
  assert.match(html, /handoff\.mode = 'settling'/);
  assert.match(html, /setTimeout\(startCamera, 110\)/);
  assert.match(html, /duration: 420/);
  assert.match(html, /requestAnimationFrame\(step\)/);
  assert.match(html, /var eased = 1 - Math\.pow\(1 - fraction, 3\)/);
  assert.match(html, /Archify\.motionGovernor\.claim\('handoff'/);
  assert.match(html, /Archify\.motionGovernor\.release\(handoff\.ownerToken\)/);
  assert.match(html, /handoffReceipt\.textContent = viewerText\('viewer\.guided\.handoff'/);
  assert.doesNotMatch(html, /chapter-handoff[^\n]+infinite/);
});

test('latest intent, manual takeover, Still, reduced motion, and hidden pages cleanly settle', () => {
  const html = render('lifecycle');
  assert.match(html, /cancelHandoff\('replaced'\)/);
  assert.match(html, /cameraTransaction\.cancel\(reason \|\| 'cancelled', commitTarget === true\)/);
  assert.match(html, /transaction\.settled/);
  assert.match(html, /currentHandoff !== handoff/);
  assert.match(html, /Archify\.guidedViews\.cancelHandoff\(reason \|\| 'manual'\)/);
  assert.match(html, /settleHandoff\(systemPaused \? 'reduced-motion' : \(hasSuspension\(\) \? 'hidden' : 'still'\)\)/);
  assert.match(html, /settleHandoff\('hidden'\)/);
  assert.match(html, /settleHandoff\('reduced-motion'\)/);
  assert.match(html, /window\.addEventListener\('beforeprint',[\s\S]*?clearChapterPreview[\s\S]*?settleHandoff\('print'\)/);
});

test('mobile, embed, print, and canonical exports keep strict static boundaries', () => {
  const html = render('sequence');
  assert.match(html, /document\.documentElement\.getAttribute\('data-embed'\) === 'true'/);
  assert.match(html, /cameraReceipt\(\{ scrollLeft: target \}/);
  assert.match(html, /behavior: instant \? 'auto' : 'smooth'/);
  assert.match(html, /\.chapter-handoff-overlay \{ display: none !important; \}/);
  assert.match(html, /clone\.removeAttribute\('data-chapter-handoff'\)/);
  assert.match(html, /clone\.removeAttribute\('data-chapter-anchor'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-chapter-handoff-overlay\]'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-chapter-role\]'\)/);
  assert.match(html, /!clone\.hasAttribute\('data-chapter-handoff'\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/chapter-rail.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-chapter-rail-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', CASES[mode]),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all guided renderers expose one runtime-built named chapter rail', () => {
  for (const mode of Object.keys(CASES)) {
    const html = render(mode);
    assert.match(html, /<nav class="guided-view-index" id="guided-view-index" aria-label="Story chapters">/, mode);
    assert.match(html, /<ol class="guided-view-chapters" id="guided-view-chapters"><\/ol>/, mode);
    assert.match(html, /function buildChapterIndex\(\)/, mode);
    assert.match(html, /views\.forEach\(function \(view, index\)/, mode);
    assert.match(html, /position\.textContent = \(index \+ 1 < 10 \? '0' : ''\) \+ \(index \+ 1\)/, mode);
    assert.match(html, /title\.textContent = view\.label/, mode);
    assert.match(html, /stops\.textContent = viewerCount\('viewer\.guided\.chapter\.stop', view\.focus\.length\)/, mode);
    assert.doesNotMatch(canonicalSvg(html), /guided-view-chapter|data-chapter-position/, mode);
  }
});

test('chapter rail delegates selection and mirrors the existing activeIndex owner', () => {
  const html = render('architecture');
  assert.match(html, /activateById\(button\.getAttribute\('data-guided-view-id'\)\)/);
  assert.match(html, /var current = index === activeIndex/);
  assert.match(html, /activeIndex < 0 \? 'available' : \(current \? 'current' : \(index < activeIndex \? 'before' : 'after'\)\)/);
  assert.match(html, /button\.setAttribute\('aria-current', 'step'\)/);
  assert.match(html, /button\.removeAttribute\('aria-current'\)/);
  assert.match(html, /syncChapterIndex\(\);[\s\S]*renderShareCue\(\)/);
  assert.doesNotMatch(html, /selectedChapter|visitedChapters|completedChapters/);
});

test('chapter rail is keyboard-first and pauses playback on reader takeover', () => {
  const html = render('workflow');
  assert.match(html, /chapterIndex\.addEventListener\('focusin',[\s\S]*if \(playing\) pausePlayback\(\)/);
  assert.match(html, /event\.key === 'ArrowRight'/);
  assert.match(html, /event\.key === 'ArrowLeft'/);
  assert.match(html, /event\.key === 'Home'/);
  assert.match(html, /event\.key === 'End'/);
  assert.match(html, /focusChapterButton\(target\)/);
  assert.match(html, /button\.type = 'button'/);
  assert.doesNotMatch(html, /role="tab"|role="tabpanel"/);
});

test('chapter rail has positional, touch, mobile, motion, embed, and print boundaries', () => {
  const html = render('lifecycle');
  assert.match(html, /\.guided-view-chapter\[data-chapter-position="current"\][\s\S]*border: 2px solid/);
  assert.match(html, /data-chapter-position="before"[\s\S]*border-style: solid/);
  assert.match(html, /data-chapter-position="after"[\s\S]*border-style: dashed/);
  assert.match(html, /\.guided-view-chapter \{ min-height: 2\.75rem; \}/);
  assert.match(html, /scroll-snap-type: x proximity/);
  assert.match(html, /flex: 0 0 min\(14rem, 78vw\)/);
  assert.match(html, /behavior: 'auto'/);
  assert.match(html, /prefers-reduced-motion: reduce[\s\S]*\.guided-view-chapter \{ transition: none !important; \}/);
  assert.match(html, /html\[data-embed="true"\] \.guided-views \{ display: none !important; \}/);
  assert.match(html, /\.toolbar, \.diagram-nav, \.focus-chip, \.guided-views, \.archify-toast, \.no-print \{ display: none !important; \}/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/checkout-line-endings.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const repoRoot = fileURLToPath(new URL('../../', import.meta.url));

for (const autocrlf of ['true', 'input', 'false']) {
  test(`Git checkout preserves committed text and binary bytes with core.autocrlf=${autocrlf}`, () => {
    const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-checkout-eol-'));
    const source = path.join(fixture, 'source');
    const checkout = path.join(fixture, 'checkout');
    // Ignore caller Git configuration, attributes, repository paths, and signing hooks.
    const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith('GIT_')));
    Object.assign(env, {
      GIT_CONFIG_NOSYSTEM: '1',
      GIT_CONFIG_GLOBAL: os.devNull,
      GIT_ATTR_NOSYSTEM: '1',
    });
    const runGit = (cwd, args) => {
      const result = spawnSync('git', ['-c', `core.attributesFile=${os.devNull}`, ...args], {
        cwd, env, timeout: 30_000,
      });
      assert.equal(result.status, 0, `git ${args.join(' ')}: ${result.error || result.stderr}`);
      return result.stdout;
    };

    try {
      fs.mkdirSync(source);
      runGit(source, ['init', '--quiet', '--template=']);
      runGit(source, ['config', 'core.autocrlf', 'false']);
      runGit(source, ['config', 'user.name', 'Archify Test']);
      runGit(source, ['config', 'user.email', 'archify@example.invalid']);
      const files = new Map([
        ['.gitattributes', fs.readFileSync(path.join(repoRoot, '.gitattributes'))],
        ['README.md', Buffer.from('# Checkout proof\n文本 stays LF.\n')],
        ['archify/bin/example.mjs', Buffer.from('export const value = 1;\n')],
        ['examples/proof.html', Buffer.from('<!doctype html>\n<p>diagram</p>\n')],
        ['scripts/proof.sh', Buffer.from('#!/bin/sh\nprintf "proof"\n')],
        // NUL identifies binary data; embedded CRLF must remain byte-for-byte intact.
        ['archify.zip', Buffer.from([0x50, 0x4b, 3, 4, 0, 13, 10, 0xff])],
        ['docs/assets/proof.png', Buffer.from([0x89, 0x50, 0x4e, 0x47, 13, 10, 0, 0xff])],
      ]);
      for (const [relative, bytes] of files) {
        const target = path.join(source, relative);
        fs.mkdirSync(path.dirname(target), { recursive: true });
        fs.writeFileSync(target, bytes);
      }
      runGit(source, ['add', '--all']);
      runGit(source, ['-c', 'commit.gpgSign=false', 'commit', '--quiet', '-m', 'checkout fixture']);
      runGit(fixture, ['clone', '--quiet', '--no-checkout', '--no-hardlinks', '--template=', source, checkout]);
      runGit(checkout, ['config', 'core.autocrlf', autocrlf]);
      runGit(checkout, ['config', 'core.eol', 'crlf']);
      runGit(checkout, ['checkout', '--quiet', '--detach', 'HEAD']);

      for (const [relative, expected] of files) {
        const committed = runGit(checkout, ['show', `HEAD:${relative}`]);
        assert.deepEqual(committed, expected, `${relative}: staging must not rewrite fixture bytes`);
        assert.deepEqual(fs.readFileSync(path.join(checkout, relative)), committed,
          `${relative}: checkout bytes must equal committed bytes`);
      }
      assert.equal(runGit(checkout, ['status', '--porcelain']).toString().trim(), '');
      assert.equal(runGit(checkout, ['check-attr', 'linguist-generated', '--', 'examples/proof.html'])
        .toString().trim(), 'examples/proof.html: linguist-generated: true');
    } finally {
      fs.rmSync(fixture, { recursive: true, force: true });
    }
  });
}
```

## test/clean-skill-staging.test.mjs

```js
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';

import { stageCleanSkill } from '../../scripts/stage-clean-skill.mjs';

const stagerPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../scripts/stage-clean-skill.mjs');
const canonicalNotices = fs.readFileSync(
  path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../THIRD_PARTY_NOTICES.md'),
  'utf8',
);

function git(root, args) {
  const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
  assert.equal(result.status, 0, result.stderr);
}

function write(root, relative, content, mode = null) {
  const target = path.join(root, relative);
  fs.mkdirSync(path.dirname(target), { recursive: true });
  fs.writeFileSync(target, content);
  if (mode !== null) fs.chmodSync(target, mode);
  return target;
}

function repositoryFixture() {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-clean-stage-'));
  write(root, 'THIRD_PARTY_NOTICES.md', canonicalNotices);
  write(root, 'archify/LICENSE', 'MIT License\n');
  write(root, 'archify/THIRD_PARTY_NOTICES.md', canonicalNotices);
  write(root, 'archify/package.json', JSON.stringify({
    name: 'archify-fixture',
    scripts: { test: 'node --test' },
    devDependencies: { ajv: '1.0.0' },
  }));
  write(root, 'archify/package-lock.json', '{}\n');
  write(root, 'archify/skill-release.json', '{}\n');
  write(root, 'archify/scripts/check-update.mjs', 'export {};\n');
  write(root, 'archify/scripts/update-contract.mjs', 'export {};\n');
  write(root, 'archify/renderers/shared/generated-validators.mjs', 'export {};\n');
  write(root, 'archify/test/repository-only.test.mjs', 'throw new Error();\n');
  git(root, ['init']);
  git(root, ['add', 'THIRD_PARTY_NOTICES.md']);
  return root;
}

test('clean staging rejects a packaged notice that diverges from the repository notice', () => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  try {
    git(root, ['add', '.']);
    fs.writeFileSync(
      path.join(root, 'archify', 'THIRD_PARTY_NOTICES.md'),
      canonicalNotices.replace('Simple Icons 16.28.0', 'Simple Icons 16.28.0 modified'),
    );

    assert.throws(
      () => stageCleanSkill({ repoRoot: root, destination }),
      /must byte-match the repository notice/,
    );
    assert.equal(fs.existsSync(destination), false);
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging rejects byte-identical but incomplete repository and packaged notices', () => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  try {
    const incomplete = canonicalNotices.replace(/## OpenAI mark[\s\S]*?## No additional rights granted/, '## No additional rights granted');
    fs.writeFileSync(path.join(root, 'THIRD_PARTY_NOTICES.md'), incomplete);
    fs.writeFileSync(path.join(root, 'archify', 'THIRD_PARTY_NOTICES.md'), incomplete);
    git(root, ['add', '.']);

    assert.throws(
      () => stageCleanSkill({ repoRoot: root, destination }),
      /repository THIRD_PARTY_NOTICES\.md is incomplete; missing required disclosure: .*OpenAI/,
    );
    assert.equal(fs.existsSync(destination), false);
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging preserves index modes and strips repository-only package metadata', () => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  try {
    write(root, 'archify/bin/executable.mjs', '#!/usr/bin/env node\n', 0o755);
    write(root, 'archify/runtime/test/required.dat', 'runtime fixture\n');
    git(root, ['add', 'archify']);

    stageCleanSkill({ repoRoot: root, destination });

    assert.equal(fs.statSync(path.join(destination, 'bin', 'executable.mjs')).mode & 0o777, 0o755);
    assert.equal(fs.existsSync(path.join(destination, 'test')), false);
    assert.equal(
      fs.readFileSync(path.join(destination, 'runtime', 'test', 'required.dat'), 'utf8'),
      'runtime fixture\n',
      'only the repository-root test tree is excluded',
    );
    assert.equal(fs.existsSync(path.join(destination, 'package-lock.json')), false);
    const packageJson = JSON.parse(fs.readFileSync(path.join(destination, 'package.json'), 'utf8'));
    assert.equal(Object.hasOwn(packageJson, 'scripts'), false);
    assert.equal(Object.hasOwn(packageJson, 'devDependencies'), false);
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging records Git index modes in a manifest outside the staged tree', () => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  const manifest = path.join(root, 'staged-modes.json');
  try {
    write(root, 'archify/bin/executable.mjs', '#!/usr/bin/env node\n');
    write(root, 'archify/renderers/shared/plain.mjs', 'export {};\n');
    git(root, ['add', 'archify']);
    // Set the index modes explicitly so the expectation does not depend on
    // whether this checkout can represent executable bits (core.fileMode).
    git(root, ['update-index', '--chmod=+x', 'archify/bin/executable.mjs']);
    git(root, ['update-index', '--chmod=-x', 'archify/renderers/shared/plain.mjs']);

    const result = stageCleanSkill({ repoRoot: root, destination, modeManifest: manifest });

    const recorded = JSON.parse(fs.readFileSync(manifest, 'utf8'));
    assert.equal(recorded['bin/executable.mjs'], '100755');
    assert.equal(recorded['renderers/shared/plain.mjs'], '100644');
    assert.deepEqual(result.modes, recorded);
    assert.deepEqual(Object.keys(recorded), [...Object.keys(recorded)].sort(), 'manifest keys are sorted');

    const stagedFiles = [];
    const walk = (directory, prefix) => {
      for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
        const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
        if (entry.isDirectory()) walk(path.join(directory, entry.name), relative);
        else stagedFiles.push(relative);
      }
    };
    walk(destination, '');
    assert.deepEqual(
      Object.keys(recorded).sort(),
      stagedFiles.sort(),
      'the manifest must list every staged file and nothing else',
    );
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging refuses to write the mode manifest inside the staged tree', () => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  try {
    git(root, ['add', 'archify']);
    const rejected = [
      destination,
      path.join(destination, 'modes.json'),
      path.join(destination, 'nested', 'modes.json'),
    ];
    for (const manifest of rejected) {
      assert.throws(
        () => stageCleanSkill({ repoRoot: root, destination, modeManifest: manifest }),
        /mode manifest must be written outside the staged Skill tree/,
      );
      assert.equal(fs.existsSync(destination), false, 'a rejected manifest location must not leave a staged tree behind');
    }
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging refuses an existing mode manifest path and leaves it untouched', () => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  const manifest = path.join(root, 'existing-modes.json');
  try {
    git(root, ['add', 'archify']);
    fs.writeFileSync(manifest, 'not ours\n');
    assert.throws(
      () => stageCleanSkill({ repoRoot: root, destination, modeManifest: manifest }),
      /mode manifest path already exists/,
    );
    assert.equal(fs.readFileSync(manifest, 'utf8'), 'not ours\n', 'an existing file at the manifest path must be preserved');
    assert.equal(fs.existsSync(destination), false, 'a refused manifest path must not leave a staged tree behind');
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging rejects a mode manifest that aliases the staged tree through a symlinked ancestor', (t) => {
  const root = repositoryFixture();
  const physical = path.join(root, 'physical');
  const alias = path.join(root, 'alias');
  try {
    git(root, ['add', 'archify']);
    fs.mkdirSync(physical);
    try {
      fs.symlinkSync(physical, alias, process.platform === 'win32' ? 'junction' : 'dir');
    } catch (error) {
      if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
        t.skip(`symlinks unavailable: ${error.code}`);
        return;
      }
      throw error;
    }
    const cases = [
      { destination: path.join(alias, 'staged'), modeManifest: path.join(physical, 'staged', 'modes.json') },
      { destination: path.join(physical, 'staged'), modeManifest: path.join(alias, 'staged', 'modes.json') },
    ];
    for (const { destination, modeManifest } of cases) {
      assert.throws(
        () => stageCleanSkill({ repoRoot: root, destination, modeManifest }),
        /mode manifest must be written outside the staged Skill tree/,
      );
      assert.equal(
        fs.existsSync(path.join(physical, 'staged')),
        false,
        'a rejected manifest location must not leave a staged tree behind',
      );
    }
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging rejects a symlink in a tracked file ancestor before copying bytes', (t) => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  try {
    const runtime = path.join(root, 'archify', 'runtime');
    write(root, 'archify/runtime/payload.txt', 'tracked fixture\n');
    git(root, ['add', 'archify']);
    fs.rmSync(runtime, { recursive: true });
    const external = path.join(root, 'outside-runtime');
    write(root, 'outside-runtime/payload.txt', 'external secret\n');
    try {
      fs.symlinkSync(external, runtime, process.platform === 'win32' ? 'junction' : 'dir');
    } catch (error) {
      if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
        t.skip(`symlinks unavailable: ${error.code}`);
        return;
      }
      throw error;
    }

    assert.throws(
      () => stageCleanSkill({ repoRoot: root, destination }),
      /refusing to package path through symlink: archify\/runtime/,
    );
    assert.equal(fs.existsSync(destination), false);
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging rejects tracked symlinks before reading through them', (t) => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  try {
    const external = write(root, 'outside.txt', 'private fixture\n');
    const linked = path.join(root, 'archify', 'linked.txt');
    try {
      fs.symlinkSync(external, linked);
    } catch (error) {
      if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
        t.skip(`symlinks unavailable: ${error.code}`);
        return;
      }
      throw error;
    }
    git(root, ['add', 'archify']);

    assert.throws(
      () => stageCleanSkill({ repoRoot: root, destination }),
      /refusing to package tracked symlink: archify\/linked\.txt/,
    );
    assert.equal(fs.existsSync(destination), false);
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging snapshots unstaged tracked bytes before a source ancestor can be swapped', (t) => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  const runtime = path.join(root, 'archify', 'runtime');
  const external = path.join(root, 'outside-runtime');
  const originalMkdirSync = fs.mkdirSync;
  let swapped = false;
  try {
    const payload = write(root, 'archify/runtime/payload.txt', 'indexed fixture\n');
    write(root, 'outside-runtime/payload.txt', 'external secret\n');
    git(root, ['add', 'archify']);
    fs.writeFileSync(payload, 'unstaged working-tree fixture\n');

    const probe = path.join(root, 'symlink-probe');
    try {
      fs.symlinkSync(external, probe, process.platform === 'win32' ? 'junction' : 'dir');
      fs.rmSync(probe, { force: true });
    } catch (error) {
      if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
        t.skip(`symlinks unavailable: ${error.code}`);
        return;
      }
      throw error;
    }

    fs.mkdirSync = function swapSourceAfterSnapshot(target, ...args) {
      const result = originalMkdirSync.call(fs, target, ...args);
      if (!swapped && path.resolve(target) === path.resolve(destination)) {
        fs.rmSync(runtime, { recursive: true });
        fs.symlinkSync(external, runtime, process.platform === 'win32' ? 'junction' : 'dir');
        swapped = true;
      }
      return result;
    };

    stageCleanSkill({ repoRoot: root, destination });

    assert.equal(swapped, true, 'the deterministic ancestor-swap attack must run');
    assert.equal(
      fs.readFileSync(path.join(destination, 'runtime', 'payload.txt'), 'utf8'),
      'unstaged working-tree fixture\n',
      'staging keeps the tracked working-tree snapshot and never follows the replacement ancestor',
    );
  } finally {
    fs.mkdirSync = originalMkdirSync;
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging rejects a source ancestor swapped during preflight traversal', (t) => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  const runtime = path.join(root, 'archify', 'runtime');
  const external = path.join(root, 'outside-runtime');
  const originalLstatSync = fs.lstatSync;
  let swapped = false;
  try {
    write(root, 'archify/runtime/payload.txt', 'tracked fixture\n');
    write(root, 'outside-runtime/payload.txt', 'external secret\n');
    git(root, ['add', 'archify']);
    const canonicalRuntime = path.join(fs.realpathSync(root), 'archify', 'runtime');

    const probe = path.join(root, 'symlink-probe');
    try {
      fs.symlinkSync(external, probe, process.platform === 'win32' ? 'junction' : 'dir');
      fs.rmSync(probe, { force: true });
    } catch (error) {
      if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
        t.skip(`symlinks unavailable: ${error.code}`);
        return;
      }
      throw error;
    }

    fs.lstatSync = function swapSourceBetweenAncestorAndLeaf(target, ...args) {
      const metadata = originalLstatSync.call(fs, target, ...args);
      if (!swapped && path.resolve(target) === canonicalRuntime) {
        // Guard before mutation: recursive removal can re-enter the patched
        // lstatSync implementation on Linux.
        swapped = true;
        fs.rmSync(runtime, { recursive: true });
        fs.symlinkSync(external, runtime, process.platform === 'win32' ? 'junction' : 'dir');
      }
      return metadata;
    };

    assert.throws(
      () => stageCleanSkill({ repoRoot: root, destination }),
      /(?:tracked package path changed before it could be read: archify\/|tracked package input is missing or unreadable: archify\/runtime\/payload\.txt)/,
    );
    assert.equal(swapped, true, 'the deterministic mid-preflight ancestor swap must run');
    assert.equal(fs.existsSync(destination), false);
  } finally {
    fs.lstatSync = originalLstatSync;
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test('clean staging reports the Git spawn error when Git cannot start', () => {
  const root = repositoryFixture();
  const destination = path.join(root, 'staged-skill');
  try {
    git(root, ['add', 'archify']);
    const result = spawnSync(process.execPath, [
      stagerPath,
      '--root', root,
      '--dest', destination,
    ], {
      encoding: 'utf8',
      env: { ...process.env, PATH: '' },
    });
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /unable to enumerate tracked Archify files: .*ENOENT/);
    assert.doesNotMatch(result.stderr, /tracked Archify paths must be valid UTF-8/);
    assert.equal(fs.existsSync(destination), false);
  } finally {
    fs.rmSync(root, { recursive: true, force: true });
  }
});

// Historical DSH snapshots predate embedded fonts and must remain packageable.
for (const fontPath of [null, 'archify/assets/template.html', 'archify/examples/standalone.html']) {
  const embedded = fontPath !== null;
  test(`clean staging applies font disclosures to snapshot contents (fontPath=${fontPath})`, () => {
    const root = repositoryFixture();
    const destination = path.join(root, 'staged-skill');
    try {
      const legacy = canonicalNotices.replace(/## JetBrains Mono[\s\S]*?(?=\n## |$)/, '');
      write(root, 'THIRD_PARTY_NOTICES.md', legacy);
      write(root, 'archify/THIRD_PARTY_NOTICES.md', legacy);
      write(root, 'archify/assets/template.html', '<html>legacy viewer</html>');
      if (embedded) write(root, fontPath, '@font-face { src: url(data:font/woff2;base64,fixture); }');
      write(root, 'archify/assets/JetBrainsMono-OFL.txt', 'fixture license');
      git(root, ['add', '.']);
      if (embedded) {
        assert.throws(() => stageCleanSkill({ repoRoot: root, destination }), /missing required disclosure: JetBrains Mono/);
        assert.equal(fs.existsSync(destination), false);
        write(root, 'THIRD_PARTY_NOTICES.md', canonicalNotices);
        write(root, 'archify/THIRD_PARTY_NOTICES.md', canonicalNotices);
        stageCleanSkill({ repoRoot: root, destination });
        assert.equal(fs.readFileSync(path.join(destination, 'assets/JetBrainsMono-OFL.txt'), 'utf8'), 'fixture license');
        fs.rmSync(destination, { recursive: true });
        fs.unlinkSync(path.join(root, 'archify/assets/JetBrainsMono-OFL.txt'));
        git(root, ['add', '.']);
        assert.throws(() => stageCleanSkill({ repoRoot: root, destination }), /requires assets\/JetBrainsMono-OFL.txt/);
      } else {
        stageCleanSkill({ repoRoot: root, destination });
        assert.equal(fs.readFileSync(path.join(destination, 'THIRD_PARTY_NOTICES.md'), 'utf8'), legacy);
      }
    } finally { fs.rmSync(root, { recursive: true, force: true }); }
  });
}
```

## test/cli-output-types.test.mjs

```js
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import assert from 'node:assert/strict';
import { startPreview } from '../bin/preview.mjs';

const root = fileURLToPath(new URL('../', import.meta.url));
const cli = path.join(root, 'bin/archify.mjs');
const workflow = path.join(root, 'examples/agent-tool-call.workflow.json');
const architecture = path.join(root, 'examples/web-app.architecture.json');
const base = path.join(root, 'examples/checkout-platform.base.architecture.json');
const head = path.join(root, 'examples/checkout-platform.head.architecture.json');
const marker = 'MARKER=do-not-destroy';
function workspace(t) {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-cli-types-'));
  t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
  return dir;
}
function run(args, cwd) {
  return spawnSync(process.execPath, [cli, ...args], { cwd, encoding: 'utf8' });
}

for (const command of ['render', 'deliver', 'compare']) {
  test(`${command} rejects a non-HTML target without replacing existing bytes`, t => {
    const dir = workspace(t);
    const cwd = path.join(dir, 'working');
    fs.mkdirSync(cwd);
    const output = path.join(dir, 'marker.env');
    fs.writeFileSync(output, marker);
    const args = command === 'compare' ? ['compare', 'architecture', base, head] : [command, 'workflow', workflow];
    const result = run([...args, '../marker.env', ...(command === 'render' ? [] : ['--json'])], cwd);
    assert.equal(result.status, 1, result.stderr);
    if (command === 'render') assert.match(result.stderr, /output\/cli-extension/);
    else assert.equal(JSON.parse(result.stdout).diagnostics[0].code, 'output/cli-extension');
    assert.equal(fs.readFileSync(output, 'utf8'), marker);
    assert.deepEqual(fs.readdirSync(cwd), []);
  });
}

test('preview rejects a non-HTML target before publishing or starting a server', async t => {
  const cwd = workspace(t);
  const output = path.join(cwd, 'marker.env');
  fs.writeFileSync(output, marker);
  let session;
  try {
    await assert.rejects(async () => {
      session = await startPreview({ type: 'workflow', input: workflow, output, cwd, open: false });
    }, error => error.archifyDiagnostics?.[0]?.code === 'output/cli-extension');
  } finally {
    if (session) { session.stop(); await session.closed; }
  }
  assert.equal(fs.readFileSync(output, 'utf8'), marker);
});

test('render and deliver reject HTML symlinks resolving to non-HTML files', t => {
  const cwd = workspace(t);
  const target = path.join(cwd, 'marker.env');
  const output = path.join(cwd, 'diagram.html');
  fs.writeFileSync(target, marker);
  try { fs.symlinkSync(target, output, 'file'); }
  catch (error) {
    if (error.code === 'EPERM') { t.skip('symlink creation requires permission'); return; }
    throw error;
  }
  for (const command of ['render', 'deliver']) {
    const result = run([command, 'workflow', workflow, output, ...(command === 'deliver' ? ['--json'] : [])], cwd);
    assert.equal(result.status, 1, result.stderr);
    if (command === 'deliver') assert.equal(JSON.parse(result.stdout).diagnostics[0].code, 'output/cli-resolved-extension');
    else assert.match(result.stderr, /output\/cli-resolved-extension/);
    assert.equal(fs.readFileSync(target, 'utf8'), marker);
    assert.equal(fs.lstatSync(output).isSymbolicLink(), true);
  }
});

test('compare rejects a non-JSON receipt before writing either output', t => {
  const cwd = workspace(t);
  const output = path.join(cwd, 'delta.html');
  const receipt = path.join(cwd, 'marker.env');
  fs.writeFileSync(output, marker);
  fs.writeFileSync(receipt, marker);
  const result = run(['compare', 'architecture', base, head, output, '--receipt', receipt, '--json'], cwd);
  assert.equal(result.status, 1, result.stderr);
  assert.equal(JSON.parse(result.stdout).diagnostics[0].code, 'output/cli-extension');
  assert.equal(fs.readFileSync(output, 'utf8'), marker);
  assert.equal(fs.readFileSync(receipt, 'utf8'), marker);
});

for (const aliasKind of ['same path', 'symlink alias']) {
  test(`compare reports target alias before CLI extension for ${aliasKind}`, t => {
    const cwd = workspace(t);
    const output = path.join(cwd, 'same.json');
    const receipt = aliasKind === 'same path'
      ? output
      : path.join(cwd, 'receipt.json');
    fs.writeFileSync(output, marker);
    if (aliasKind === 'symlink alias') {
      try { fs.symlinkSync(output, receipt, 'file'); }
      catch (error) {
        if (error.code === 'EPERM') { t.skip('symlink creation requires permission'); return; }
        throw error;
      }
    }

    const result = run([
      'compare', 'architecture', base, head, output,
      '--receipt', receipt, '--json',
    ], cwd);

    assert.equal(result.status, 1, result.stderr);
    assert.equal(JSON.parse(result.stdout).diagnostics[0].code, 'output/target-alias');
    assert.equal(fs.readFileSync(output, 'utf8'), marker);
    if (aliasKind === 'symlink alias') assert.equal(fs.readlinkSync(receipt), output);
  });
}

test('compare keeps CLI extension precedence for distinct invalid outputs', t => {
  const cwd = workspace(t);
  const output = path.join(cwd, 'artifact.json');
  const receipt = path.join(cwd, 'receipt.txt');
  fs.writeFileSync(output, marker);
  fs.writeFileSync(receipt, marker);

  const result = run([
    'compare', 'architecture', base, head, output,
    '--receipt', receipt, '--json',
  ], cwd);

  assert.equal(result.status, 1, result.stderr);
  assert.equal(JSON.parse(result.stdout).diagnostics[0].code, 'output/cli-extension');
  assert.equal(fs.readFileSync(output, 'utf8'), marker);
  assert.equal(fs.readFileSync(receipt, 'utf8'), marker);
});

test('compare reports target alias before CLI extension for a derived receipt symlink', t => {
  const cwd = workspace(t);
  const output = path.join(cwd, 'artifact.json');
  const receipt = path.join(cwd, 'artifact.receipt.json');
  fs.writeFileSync(output, marker);
  try { fs.symlinkSync(output, receipt, 'file'); }
  catch (error) {
    if (error.code === 'EPERM') { t.skip('symlink creation requires permission'); return; }
    throw error;
  }

  const result = run(['compare', 'architecture', base, head, output, '--json'], cwd);

  assert.equal(result.status, 1, result.stderr);
  assert.equal(JSON.parse(result.stdout).diagnostics[0].code, 'output/target-alias');
  assert.equal(fs.readFileSync(output, 'utf8'), marker);
  assert.equal(fs.readlinkSync(receipt), output);
});

test('absolute HTML and JSON outputs outside cwd remain supported', t => {
  const dir = workspace(t);
  const cwd = path.join(dir, 'working');
  fs.mkdirSync(cwd);
  const output = path.join(dir, 'diagram.HTML');
  const delivered = run(['deliver', 'workflow', workflow, output, '--json'], cwd);
  assert.equal(delivered.status, 0, delivered.stderr);
  assert.equal(JSON.parse(delivered.stdout).ok, true);
  assert.match(fs.readFileSync(output, 'utf8'), /<!DOCTYPE html>/i);
  const receipt = path.join(dir, 'delta.JSON');
  const compared = run(['compare', 'architecture', base, head, output, '--receipt', receipt, '--json'], cwd);
  assert.equal(compared.status, 0, compared.stderr || compared.stdout);
  assert.equal(JSON.parse(fs.readFileSync(receipt, 'utf8')).ok, true);
});

test('validate, inspect and layout JSON retain their internal no-output behavior', t => {
  const cwd = workspace(t);
  for (const args of [
    ['validate', 'architecture', architecture, '--json'],
    ['inspect', 'architecture', architecture],
    ['validate', 'workflow', workflow, '--layout-json'],
  ]) {
    const result = run(args, cwd);
    assert.equal(result.status, 0, result.stderr);
    assert.doesNotThrow(() => JSON.parse(result.stdout));
    assert.deepEqual(fs.readdirSync(cwd), []);
  }
});

test('compare rejects a JSON receipt symlink to a non-JSON target', t => {
  const cwd = workspace(t);
  const target = path.join(cwd, 'marker.env');
  const receipt = path.join(cwd, 'receipt.json');
  const output = path.join(cwd, 'delta.html');
  fs.writeFileSync(target, marker);
  try { fs.symlinkSync(target, receipt, 'file'); }
  catch (error) {
    if (error.code === 'EPERM') { t.skip('symlink creation requires permission'); return; }
    throw error;
  }
  const result = run(['compare', 'architecture', base, head, output, '--receipt', receipt, '--json'], cwd);
  assert.equal(result.status, 1, result.stderr);
  assert.equal(JSON.parse(result.stdout).diagnostics[0].code, 'output/cli-resolved-extension');
  assert.equal(fs.readFileSync(target, 'utf8'), marker);
  assert.equal(fs.existsSync(output), false);
});
```

## test/cli.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawn, spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { extractSvgs, parseXml } from './helpers/xml.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-cli-'));
const cli = path.join(skillRoot, 'bin/archify.mjs');

function run(args, options = {}) {
  return spawnSync(process.execPath, [cli, ...args], {
    cwd: options.cwd || skillRoot,
    encoding: 'utf8',
    env: options.env || process.env,
  });
}

function sha256(file) {
  return createHash('sha256').update(fs.readFileSync(file)).digest('hex');
}

function makeFakeOpeners(name, { exitCode = 0 } = {}) {
  const bin = path.join(tmp, name);
  const log = path.join(bin, 'open-log.json');
  fs.mkdirSync(bin, { recursive: true });
  const source = `#!/usr/bin/env node
const fs = require('node:fs');
const target = process.argv[process.argv.length - 1];
fs.writeFileSync(process.env.ARCHIFY_TEST_OPEN_LOG, JSON.stringify({
  argv: process.argv.slice(2),
  target,
  existed: fs.existsSync(target),
}));
process.exit(${exitCode});
`;
  for (const command of ['open', 'xdg-open']) {
    const executable = path.join(bin, command);
    fs.writeFileSync(executable, source);
    fs.chmodSync(executable, 0o755);
  }
  return {
    log,
    env: {
      ...process.env,
      PATH: `${bin}${path.delimiter}${process.env.PATH || ''}`,
      ARCHIFY_TEST_OPEN_LOG: log,
    },
  };
}

function copyInstalledSkill(target) {
  fs.cpSync(skillRoot, target, {
    recursive: true,
    filter(source) {
      const rel = path.relative(skillRoot, source);
      return rel !== 'node_modules' && !rel.startsWith(`node_modules${path.sep}`)
        && rel !== 'test' && !rel.startsWith(`test${path.sep}`)
        // Another test creates this short-lived directory under skillRoot so
        // Ajv resolves from the checkout. Never copy a concurrently removed
        // test fixture into an installed-skill simulation.
        && !rel.startsWith('.validator-check-');
    },
  });
}

test('cli: help lists commands and diagram types', () => {
  const result = run(['--help']);
  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout, /archify render <type>/);
  assert.match(result.stdout, /archify compare architecture <base\.json> <head\.json>/);
  assert.match(result.stdout, /archify deliver <type>/);
  assert.match(result.stdout, /archify preview <type>/);
  assert.match(result.stdout, /archify visual-check <output\.html>/);
  assert.match(result.stdout, /--open/);
  assert.match(result.stdout, /--repo-root path \(architecture only\)/);
  assert.match(result.stdout, /archify guide \[scenario or question\]/);
  assert.match(result.stdout, /archify doctor/);
  assert.match(result.stdout, /archify demo \[output-directory\]/);
  assert.match(result.stdout, /architecture, workflow, sequence, dataflow, lifecycle/);
});

test('cli: doctor reports a complete installation is ready', () => {
  const result = run(['doctor']);
  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout, /\[ok\] Node\.js v\d+/);
  assert.match(result.stdout, /\[ok\] Core template/);
  assert.match(result.stdout, /\[ok\] Example renderer/);
  assert.match(result.stdout, /\[ok\] Live preview runtime/);
  assert.match(result.stdout, /\[ok\] Scenario recipe guide/);
  assert.match(result.stdout, /\[ok\] Progressive authoring references/);
  assert.match(result.stdout, /\[ok\] Architecture compare runtime and proof fixtures/);
  assert.match(result.stdout, /\[ok\] Standalone schema validators/);
  assert.match(result.stdout, /\[ok\] architecture renderer, schema, and example/);
  assert.match(result.stdout, /\[ok\] lifecycle renderer, schema, and example/);
  assert.match(result.stdout, /Archify is ready\./);
});

test('cli: doctor identifies an incomplete installation', () => {
  const incompleteRoot = path.join(tmp, 'incomplete-skill');
  const incompleteBin = path.join(incompleteRoot, 'bin');
  fs.mkdirSync(incompleteBin, { recursive: true });
  fs.copyFileSync(cli, path.join(incompleteBin, 'archify.mjs'));

  const result = spawnSync(process.execPath, [path.join(incompleteBin, 'archify.mjs'), 'doctor'], {
    cwd: incompleteRoot,
    encoding: 'utf8',
  });

  assert.equal(result.status, 1);
  assert.match(result.stdout, /\[missing\] Core template/);
  assert.match(result.stdout, /\[missing\] Scenario recipe guide/);
  assert.match(result.stdout, /\[missing\] workflow renderer, schema, and example/);
  assert.match(result.stderr, /Archify is not ready: \d+ required files? missing\./);
});

test('cli: doctor rejects a corrupt standalone validator', () => {
  const corruptRoot = path.join(tmp, 'corrupt-skill');
  copyInstalledSkill(corruptRoot);
  fs.writeFileSync(path.join(corruptRoot, 'renderers/shared/generated-validators.mjs'), 'export const workflow = ;\n');

  const result = spawnSync(process.execPath, [path.join(corruptRoot, 'bin/archify.mjs'), 'doctor'], {
    cwd: corruptRoot,
    encoding: 'utf8',
  });

  assert.equal(result.status, 1);
  assert.match(result.stdout, /\[invalid\] Standalone schema validators/);
  assert.match(result.stderr, /Archify is not ready: 1 runtime check failed\./);
});

test('cli: examples renders from an installed skill', () => {
  const installedRoot = path.join(tmp, 'installed-skill');
  copyInstalledSkill(installedRoot);

  const result = spawnSync(process.execPath, [path.join(installedRoot, 'bin/archify.mjs'), 'examples'], {
    cwd: installedRoot,
    encoding: 'utf8',
  });

  assert.equal(result.status, 0, result.stderr);
  for (const output of [
    'workflow-agent-tool-call-rendered.html',
    'sequence-cache-miss-request.html',
    'dataflow-product-analytics.html',
    'lifecycle-agent-run.html',
    'web-app-rendered.html',
  ]) {
    assert.equal(fs.existsSync(path.join(installedRoot, 'examples', output)), true, output);
  }
});

test('cli: argument-free commands reject trailing arguments', () => {
  for (const command of ['examples', 'doctor']) {
    const extra = run([command, 'ignored-extra']);
    assert.equal(extra.status, 2, command);
    assert.match(extra.stderr, /Usage:/, command);

    const unknown = run([command, '--bogus']);
    assert.equal(unknown.status, 2, command);
    assert.match(unknown.stderr, new RegExp(`Unknown ${command} option "--bogus"`), command);
  }
});

test('cli: guide lists all scenario recipes by diagram type', () => {
  const result = run(['guide']);

  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout, /Archify scenario recipes \(11\)/);
  for (const type of ['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']) {
    assert.match(result.stdout, new RegExp(`\\[${type}\\]`));
  }
});

test('cli: guide recommends a scenario as structured json', () => {
  const result = run(['guide', 'Show an API request with Redis cache miss', '--json']);

  assert.equal(result.status, 0, result.stderr);
  const parsed = JSON.parse(result.stdout);
  assert.equal(parsed.ok, true);
  assert.equal(parsed.lang, 'en');
  assert.equal(parsed.confidence, 'high');
  assert.equal(parsed.recommendation.id, 'api-request');
  assert.equal(parsed.recommendation.type, 'sequence');
});

test('cli: guide detects Chinese and explains the recommendation boundary', () => {
  const result = run(['guide', '展示 Kafka topic 消费者组和死信队列']);

  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout, /推荐: 事件流拓扑  \[dataflow\]/);
  assert.match(result.stdout, /不要这样用:/);
  assert.match(result.stdout, /必须包含:/);
  assert.match(result.stdout, /可直接复制的提示词:/);
});

test('cli: guide works from an installed skill without node_modules', () => {
  const installedRoot = path.join(tmp, 'installed-guide-skill');
  copyInstalledSkill(installedRoot);
  const installedCli = path.join(installedRoot, 'bin/archify.mjs');

  const result = spawnSync(process.execPath, [installedCli, 'guide', 'incident-runbook', '--json'], {
    cwd: installedRoot,
    encoding: 'utf8',
  });

  assert.equal(result.status, 0, result.stderr);
  assert.equal(JSON.parse(result.stdout).recommendation.id, 'incident-runbook');
});

test('cli: demo creates a ready-to-open diagram in a chosen directory', () => {
  const outputDirectory = path.join(tmp, 'my-demo');
  const output = path.join(outputDirectory, 'archify-demo.html');
  const result = run(['demo', outputDirectory]);

  assert.equal(result.status, 0, result.stderr);
  assert.equal(fs.existsSync(output), true);
  assert.match(fs.readFileSync(output, 'utf8'), /Sample Web App Diagram/);
  assert.match(result.stdout, new RegExp(`Demo ready: ${output.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
  assert.match(result.stdout, /Next: open the HTML in your browser/);
  assert.match(result.stdout, /archify render architecture/);
});

test('cli: demo defaults to the current directory', () => {
  const workingDirectory = path.join(tmp, 'default-demo');
  fs.mkdirSync(workingDirectory);
  const result = run(['demo'], { cwd: workingDirectory });

  assert.equal(result.status, 0, result.stderr);
  assert.equal(fs.existsSync(path.join(workingDirectory, 'archify-demo.html')), true);
});

test('cli: demo rejects a mistyped option without creating an output directory', () => {
  const workingDirectory = path.join(tmp, 'demo-option-guard');
  fs.mkdirSync(workingDirectory);

  const result = run(['demo', '--typo'], { cwd: workingDirectory });

  assert.equal(result.status, 2);
  assert.match(result.stderr, /Unknown demo option "--typo"/);
  assert.deepEqual(fs.readdirSync(workingDirectory), []);
});

test('cli: render writes a diagram html file', () => {
  const out = path.join(tmp, 'workflow.html');
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  const result = run(['render', 'workflow', input, out]);
  assert.equal(result.status, 0, result.stderr);
  assert.equal(fs.existsSync(out), true);
  assert.match(fs.readFileSync(out, 'utf8'), /Agent Tool Call Workflow/);
});

test('cli: visual-check returns a skipped receipt with exit 2 when Chrome is unavailable', () => {
  const out = path.join(tmp, 'visual-check-skipped.html');
  fs.writeFileSync(out, '<!doctype html><html><body>delivered</body></html>');
  const missingChrome = path.join(tmp, 'missing-chrome');
  const result = run(['visual-check', out, '--json'], {
    env: { ...process.env, ARCHIFY_CHROME: missingChrome },
  });

  assert.equal(result.status, 2, result.stderr);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.status, 'skipped');
  assert.equal(receipt.evidenceKind, 'automated-browser');
  assert.equal(receipt.visualReview, 'pending');
  assert.equal(receipt.chrome.status, 'unavailable');
  assert.equal(fs.existsSync(out.replace(/\.html$/, '.visual-check.json')), true);
});

test('cli: visual-check describes human output as automated browser evidence, not visual approval', () => {
  const out = path.join(tmp, 'visual-check-browser-evidence.html');
  fs.writeFileSync(out, '<!doctype html><html><body>delivered</body></html>');
  const missingChrome = path.join(tmp, 'missing-browser-evidence-chrome');
  const result = run(['visual-check', out], {
    env: { ...process.env, ARCHIFY_CHROME: missingChrome },
  });

  assert.equal(result.status, 2, result.stderr);
  assert.match(result.stdout, /automated browser evidence skipped:/i);
  assert.match(result.stdout, /perceptual visual review pending/i);
  assert.doesNotMatch(result.stdout, /^visual-check skipped:/m);
});

test('cli: visual-check keeps automated and perceptual claims separate on input failure', () => {
  const result = run(['visual-check', path.join(tmp, 'missing-browser-evidence.html')]);

  assert.equal(result.status, 1);
  assert.match(result.stderr, /automated browser evidence failed:/i);
  assert.match(result.stderr, /perceptual visual review pending/i);
  assert.doesNotMatch(result.stderr, /^visual-check failed:/m);
});

test('cli: deliver atomically writes a checked artifact and structured receipt', () => {
  const out = path.join(tmp, 'delivered-workflow.html');
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  const result = run(['deliver', 'workflow', input, out, '--quality', 'showcase', '--json']);

  assert.equal(result.status, 0, result.stderr);
  assert.equal(fs.existsSync(out), true);
  assert.match(fs.readFileSync(out, 'utf8'), /Agent Tool Call Workflow/);

  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.schemaVersion, 1);
  assert.equal(receipt.ok, true);
  assert.equal(receipt.command, 'deliver');
  assert.equal(receipt.type, 'workflow');
  assert.equal(receipt.input, input);
  assert.equal(receipt.output, out);
  assert.deepEqual(receipt.specification, {
    sha256: sha256(input),
    bytes: fs.statSync(input).size,
  });
  assert.match(receipt.artifact.sha256, /^[a-f0-9]{64}$/);
  assert.equal(receipt.artifact.sha256, sha256(out));
  assert.equal(receipt.artifact.bytes, fs.statSync(out).size);
  assert.deepEqual(receipt.validation, {
    checksPassed: 9,
    checkCount: 9,
    compositionProfile: 'showcase',
    compositionStatus: 'pass',
    errors: 0,
    warnings: 0,
  });
  assert.equal('open' in receipt, false);
});

test('cli: deliver --open launches only the committed absolute artifact as one argument', {
  skip: process.platform === 'win32',
}, () => {
  const fake = makeFakeOpeners('successful-open');
  const out = path.join(tmp, `-复杂 path 'quoted'`, 'verified diagram.html');
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  const result = run(['deliver', 'workflow', input, out, '--open', '--json'], { env: fake.env });

  assert.equal(result.status, 0, result.stderr);
  const receipt = JSON.parse(result.stdout);
  assert.deepEqual(receipt.open, {
    requested: true,
    status: 'opened',
    target: out,
    method: process.platform === 'darwin' ? 'open' : 'xdg-open',
  });
  const invocation = JSON.parse(fs.readFileSync(fake.log, 'utf8'));
  assert.equal(invocation.existed, true, 'the opener must run after the atomic commit');
  assert.deepEqual(invocation.argv, [out]);
  assert.equal(invocation.target, out);
  assert.equal(fs.existsSync(out), true);
});

test('cli: opener failure does not invalidate a verified delivery or pollute json stdout', {
  skip: process.platform === 'win32',
}, () => {
  const fake = makeFakeOpeners('failed-open', { exitCode: 17 });
  const out = path.join(tmp, 'open-failure-preserves-delivery.html');
  const input = path.join(skillRoot, 'examples/web-app.architecture.json');
  const result = run(['deliver', 'architecture', input, out, '--open', '--json'], { env: fake.env });

  assert.equal(result.status, 0, result.stderr);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.ok, true);
  assert.equal(receipt.open.status, 'failed');
  assert.equal(receipt.open.target, out);
  assert.match(result.stderr, /Could not open the verified artifact/);
  assert.match(result.stderr, new RegExp(out.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
  assert.equal(fs.existsSync(out), true);
  assert.equal(receipt.artifact.sha256, sha256(out));
});

test('cli: deliver failure never invokes the optional opener', {
  skip: process.platform === 'win32',
}, () => {
  const fake = makeFakeOpeners('never-open');
  const input = path.join(tmp, 'invalid-open-delivery.json');
  fs.writeFileSync(input, '{broken json');
  const out = path.join(tmp, 'must-not-open.html');
  const result = run(['deliver', 'architecture', input, out, '--open', '--json'], { env: fake.env });

  assert.equal(result.status, 1);
  assert.equal(JSON.parse(result.stdout).stage, 'input');
  assert.equal(fs.existsSync(fake.log), false);
  assert.equal(fs.existsSync(out), false);
});

test('cli: a missing optional opener module preserves verified delivery with a fallback receipt', () => {
  const installedRoot = path.join(tmp, 'missing-open-module-skill');
  copyInstalledSkill(installedRoot);
  const installedCli = path.join(installedRoot, 'bin/archify.mjs');
  fs.rmSync(path.join(installedRoot, 'bin/open-artifact.mjs'));
  const input = path.join(installedRoot, 'examples/agent-tool-call.workflow.json');
  const out = path.join(tmp, 'missing-open-module-delivery.html');

  const result = spawnSync(process.execPath, [installedCli, 'deliver', 'workflow', input, out, '--open', '--json'], {
    cwd: installedRoot,
    encoding: 'utf8',
  });

  assert.equal(result.status, 0, result.stderr);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.ok, true);
  assert.deepEqual(receipt.open, {
    requested: true,
    status: 'unsupported',
    target: out,
    method: null,
  });
  assert.match(result.stderr, /Open it manually/);
  assert.equal(receipt.artifact.sha256, sha256(out));
});

test('cli: deliver preserves the renderer default output contract', () => {
  const workingDirectory = path.join(tmp, 'delivery-default-output');
  fs.mkdirSync(workingDirectory, { recursive: true });
  const input = path.join(workingDirectory, 'source.architecture.json');
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  source.meta.output = 'verified-default.html';
  fs.writeFileSync(input, JSON.stringify(source));

  const result = run(['deliver', 'architecture', input, '--json'], { cwd: workingDirectory });
  assert.equal(result.status, 0, result.stderr);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.output, path.join(fs.realpathSync(workingDirectory), 'verified-default.html'));
  assert.equal(fs.existsSync(receipt.output), true);
});

test('cli: deliver works from an installed skill without node_modules', () => {
  const installedRoot = path.join(tmp, 'installed-deliver-skill');
  copyInstalledSkill(installedRoot);
  const installedCli = path.join(installedRoot, 'bin/archify.mjs');
  const cases = [
    ['architecture-boundaries', 'architecture', 'production-deployment.architecture.json'],
    ['architecture-issue-110', 'architecture', 'brand-aware-delivery.architecture.json'],
    ['workflow', 'workflow', 'agent-tool-call.workflow.json'],
    ['sequence', 'sequence', 'cache-miss-request.sequence.json'],
    ['dataflow', 'dataflow', 'product-analytics.dataflow.json'],
    ['lifecycle', 'lifecycle', 'agent-run.lifecycle.json'],
  ];

  for (const [label, type, example] of cases) {
    const input = path.join(installedRoot, 'examples', example);
    const out = path.join(tmp, `installed-${label}-delivery.html`);
    const result = spawnSync(process.execPath, [installedCli, 'deliver', type, input, out, '--json'], {
      cwd: installedRoot,
      encoding: 'utf8',
    });

    assert.equal(result.status, 0, `${label}: ${result.stderr}`);
    assert.equal(JSON.parse(result.stdout).validation.checkCount, 9, label);
    assert.equal(fs.existsSync(out), true, label);
    const extracted = extractSvgs(fs.readFileSync(out, 'utf8'));
    assert.equal(extracted.direct.length, 1, `${label}: expected one delivered SVG`);
    assert.doesNotThrow(
      () => parseXml(extracted.direct[0]),
      `${label}: delivered SVG must be well-formed XML`,
    );
  }
});

test('cli: deliver XML guard parses markup instead of scanning attribute-like text', () => {
  assert.doesNotThrow(() => parseXml(
    '<svg xmlns="http://www.w3.org/2000/svg" aria-label="mentions data-node-label safely"/>',
  ));
  assert.throws(
    () => parseXml('<svg xmlns="http://www.w3.org/2000/svg" data-node-label></svg>'),
    /attribute without value/i,
  );
  assert.throws(
    () => parseXml('<svg xmlns="http://www.w3.org/2000/svg"><g></svg>'),
    /unexpected close tag/i,
  );
});

test('cli: preview runs from an installed skill without node_modules and exits cleanly', { timeout: 30000 }, async () => {
  const installedRoot = path.join(tmp, 'installed-preview-skill');
  copyInstalledSkill(installedRoot);
  const installedCli = path.join(installedRoot, 'bin/archify.mjs');
  const input = path.join(installedRoot, 'examples/web-app.architecture.json');
  const output = path.join(tmp, 'installed-preview.html');
  const child = spawn(process.execPath, [installedCli, 'preview', 'architecture', input, output, '--quality', 'showcase', '--no-open'], {
    cwd: installedRoot,
    encoding: 'utf8',
    stdio: ['ignore', 'pipe', 'pipe'],
  });
  let stdout = '';
  let stderr = '';
  child.stdout.setEncoding('utf8');
  child.stderr.setEncoding('utf8');
  child.stdout.on('data', (chunk) => { stdout += chunk; });
  child.stderr.on('data', (chunk) => { stderr += chunk; });

  let previewUrl;
  const started = Date.now();
  while (!previewUrl && Date.now() - started < 8000) {
    previewUrl = stdout.match(/preview (http:\/\/127\.0\.0\.1:\d+\/)/)?.[1];
    if (!previewUrl) await new Promise((resolve) => setTimeout(resolve, 40));
  }
  assert.ok(previewUrl, `preview URL missing; stdout=${stdout}; stderr=${stderr}`);

  let state;
  while (Date.now() - started < 15000) {
    state = await fetch(new URL('/state', previewUrl)).then((response) => response.json());
    if (state.status === 'verified') break;
    await new Promise((resolve) => setTimeout(resolve, 50));
  }
  assert.equal(state?.status, 'verified', `preview did not verify; stdout=${stdout}; stderr=${stderr}`);
  assert.equal(state.revision, 1);
  assert.equal(fs.existsSync(output), true);

  child.kill('SIGTERM');
  const exit = await new Promise((resolve) => child.once('close', (code, signal) => resolve({ code, signal })));
  assert.deepEqual(exit, { code: 0, signal: null });
  assert.match(stdout, /stopping preview/);
  await assert.rejects(fetch(previewUrl));
  assert.deepEqual(fs.readdirSync(path.dirname(output)).filter((name) => name.startsWith('.archify-preview-')), []);
});

test('cli: deliver preserves the previous artifact when the final check fails', () => {
  const installedRoot = path.join(tmp, 'broken-deliver-skill');
  copyInstalledSkill(installedRoot);
  const installedCli = path.join(installedRoot, 'bin/archify.mjs');
  const templatePath = path.join(installedRoot, 'assets/template.html');
  const template = fs.readFileSync(templatePath, 'utf8');
  fs.writeFileSync(templatePath, template.replace('</body>', '<svg aria-label="accidental second svg"></svg>\n</body>'));

  const input = path.join(installedRoot, 'examples/web-app.architecture.json');
  const out = path.join(tmp, 'preserved-delivery.html');
  const trustedPriorArtifact = '<!doctype html><title>trusted prior artifact</title>\n';
  fs.writeFileSync(out, trustedPriorArtifact);

  const result = spawnSync(process.execPath, [installedCli, 'deliver', 'architecture', input, out, '--json'], {
    cwd: installedRoot,
    encoding: 'utf8',
  });

  assert.equal(result.status, 1);
  const failure = JSON.parse(result.stdout);
  assert.equal(failure.ok, false);
  assert.equal(failure.stage, 'check');
  assert.equal(failure.diagnostics[0].code, 'artifact/single-svg');
  assert.equal(failure.diagnostics[0].subject.check, 'single_svg');
  assert.ok(failure.diagnostics[0].supportedFixes.some((fix) => fix.includes('exactly one diagram SVG')));
  assert.equal(failure.checker.checks.find((entry) => entry.name === 'single_svg').ok, false);
  assert.equal(fs.readFileSync(out, 'utf8'), trustedPriorArtifact);
  assert.deepEqual(
    fs.readdirSync(path.dirname(out)).filter((name) => name.includes('.archify-delivery-')),
    [],
  );
});

test('cli: deliver reports renderer failure as json and preserves the previous artifact', () => {
  const input = path.join(tmp, 'invalid-delivery.workflow.json');
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8'));
  source.nodes[0].unexpected = true;
  fs.writeFileSync(input, JSON.stringify(source));

  const out = path.join(tmp, 'renderer-failure-preserved.html');
  const trustedPriorArtifact = '<!doctype html><title>last known good</title>\n';
  fs.writeFileSync(out, trustedPriorArtifact);

  const result = run(['deliver', 'workflow', input, out, '--json']);
  assert.equal(result.status, 1);
  const failure = JSON.parse(result.stdout);
  assert.equal(failure.ok, false);
  assert.equal(failure.stage, 'render');
  assert.match(failure.error, /schema validation failed/i);
  assert.equal(fs.readFileSync(out, 'utf8'), trustedPriorArtifact);
});

test('cli: deliver reports unreadable input as json without touching the target', () => {
  const input = path.join(tmp, 'malformed-delivery.json');
  fs.writeFileSync(input, '{not valid json');
  const out = path.join(tmp, 'malformed-input-preserved.html');
  const trustedPriorArtifact = '<!doctype html><title>still trusted</title>\n';
  fs.writeFileSync(out, trustedPriorArtifact);

  const result = run(['deliver', 'architecture', input, out, '--json']);
  assert.equal(result.status, 1);
  const failure = JSON.parse(result.stdout);
  assert.equal(failure.ok, false);
  assert.equal(failure.stage, 'input');
  assert.match(failure.error, /Could not read delivery input/);
  assert.equal(fs.readFileSync(out, 'utf8'), trustedPriorArtifact);
});

test('cli: invalid source output metadata still fails inside the renderer', () => {
  const workingDirectory = path.join(tmp, 'invalid-output-metadata');
  fs.mkdirSync(workingDirectory, { recursive: true });
  const input = path.join(workingDirectory, 'source.architecture.json');
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  source.meta.output = 17;
  fs.writeFileSync(input, JSON.stringify(source));
  const out = path.join(workingDirectory, 'architecture.html');
  const trustedPriorArtifact = '<!doctype html><title>metadata did not replace me</title>\n';
  fs.writeFileSync(out, trustedPriorArtifact);

  const result = run(['deliver', 'architecture', input, '--json'], { cwd: workingDirectory });
  assert.equal(result.status, 1);
  const failure = JSON.parse(result.stdout);
  assert.equal(failure.stage, 'render');
  assert.match(failure.error, /schema validation failed/i);
  assert.equal(fs.readFileSync(out, 'utf8'), trustedPriorArtifact);
});

test('cli: deliver reports commit failure without a false success receipt', () => {
  const input = path.join(skillRoot, 'examples/web-app.architecture.json');
  const outputDirectory = path.join(tmp, 'commit-target-is-a-directory.html');
  fs.mkdirSync(outputDirectory, { recursive: true });

  const result = run(['deliver', 'architecture', input, outputDirectory, '--json']);
  assert.equal(result.status, 1);
  const failure = JSON.parse(result.stdout);
  assert.equal(failure.ok, false);
  assert.equal(failure.stage, 'commit');
  assert.match(failure.error, /Could not commit verified delivery/);
  assert.equal(fs.statSync(outputDirectory).isDirectory(), true);
  assert.equal(fs.readdirSync(outputDirectory).length, 0);
});

test('cli: deliver reports preparation failure as json without touching the blocker', () => {
  const input = path.join(skillRoot, 'examples/web-app.architecture.json');
  const blockingFile = path.join(tmp, 'delivery-parent-is-a-file');
  fs.writeFileSync(blockingFile, 'do not replace me');
  const out = path.join(blockingFile, 'cannot-write.html');

  const result = run(['deliver', 'architecture', input, out, '--json']);
  assert.equal(result.status, 1);
  const failure = JSON.parse(result.stdout);
  assert.equal(failure.ok, false);
  assert.equal(failure.stage, 'prepare');
  assert.match(failure.error, /Could not create delivery directory/);
  assert.equal(fs.readFileSync(blockingFile, 'utf8'), 'do not replace me');
});

test('cli: check validates rendered html', () => {
  const out = path.join(tmp, 'workflow-check.html');
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  assert.equal(run(['render', 'workflow', input, out]).status, 0);

  const result = run(['check', out]);
  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout, /"ok": true/);
});

test('cli: check rejects unknown options and extra positionals', () => {
  const out = path.join(tmp, 'workflow-check-args.html');
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  assert.equal(run(['render', 'workflow', input, out]).status, 0);

  const unknown = run(['check', '--json', out]);
  assert.equal(unknown.status, 2);
  assert.match(unknown.stderr, /Unknown check option "--json"/);

  const extra = run(['check', out, 'ignored-extra']);
  assert.equal(extra.status, 2);
  assert.match(extra.stderr, /Usage:/);
});

test('cli: validate emits structured json without keeping html output', () => {
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  const before = new Set(fs.readdirSync(tmp));
  const result = run(['validate', 'workflow', input, '--json']);
  assert.equal(result.status, 0, result.stderr);
  const parsed = JSON.parse(result.stdout);
  assert.equal(parsed.ok, true);
  assert.equal(parsed.type, 'workflow');
  assert.equal(parsed.checks.length, 9);
  assert.equal(parsed.composition.profile, 'showcase');
  assert.deepEqual(parsed.composition.summary, { errors: 0, warnings: 0 });
  assert.equal(parsed.composition.metrics.containerBorderRuns, 0);
  assert.equal(parsed.composition.metrics.ambiguousCorridors, 0);
  assert.deepEqual(new Set(fs.readdirSync(tmp)), before);
});

test('cli: validate JSON exposes only the primary v1 column-capacity diagnostic', () => {
  const input = path.join(tmp, 'pinned-column-capacity.workflow.json');
  fs.writeFileSync(input, `${JSON.stringify({
    schema_version: 1,
    diagram_type: 'workflow',
    meta: {
      title: 'Pinned issue 126 diagnostic boundary',
      viewBox: [720, 400],
      legend: { mode: 'hidden' },
    },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 1, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{
      id: 'ab',
      from: 'a',
      to: 'b',
      fromSide: 'top',
      toSide: 'top',
      via: [[220, 60], [300, 60]],
    }],
  }, null, 2)}\n`);

  const result = run(['validate', 'workflow', input, '--json'], {
    env: { ...process.env, ARCHIFY_DIAGNOSTIC_FORMAT: 'json' },
  });

  assert.equal(result.status, 1, result.stderr || result.stdout);
  assert.equal(result.stderr, '');
  const failure = JSON.parse(result.stdout);
  assert.equal(failure.ok, false);
  assert.equal(failure.command, 'validate');
  assert.equal(failure.stage, 'render');
  assert.equal(failure.type, 'workflow');
  assert.equal(failure.diagnostics.length, 1, JSON.stringify(failure.diagnostics, null, 2));
  const [primary] = failure.diagnostics;
  assert.equal(primary.code, 'workflow/column-capacity');
  assert.equal(primary.subject.edge, 'ab');
  assert.equal(primary.subject.fromCol, 1);
  assert.equal(primary.subject.toCol, 2);
  assert.ok(primary.supportedFixes.length > 0);
  assert.ok(failure.diagnostics.every(({ code }) => (
    code !== 'workflow/explicit-pin-conflict' && code !== 'workflow/viewbox-capacity'
  )));
});

test('cli: --quality overrides the source profile for render, validate, and deliver', () => {
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  const out = path.join(tmp, 'workflow-standard.html');
  const rendered = run(['render', 'workflow', input, out, '--quality', 'standard']);
  assert.equal(rendered.status, 0, rendered.stderr);
  assert.match(fs.readFileSync(out, 'utf8'), /data-quality-profile="standard"/);

  const validated = run(['validate', 'workflow', input, '--quality=standard', '--json']);
  assert.equal(validated.status, 0, validated.stderr);
  assert.equal(JSON.parse(validated.stdout).composition.profile, 'standard');

  const deliveredOut = path.join(tmp, 'workflow-delivered-standard.html');
  const delivered = run(['deliver', 'workflow', input, deliveredOut, '--quality=standard', '--json']);
  assert.equal(delivered.status, 0, delivered.stderr);
  assert.equal(JSON.parse(delivered.stdout).validation.compositionProfile, 'standard');
});

test('cli: rejects an unknown quality profile', () => {
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  const result = run(['validate', 'workflow', input, '--quality', 'hero']);
  assert.equal(result.status, 2);
  assert.match(result.stderr, /Expected standard or showcase/);
});

test('cli: rejects a quality flag without a value', () => {
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  for (const args of [
    ['validate', 'workflow', input, '--quality'],
    ['deliver', 'workflow', input, '--quality='],
    ['validate', 'workflow', input, '--quality='],
  ]) {
    const result = run(args);
    assert.equal(result.status, 2);
    assert.match(result.stderr, /--quality requires standard or showcase/);
  }
});

test('cli: validate rejects unknown flags, layout-json assignment typos, and extra positionals', () => {
  const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  const cases = [
    {
      args: ['validate', 'workflow', input, '--layout-json', '--bogus'],
      pattern: /Unknown validate option "--bogus"/,
    },
    {
      args: ['validate', 'workflow', input, '--layout-json=true'],
      pattern: /Unknown validate option "--layout-json=true"/,
    },
    {
      args: ['validate', 'workflow', input, 'unexpected-output.html', '--layout-json'],
      pattern: /Usage:/,
    },
  ];

  for (const { args, pattern } of cases) {
    const result = run(args);
    assert.equal(result.status, 2, `${args.join(' ')}\n${result.stderr}\n${result.stdout}`);
    assert.equal(result.stdout, '');
    assert.match(result.stderr, pattern);
  }
});

test('cli: validate and deliver keep argument failures machine-readable with --json', () => {
  const workflow = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
  const sequence = path.join(skillRoot, 'examples/cache-miss-request.sequence.json');
  const cases = [
    {
      args: ['validate', '--json'],
      command: 'validate',
      code: 'cli/usage',
    },
    {
      args: ['validate', '--json', 'workflow', workflow, '--quality', 'hero'],
      command: 'validate',
      code: 'cli/invalid-option-value',
      subject: { option: '--quality' },
    },
    {
      args: ['validate', 'workflow', workflow, '--quality', '--json'],
      command: 'validate',
      code: 'cli/missing-option-value',
      subject: { option: '--quality' },
    },
    {
      args: ['validate', 'workflow', workflow, '--quality=', '--json'],
      command: 'validate',
      code: 'cli/missing-option-value',
      subject: { option: '--quality' },
    },
    {
      args: ['validate', 'workflow', workflow, '--layout-json=true', '--json'],
      command: 'validate',
      code: 'cli/unknown-option',
      subject: { option: '--layout-json=true' },
    },
    {
      args: ['validate', '--bogus', 'payload', 'workflow', workflow, '--json'],
      command: 'validate',
      code: 'cli/unknown-option',
      subject: { option: '--bogus' },
    },
    {
      args: ['validate', 'workflow', workflow, 'unexpected-output.html', '--json'],
      command: 'validate',
      code: 'cli/usage',
    },
    {
      args: ['validate', 'unknown', workflow, '--json'],
      command: 'validate',
      code: 'cli/unknown-diagram-type',
      subject: { type: 'unknown' },
    },
    {
      args: ['validate', 'workflow', workflow, '--repo-root', '.', '--json'],
      command: 'validate',
      code: 'cli/unsupported-option',
      subject: { option: '--repo-root', type: 'workflow' },
    },
    {
      args: ['validate', 'architecture', workflow, '--repo-root=', '--json'],
      command: 'validate',
      code: 'cli/missing-option-value',
      subject: { option: '--repo-root' },
    },
    {
      args: ['validate', 'sequence', sequence, '--layout-json', '--json'],
      command: 'validate',
      code: 'cli/unsupported-option',
      subject: { option: '--layout-json', type: 'sequence' },
    },
    {
      args: ['deliver', '--json', 'workflow', workflow, '--bogus'],
      command: 'deliver',
      code: 'cli/unknown-option',
      subject: { option: '--bogus' },
    },
    {
      args: ['deliver', '--json'],
      command: 'deliver',
      code: 'cli/usage',
    },
    {
      args: ['deliver', 'workflow', workflow, '--repo-root', '--json'],
      command: 'deliver',
      code: 'cli/missing-option-value',
      subject: { option: '--repo-root' },
    },
    {
      args: ['deliver', 'unknown', workflow, '--json'],
      command: 'deliver',
      code: 'cli/unknown-diagram-type',
      subject: { type: 'unknown' },
    },
    {
      args: ['deliver', 'workflow', workflow, 'diagram.html', 'extra.html', '--json'],
      command: 'deliver',
      code: 'cli/usage',
    },
  ];

  for (const { args, command, code, subject = {} } of cases) {
    const result = run(args);
    assert.equal(result.status, 2, `${args.join(' ')}\n${result.stderr}\n${result.stdout}`);
    assert.equal(result.stderr, '');
    const failure = JSON.parse(result.stdout);
    assert.equal(failure.schemaVersion, 1);
    assert.equal(failure.ok, false);
    assert.equal(failure.command, command);
    assert.equal(failure.stage, 'arguments');
    assert.equal('type' in failure, false);
    assert.equal('input' in failure, false);
    assert.equal(failure.diagnostics.length, 1);
    assert.equal(failure.diagnostics[0].code, code);
    assert.equal(failure.diagnostics[0].severity, 'error');
    assert.deepEqual(failure.diagnostics[0].subject, { command, ...subject });
    assert.ok(failure.diagnostics[0].supportedFixes.length > 0);
    assert.equal('stack' in failure, false);
    assert.equal('stack' in failure.diagnostics[0], false);
  }
});

test('cli: inspect emits architecture layout json', () => {
  const input = path.resolve(skillRoot, '../examples/archify-repo-grid.architecture.json');
  const result = run(['inspect', 'architecture', input]);
  assert.equal(result.status, 0, result.stderr);
  const parsed = JSON.parse(result.stdout);
  assert.equal(parsed.ok, true);
  assert.equal(parsed.diagram_type, 'architecture');
  assert.equal(parsed.layout.mode, 'grid');
  assert.ok(parsed.components.length >= 5);
  assert.ok(parsed.connections.length >= 1);
});

test('cli: inspect remains architecture-only while workflow uses validate --layout-json', () => {
  const input = path.join(skillRoot, 'examples', 'agent-tool-call.workflow.json');
  const result = run(['inspect', 'workflow', input]);
  assert.equal(result.status, 2);
  assert.match(result.stderr, /inspect is currently supported for architecture diagrams only/);
  assert.equal(result.stdout, '');
});

test('cli: validate returns renderer errors for bad input', () => {
  const input = path.join(tmp, 'bad.workflow.json');
  const validateTmp = path.join(tmp, 'validate-failure-tmp');
  const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8'));
  doc.edges[0].to = 'ghost';
  fs.writeFileSync(input, JSON.stringify(doc));
  fs.mkdirSync(validateTmp);

  const result = run(['validate', 'workflow', input], {
    env: { ...process.env, TMPDIR: validateTmp },
  });
  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /unknown target "ghost"/);
  assert.deepEqual(fs.readdirSync(validateTmp), []);
});

test('cli: validate rejects an unknown type without leaking a temp directory', () => {
  const validateTmp = path.join(tmp, 'validate-unknown-type-tmp');
  fs.mkdirSync(validateTmp);

  const result = run(['validate', 'unknown', 'ignored.json'], {
    env: {
      ...process.env,
      TMPDIR: validateTmp,
      TMP: validateTmp,
      TEMP: validateTmp,
    },
  });

  assert.equal(result.status, 2);
  assert.match(result.stderr, /Unknown diagram type "unknown"/);
  assert.deepEqual(fs.readdirSync(validateTmp), []);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));

test('render rejects a mistyped option instead of writing a file named after it', () => {
  const dir = fs.mkdtempSync(path.join(tmp, 'render-guard-'));
  const spec = path.join(dir, 'spec.json');
  fs.copyFileSync(path.join(skillRoot, '../examples/archify-repo.architecture.json'), spec);

  // Without the guard this wrote a 600KB file literally named `--json` and
  // never wrote out.html, exiting 0.
  const result = run(['render', 'architecture', spec, '--json', 'out.html'], { cwd: dir });

  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /Unknown render option/);
  assert.deepEqual(fs.readdirSync(dir), ['spec.json']);
});

test('render rejects an extra positional argument', () => {
  const dir = fs.mkdtempSync(path.join(tmp, 'render-arity-'));
  const spec = path.join(dir, 'spec.json');
  fs.copyFileSync(path.join(skillRoot, '../examples/archify-repo.architecture.json'), spec);

  const result = run(['render', 'architecture', spec, 'out.html', 'extra.html'], { cwd: dir });

  assert.notEqual(result.status, 0);
  assert.deepEqual(fs.readdirSync(dir), ['spec.json']);
});
```

## test/community-proof-intake.test.mjs

```js
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(here, '../..');

function read(relativePath) {
  return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
}

test('showcase intake requires reproducible proof, redaction, and explicit publication permission', () => {
  const template = read('.github/ISSUE_TEMPLATE/showcase.yml');

  for (const field of [
    'id: diagram_type',
    'id: archify_version',
    'id: agent',
    'id: model',
    'id: prompt',
    'id: source_json',
    'id: artifact',
    'id: validation_receipt',
    'id: visual_review',
    'id: sensitive_data',
    'id: sharing_rights',
    'id: public_permission',
  ]) {
    assert.match(template, new RegExp(field), field);
  }
  assert.match(template, /access tokens/i);
  assert.match(template, /personal or customer data/i);
  assert.match(template, /repository, documentation, gallery, and project website/i);
  assert.match(template, /required:\s*true/g);

  const submissionUrl = 'https://github.com/tt-a1i/archify/issues/new?template=showcase.yml';
  for (const readme of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    assert.match(read(readme), new RegExp(submissionUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `${readme}: direct showcase link`);
  }
});

test('bug intake captures a minimal deterministic reproduction before visual diagnosis', () => {
  const template = read('.github/ISSUE_TEMPLATE/bug-report.yml');

  for (const field of [
    'id: archify_version',
    'id: install_method',
    'id: diagram_type',
    'id: command',
    'id: minimal_json',
    'id: validation_receipt',
    'id: expected',
    'id: actual',
    'id: visual_evidence',
    'id: environment',
    'id: sensitive_data',
  ]) {
    assert.match(template, new RegExp(field), field);
  }
  assert.ok(
    template.indexOf('id: validation_receipt') < template.indexOf('id: visual_evidence'),
    'deterministic evidence should be requested before visual evidence',
  );
  const evidenceBlock = template.slice(
    template.indexOf('id: visual_evidence'),
    template.indexOf('id: environment'),
  );
  assert.match(template, /type: upload\s+id: visual_evidence/);
  assert.match(evidenceBlock, /required:\s*false/);
  assert.match(evidenceBlock, /screenshots or recordings/i);
  assert.match(evidenceBlock, /Reproducible steps above may be enough/i);
  assert.doesNotMatch(evidenceBlock, /Required for visual problems/i);
  assert.doesNotMatch(evidenceBlock, /accept:/);
});

test('contributor and pull-request guides keep proof changes reproducible and stability-first', () => {
  const contributing = read('CONTRIBUTING.md');
  const pullRequest = read('.github/PULL_REQUEST_TEMPLATE.md');

  for (const required of [
    '.github/ISSUE_TEMPLATE/showcase.yml',
    '.github/ISSUE_TEMPLATE/bug-report.yml',
    'npm test',
    'node scripts/build-gallery.mjs docs',
    'Do not include secrets',
    'Agent-first',
    'diagnostics[]',
    'Start from the latest `main`',
    'tracked-only, symlink-safe',
    'is **skipped**, not passed',
  ]) {
    assert.match(contributing, new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), required);
  }
  assert.match(
    contributing,
    /(?:^|\n)scripts\/build-zip\.sh \/tmp\/archify-contrib\.zip(?:\n|$)/,
    'the archive builder must be documented as an executable shell script',
  );
  assert.doesNotMatch(
    contributing,
    /\bnode\s+scripts\/build-zip\.sh\b/,
    'the shell archive builder must not be documented as a Node.js command',
  );
  for (const required of [
    'Stability impact',
    'Tests run',
    'Generated artifacts',
    'Visual evidence',
    'Evidence provided:',
    'Automated or browser checks:',
    'Perceptual visual review: passed / failed / skipped / Not applicable',
    'No unrelated changes',
  ]) {
    assert.match(pullRequest, new RegExp(required), required);
  }
  assert.match(contributing, /enough evidence to evaluate whether the intended user value was achieved/i);
  assert.match(contributing, /screenshots, recordings, or reproducible steps/i);
  assert.match(contributing, /same input/i);
  assert.match(contributing, /automated or browser evidence separately from perceptual review/i);
  assert.match(contributing, /non-visual pull request must write `Not applicable`/i);
  assert.match(pullRequest, /screenshots, recordings, or reproducible steps/i);
  assert.match(pullRequest, /automated or browser evidence separately from perceptual review/i);
  assert.doesNotMatch(contributing, /must reach `passed` before final review or merge/i);
  assert.doesNotMatch(pullRequest, /must reach `passed` before final review or merge/i);
});
```

## test/cursor-onboarding.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const cursorCommand = 'npx -y skills add tt-a1i/archify --skill archify --agent cursor --global --copy --yes';

test('Cursor onboarding stays explicit, bilingual, and backed by the same Skill', () => {
  const english = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8');
  const englishMirror = fs.readFileSync(path.join(repoRoot, 'README_EN.md'), 'utf8');
  const chinese = fs.readFileSync(path.join(repoRoot, 'README_ZH.md'), 'utf8');
  const start = fs.readFileSync(path.join(repoRoot, 'docs', 'start.html'), 'utf8');
  const landing = fs.readFileSync(path.join(repoRoot, 'docs', 'index.html'), 'utf8');

  assert.equal(english, englishMirror, 'English README mirrors must stay synchronized');
  assert.match(english, /Cursor, Claude Code, Codex CLI, and OpenCode/);
  assert.match(chinese, /Cursor、Claude Code、Codex CLI 和 OpenCode/);
  for (const surface of [english, chinese, landing]) assert.ok(surface.includes(cursorCommand));
  for (const surface of [english, chinese, start, landing]) {
    assert.doesNotMatch(surface, /skills use[^\n<]*--agent cursor/);
    assert.doesNotMatch(surface, /~\/\.cursor\/skills\/archify/);
    assert.doesNotMatch(surface, /all Cursor models|every Cursor model/i);
  }

  assert.match(start, /data-agent="cursor">Cursor<\/button>/);
  assert.match(start, /data-agent="codex">Codex<\/button>/);
  assert.match(start, /data-agent="claude-code">Claude Code<\/button>/);
  assert.match(start, /data-agent="opencode">OpenCode<\/button>/);
  assert.match(start, /KNOWN_AGENTS\.has\(requestedAgent\)/);
  assert.match(start, /same Skill/);
  assert.match(start, /同一份 Skill/);
  assert.doesNotMatch(start, /vendor-specific (?:renderer|schema|skill)/i);
});

test('the zero-dependency archive works from the canonical Cursor-visible agent path', () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-cursor-package-'));
  const agentSkills = path.join(tmp, '.agents', 'skills');
  try {
    fs.mkdirSync(agentSkills, { recursive: true });
    execFileSync('unzip', ['-q', path.join(repoRoot, 'archify.zip'), '-d', agentSkills]);
    const installed = path.join(agentSkills, 'archify');
    const cli = path.join(installed, 'bin', 'archify.mjs');
    const doctor = execFileSync(process.execPath, [cli, 'doctor'], { encoding: 'utf8' });
    assert.match(doctor, /Archify is ready\./);

    const fixtures = {
      architecture: 'web-app.architecture.json',
      workflow: 'agent-tool-call.workflow.json',
      sequence: 'cache-miss-request.sequence.json',
      dataflow: 'product-analytics.dataflow.json',
      lifecycle: 'agent-run.lifecycle.json',
    };
    for (const [type, fixture] of Object.entries(fixtures)) {
      const output = execFileSync(process.execPath, [
        cli,
        'validate',
        type,
        path.join(installed, 'examples', fixture),
        '--json',
      ], { encoding: 'utf8' });
      const receipt = JSON.parse(output);
      assert.equal(receipt.ok, true, `${type}: installed package validation failed`);
      assert.equal(receipt.type, type);
    }
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});
```

## test/degraded.test.mjs

```js
// Installation contract: the shipped skill performs full JSON Schema
// validation without node_modules. AJV is a build-time dependency only; its
// standalone validators are committed and included in the distribution.
//
// A malformed-but-JSON-legal document must EXIT
// NON-ZERO with a friendly message — never crash (TypeError / is not a
// function) and never write NaN/undefined into the HTML. A random VALID
// perturbation of an example must still render (exit 0, no NaN).
//
//   node --test test/*.test.mjs

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-degraded-'));

const EXAMPLES = {
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
  architecture: 'web-app.architecture.json',
};

const installedRoot = path.join(tmp, 'installed-skill');
fs.cpSync(skillRoot, installedRoot, {
  recursive: true,
  filter(source) {
    const rel = path.relative(skillRoot, source);
    return rel !== 'node_modules' && !rel.startsWith(`node_modules${path.sep}`)
      && rel !== 'test' && !rel.startsWith(`test${path.sep}`)
      // The validator freshness test creates and removes this fixture inside
      // skillRoot while the test runner executes files concurrently. Exclude
      // it from the installed-skill copy to avoid a copy/remove race.
      && !rel.startsWith('.validator-check-');
  },
});

function render(mode, doc) {
  const input = path.join(tmp, `in-${Math.random().toString(36).slice(2)}.json`);
  const out = path.join(tmp, 'out.html');
  fs.writeFileSync(input, JSON.stringify(doc));
  if (fs.existsSync(out)) fs.rmSync(out);
  let code = 0;
  let stderr = '';
  try {
    execFileSync('node', [path.join(installedRoot, `renderers/${mode}/render-${mode}.mjs`), input, out],
      { stdio: ['ignore', 'ignore', 'pipe'] });
  } catch (err) {
    code = err.status ?? 1;
    stderr = String(err.stderr || '');
  }
  const html = fs.existsSync(out) ? fs.readFileSync(out, 'utf8') : '';
  return { code, stderr, html };
}

function assertFriendlyFailure(mode, doc, label) {
  const { code, stderr, html } = render(mode, doc);
  assert.notEqual(code, 0, `${label}: expected non-zero exit`);
  assert.doesNotMatch(stderr, /TypeError|RangeError|is not a function|Cannot read/,
    `${label}: crashed instead of reporting friendly error:\n${stderr}`);
  assert.doesNotMatch(html, /NaN|undefined/, `${label}: wrote NaN/undefined into HTML`);
}

// ---- type-wrong-but-JSON-legal documents per mode ----
const ARRAY_FIELDS = {
  workflow: ['lanes', 'phases', 'groups', 'mainPath', 'nodes', 'edges', 'cards'],
  sequence: ['participants', 'messages', 'segments', 'activations', 'cards'],
  dataflow: ['stages', 'nodes', 'flows', 'cards'],
  lifecycle: ['lanes', 'states', 'transitions', 'cards'],
  architecture: ['components', 'boundaries', 'connections', 'cards'],
};

for (const [mode, fields] of Object.entries(ARRAY_FIELDS)) {
  for (const field of fields) {
    test(`${mode}: ${field} as a string fails friendly`, () => {
      const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8'));
      if (!(field in doc)) return; // optional field absent in this example
      doc[field] = 'oops';
      assertFriendlyFailure(mode, doc, `${mode}.${field}`);
    });
  }
  test(`${mode}: scalar meta fails friendly`, () => {
    const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8'));
    doc.meta = 42;
    assertFriendlyFailure(mode, doc, `${mode}.meta`);
  });
}

// ---- missing-coordinate fields must not yield NaN coordinates ----
test('workflow: node missing col never writes NaN', () => {
  const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES.workflow), 'utf8'));
  delete doc.nodes[0].col;
  assertFriendlyFailure('workflow', doc, 'workflow node no col');
});
test('lifecycle: state missing col never writes NaN', () => {
  const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES.lifecycle), 'utf8'));
  delete doc.states[0].col;
  assertFriendlyFailure('lifecycle', doc, 'lifecycle state no col');
});

// ---- property test: deterministic VALID perturbations always render ----
// Seeded PRNG (no Math.random — keeps the test reproducible across runs).
function mulberry32(seed) {
  return function next() {
    seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

test('property: shuffling node/state order still renders (order-independence)', () => {
  for (const mode of ['workflow', 'dataflow', 'lifecycle']) {
    const arrKey = mode === 'lifecycle' ? 'states' : 'nodes';
    for (let seed = 1; seed <= 8; seed += 1) {
      const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8'));
      const rng = mulberry32(seed);
      // Fisher–Yates with the seeded PRNG.
      const a = doc[arrKey];
      for (let i = a.length - 1; i > 0; i -= 1) {
        const j = Math.floor(rng() * (i + 1));
        [a[i], a[j]] = [a[j], a[i]];
      }
      const { code, html } = render(mode, doc);
      assert.equal(code, 0, `${mode} seed ${seed}: valid shuffle should render (exit 0)`);
      assert.doesNotMatch(html, /NaN|undefined>/, `${mode} seed ${seed}: NaN in output`);
    }
  }
});

test('installed skill rejects unknown fields without node_modules', () => {
  const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES.workflow), 'utf8'));
  doc.nodes[0].colour = 'cyan';
  const { code, stderr } = render('workflow', doc);
  assert.notEqual(code, 0);
  assert.match(stderr, /workflow schema validation failed/);
  assert.match(stderr, /\/nodes\/0 \(id\/label: "user"\) must NOT have additional properties/);
  assert.match(stderr, /"additionalProperty":"colour"/);
  assert.doesNotMatch(stderr, /ajv is not installed|skipping JSON-schema validation/);
});

for (const mode of Object.keys(EXAMPLES)) {
  test(`installed skill retains full ${mode} schema without node_modules`, () => {
    const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8'));
    doc.unknownField = true;
    const { code, stderr } = render(mode, doc);
    assert.notEqual(code, 0);
    assert.match(stderr, new RegExp(`${mode} schema validation failed`));
    assert.match(stderr, /"additionalProperty":"unknownField"/);
  });
}

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/delivery-contract.test.mjs

```js
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import assert from 'node:assert/strict';

const here = path.dirname(fileURLToPath(import.meta.url));
const skill = readFileSync(path.join(here, '..', 'SKILL.md'), 'utf8');
const delivery = readFileSync(path.join(here, '..', 'references', 'delivery-contract.md'), 'utf8');

test('skill requires a bounded and truthful perceptual delivery receipt', () => {
  assert.match(delivery, /browser_evidence: passed\|failed\|skipped/);
  assert.match(delivery, /visual_review: passed/);
  assert.match(delivery, /visual_review: skipped \(image reader unavailable\)/);
  assert.match(delivery, /correction_rounds: [0-2]/);
  assert.match(delivery, /maximum of two focused correction rounds/i);
  assert.match(delivery, /never report `visual_review: passed` without inspecting/i);
});

test('skill keeps deterministic delivery, automated browser evidence, and perceptual review distinct', () => {
  for (const [name, source] of [['SKILL.md', skill], ['delivery contract', delivery]]) {
    assert.match(source, /deliver[\s\S]*deterministic/i, name);
    assert.match(source, /visual-check[\s\S]*automated browser evidence/i, name);
    assert.match(source, /human|perceptual visual review/i, name);
  }
  assert.match(delivery, /manual browser record[\s\S]*all four exact viewport measurements, both endpoint themes, and an artifact-bound record/i);
});

test('handoff browser evidence mirrors only the automated visual-check outcome', () => {
  assert.match(delivery, /`browser_evidence`[\s\S]*records only the outcome of this automated command/i);
  assert.match(delivery, /`passed`[\s\S]*exit 0[\s\S]*receipt `status: "pass"`/i);
  assert.match(delivery, /`failed`[\s\S]*exit 1[\s\S]*receipt `status: "fail"`/i);
  assert.match(delivery, /`skipped`[\s\S]*exit 2[\s\S]*receipt `status: "skipped"`/i);
  assert.match(delivery, /runtime or capture failures[\s\S]*must not be normalized to `skipped`/i);
  assert.match(delivery, /remains `skipped` even when[\s\S]*`visual_review: passed`/i);
  assert.match(delivery, /manual browser record[\s\S]*never changes `browser_evidence`/i);
});

test('skill uses atomic verified delivery for the final artifact', () => {
  assert.match(delivery, /archify\.mjs deliver <type>/);
  assert.match(delivery, /same-directory candidate/i);
  assert.match(delivery, /only replaces the target after.*artifact checks pass/i);
  assert.match(delivery, /never claim that the deterministic receipt includes visual review/i);
});

test('skill keeps optional opening behind the verified commit and outside automation', () => {
  assert.match(delivery, /Add `--open` only when the user wants an immediate local preview/);
  assert.match(delivery, /runs after that atomic commit/);
  assert.match(delivery, /Keep it off for CI, unattended agents, and non-interactive environments/);
  assert.match(delivery, /never invokes an opener/);
  assert.match(delivery, /status proves only whether the local opener invocation succeeded/);
});
```

## test/desktop-reader-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { findChrome, runVisualCheck } from '../bin/visual-check.mjs';
import { DESKTOP_READABILITY_VIEWPORT, MIN_PROJECTED_NODE_TEXT_PX } from '../renderers/shared/desktop-readability.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const chromePath = process.env.ARCHIFY_CHROME ? findChrome() : null;
const packagedHtmlExamples = fs.readdirSync(path.join(skillRoot, 'examples'))
  .filter((name) => name.endsWith('.html') && !name.endsWith('.visual-check.html'))
  .sort();

test('all packaged HTML examples pass the real visual-check desktop gate', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-packaged-examples-'));
  try {
    assert.ok(packagedHtmlExamples.length > 0, 'expected at least one packaged HTML example');
    for (const name of packagedHtmlExamples) {
      const artifact = path.join(tmp, name);
      fs.copyFileSync(path.join(skillRoot, 'examples', name), artifact);
      const result = await runVisualCheck({ artifactPath: artifact, chromePath });
      assert.equal(result.exitCode, 0, `${name}: ${JSON.stringify(result.receipt, null, 2)}`);
      assert.equal(result.receipt.containment.status, 'pass', name);
      assert.equal(result.receipt.containment.viewports.every((viewport) => viewport.ok), true, name);
    }
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('production showcase is readable in the real 1440 by 900 adaptive reader', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-desktop-reader-'));
  const artifact = path.join(tmp, 'production-deployment.html');
  try {
    execFileSync(process.execPath, [
      path.join(skillRoot, 'bin', 'archify.mjs'),
      'render',
      'architecture',
      path.join(skillRoot, 'examples', 'production-deployment.architecture.json'),
      artifact,
      '--quality',
      'showcase',
    ], { cwd: skillRoot, encoding: 'utf8' });

    for (let attempt = 1; attempt <= 3; attempt += 1) {
      const result = await runVisualCheck({ artifactPath: artifact, chromePath });
      assert.equal(result.exitCode, 0, `attempt ${attempt}: ${JSON.stringify(result.receipt, null, 2)}`);
      assert.equal(result.receipt.readability.status, 'pass', `attempt ${attempt}: ${JSON.stringify(result.receipt, null, 2)}`);
      const desktop = result.receipt.readability.viewports.find(({ width, height }) => (
        width === DESKTOP_READABILITY_VIEWPORT.width && height === DESKTOP_READABILITY_VIEWPORT.height
      ));
      const darkDesktop = result.receipt.captures.screenshots.find(({ width, height, theme }) => (
        width === DESKTOP_READABILITY_VIEWPORT.width
        && height === DESKTOP_READABILITY_VIEWPORT.height
        && theme === 'dark'
      ));
      for (const observation of [desktop, darkDesktop]) {
        assert.ok(observation);
        assert.equal(observation.readerWidth, 960);
        assert.equal(observation.diagramWidth, 930);
        assert.ok(observation.minimumProjectedNodeTextPx >= MIN_PROJECTED_NODE_TEXT_PX);
        assert.equal(observation.minimumProjectedNodeTextDetail, 'boundary');
        assert.equal(observation.minimumProjectedNodeText, 'AWS eu-west-1 / disaster recovery');
        assert.equal(observation.readabilityOk, true);
        assert.equal(observation.scrollHeight, DESKTOP_READABILITY_VIEWPORT.height);
      }
    }
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});
```

## test/diagram-guide.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-diagram-guide-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers inherit one viewer-only Diagram Guide', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="diagram-guide" hidden role="dialog" aria-modal="false" aria-labelledby="diagram-guide-title"/, mode);
    assert.match(html, /id="btn-diagram-guide"[^>]+aria-label="Open diagram guide"[^>]+aria-haspopup="dialog"[^>]+aria-expanded="false"/, mode);
    assert.match(html, /Archify\.guide = \(function \(\)/, mode);
    assert.match(html, /Diagram Guide — a factual command deck over existing interactions/, mode);
    assert.doesNotMatch(canonicalSvg(html), /diagram-guide|Archify\.guide|Explore this system/, mode);
  }
});

test('Diagram Guide reports compiled semantic facts and honest story availability', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /svg\.querySelectorAll\('\[data-node-id\]'\)\.length/);
  assert.match(html, /svg\.querySelectorAll\('\[data-edge-from\]\[data-edge-to\]'\)/);
  assert.match(html, /edge\.getAttribute\('data-edge-key'\)/);
  assert.match(html, /return Archify\.guidedViews && Number\(Archify\.guidedViews\.count\) \|\| 0/);
  assert.match(html, /storyBtn\.disabled = views === 0/);
  assert.match(html, /viewerCount\('viewer\.guide\.fact\.view', views\)/);
  assert.match(html, /viewerText\('viewer\.guide\.story\.unavailable'\)/);
});

test('Diagram Guide delegates its task rows to existing production interactions', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /if \(action === 'find'\) return Archify\.finder\.open\(\)/);
  assert.match(html, /if \(action === 'route'\) return Archify\.routeProbe\.begin\(\{ focusNode: true \}\)/);
  assert.match(html, /if \(action === 'map'\) return Archify\.radar\.open\(\)/);
  assert.match(html, /if \(action === 'story'\) return Archify\.guidedViews\.play\(\)/);
  assert.match(html, /if \(action === 'present'\) return Archify\.presentation\.enter\(\)/);
  assert.match(html, /if \(action === 'export'\) return Archify\.exportMenu\.open\(\)/);
  assert.match(html, /if \(action === 'theme'\) return Archify\.theme\.toggle\(\)/);
  assert.match(html, /if \(action === 'reset'\) return Archify\.view\.reset\(\)/);
  assert.match(html, /Archify\.guidedViews\.pause\(\)/);
  assert.match(html, /Archify\.finder\.close\(\{ restoreFocus: false \}\)/);
  assert.match(html, /Archify\.radar\.close\(\{ restoreFocus: false \}\)/);
  assert.match(html, /event\.stopPropagation\(\);[\s\S]+execute\(button\.getAttribute\('data-guide-action'\)\)/);
});

test('Diagram Guide is keyboard-first, mobile-contained, motion-safe, and embed-clean', () => {
  const html = render('sequence', CASES.sequence);
  assert.match(html, /e\.key === '\?'/);
  assert.match(html, /Archify\.guide\.toggle\(\)/);
  assert.match(html, /event\.key === 'ArrowRight'/);
  assert.match(html, /event\.key === 'ArrowDown'/);
  assert.match(html, /event\.key === 'Home'/);
  assert.match(html, /event\.key === 'End'/);
  assert.match(html, /event\.key === 'Escape' \|\| event\.key === '\?'/);
  assert.match(html, /html\.setAttribute\('data-guide-open', 'true'\)/);
  assert.match(html, /html\.getAttribute\('data-guide-open'\) === 'true'/);
  assert.match(html, /html\[data-embed="true"\] \.diagram-guide/);
  assert.match(html, /data-wide-diagram="true"\] \.diagram-guide/);
  assert.match(html, /\.route-probe\[data-guide-open="true"\]/);
  assert.match(html, /@media \(prefers-reduced-motion: reduce\)[\s\S]+\.diagram-guide/);
  assert.match(html, /class="diagram-guide no-print"/);
  assert.doesNotMatch(canonicalSvg(html), /data-guide-open|diagram-guide/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/engineering-profile.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import {
  deploymentOwnershipDiagnostics,
  validateEngineeringProfile,
} from '../renderers/shared/engineering-profiles.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const cli = path.join(skillRoot, 'bin', 'archify.mjs');
const examplePath = path.join(skillRoot, 'examples', 'production-deployment.architecture.json');
const example = JSON.parse(fs.readFileSync(examplePath, 'utf8'));

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

function validateJson(input, output) {
  return spawnSync(process.execPath, [cli, 'validate', 'architecture', input, '--json'], {
    cwd: path.dirname(output),
    encoding: 'utf8',
  });
}

test('deployment ownership profile passes the checked production example and stays opt-in', () => {
  assert.equal(example.meta.engineering_profile, 'deployment-ownership');
  assert.deepEqual(deploymentOwnershipDiagnostics(example), []);
  assert.doesNotThrow(() => validateEngineeringProfile('architecture', example));

  const ordinary = clone(example);
  delete ordinary.meta.engineering_profile;
  ordinary.boundaries = [];
  ordinary.components.forEach((component) => { delete component.tag; });
  assert.doesNotThrow(() => validateEngineeringProfile('architecture', ordinary));
});

test('deployment ownership profile reports exact owners, scopes, state, and crossing mechanisms', () => {
  const candidate = clone(example);
  delete candidate.components.find((component) => component.id === 'edge').tag;
  candidate.boundaries.find((boundary) => boundary.label.includes('us-east-1')).wraps =
    candidate.boundaries.find((boundary) => boundary.label.includes('us-east-1')).wraps.filter((id) => id !== 'edge');
  candidate.boundaries.find((boundary) => boundary.label === 'private application network').wraps =
    candidate.boundaries.find((boundary) => boundary.label === 'private application network').wraps.filter((id) => id !== 'redis');
  const crossing = candidate.connections.find((connection) => connection.from === 'gateway' && connection.to === 'api_a');
  crossing.label = '';

  const diagnostics = deploymentOwnershipDiagnostics(candidate);
  const codes = new Set(diagnostics.map((entry) => entry.code));
  assert.ok(codes.has('engineering/deployment-owner-missing'));
  assert.ok(codes.has('engineering/deployment-region-scope'));
  assert.ok(codes.has('engineering/deployment-private-state'));
  assert.ok(codes.has('engineering/deployment-crossing-mechanism'));

  const boundaryDiagnostic = diagnostics.find((entry) => entry.code === 'engineering/deployment-crossing-mechanism');
  assert.equal(boundaryDiagnostic.subject.collection, 'connections');
  assert.equal(boundaryDiagnostic.evidence.from, 'gateway');
  assert.equal(boundaryDiagnostic.evidence.to, 'api_a');
  assert.ok(boundaryDiagnostic.evidence.crossedBoundaries.some((boundary) => boundary.kind === 'security-group'));
  assert.deepEqual(boundaryDiagnostic.supportedFixes, [
    `set /connections/${boundaryDiagnostic.subject.index}/label to the real cross-boundary mechanism`,
  ]);
});

test('deployment ownership profile requires both region and private boundary kinds', () => {
  const candidate = clone(example);
  candidate.boundaries = candidate.boundaries.filter((boundary) => boundary.kind === 'region');
  const diagnostics = deploymentOwnershipDiagnostics(candidate);
  assert.ok(diagnostics.some((entry) => entry.code === 'engineering/deployment-boundary-kind'
    && entry.evidence.requiredKind === 'security-group'));
});

test('deployment ownership profile rejects ambiguous regions and cross-region private groups', () => {
  const candidate = clone(example);
  const secondRegion = candidate.boundaries.find((boundary) => boundary.label.includes('eu-west-1'));
  secondRegion.wraps.push('api_a');
  const diagnostics = deploymentOwnershipDiagnostics(candidate);
  assert.ok(diagnostics.some((entry) => entry.code === 'engineering/deployment-region-ambiguous'
    && entry.subject.id === 'api_a'));
  assert.ok(diagnostics.some((entry) => entry.code === 'engineering/deployment-private-region-consistency'
    && entry.subject.collection === 'boundaries'));
});

test('deployment ownership crossing math follows authored membership instead of geometry or labels', () => {
  const cases = [
    ['outside to region', 'clients', 'edge'],
    ['region to region', 'postgres', 'replica'],
    ['public to private', 'gateway', 'api_a'],
    ['private to public', 'worker', 'audit'],
  ];
  for (const [name, from, to] of cases) {
    const candidate = clone(example);
    candidate.connections.forEach((connection) => {
      connection.label ||= 'same-scope relation';
    });
    const connection = candidate.connections.find((entry) => entry.from === from && entry.to === to);
    connection.label = '';
    const crossings = deploymentOwnershipDiagnostics(candidate)
      .filter((diagnostic) => diagnostic.code === 'engineering/deployment-crossing-mechanism');
    assert.equal(crossings.length, 1, name);
    assert.equal(crossings[0].evidence.from, from, name);
    assert.equal(crossings[0].evidence.to, to, name);
  }

  const sameScope = clone(example);
  sameScope.connections.forEach((connection) => {
    connection.label ||= 'same-scope relation';
  });
  sameScope.connections.find((connection) => connection.from === 'api_a' && connection.to === 'redis').label = '';
  sameScope.connections.push({ from: 'api_a', to: 'api_a', label: '' });
  assert.ok(!deploymentOwnershipDiagnostics(sameScope)
    .some((diagnostic) => diagnostic.code === 'engineering/deployment-crossing-mechanism'));
});

test('other diagram modes reject the architecture-only engineering profile', () => {
  const fixtures = [
    ['workflow', 'agent-tool-call.workflow.json'],
    ['sequence', 'cache-miss-request.sequence.json'],
    ['dataflow', 'product-analytics.dataflow.json'],
    ['lifecycle', 'agent-run.lifecycle.json'],
  ];
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-engineering-schema-'));
  try {
    for (const [mode, fixture] of fixtures) {
      const candidate = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', fixture), 'utf8'));
      candidate.meta.engineering_profile = 'deployment-ownership';
      const input = path.join(tmp, `${mode}.json`);
      fs.writeFileSync(input, JSON.stringify(candidate));
      const result = spawnSync(process.execPath, [cli, 'validate', mode, input, '--json'], {
        cwd: tmp,
        encoding: 'utf8',
      });
      assert.notEqual(result.status, 0, mode);
      const receipt = JSON.parse(result.stdout);
      assert.ok(receipt.diagnostics.some((diagnostic) => diagnostic.code === 'schema/additionalProperties'), mode);
    }
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('validate and deliver expose one truthful engineering-profile receipt', () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-engineering-profile-'));
  try {
    const invalidPath = path.join(tmp, 'invalid.architecture.json');
    const invalid = clone(example);
    const crossing = invalid.connections.find((connection) => connection.from === 'gateway' && connection.to === 'api_a');
    crossing.label = '';
    fs.writeFileSync(invalidPath, JSON.stringify(invalid, null, 2));

    const preservedOutput = path.join(tmp, 'preserved.html');
    const preservedBytes = Buffer.from('last known good deployment');
    fs.writeFileSync(preservedOutput, preservedBytes);
    const failedDelivery = spawnSync(process.execPath, [
      cli, 'deliver', 'architecture', invalidPath, preservedOutput, '--json',
    ], { cwd: tmp, encoding: 'utf8' });
    assert.notEqual(failedDelivery.status, 0);
    assert.equal(fs.readFileSync(preservedOutput).equals(preservedBytes), true);

    const failed = validateJson(invalidPath, path.join(tmp, 'unused.html'));
    assert.notEqual(failed.status, 0);
    assert.equal(failed.stderr, '');
    const failure = JSON.parse(failed.stdout);
    assert.equal(failure.ok, false);
    assert.equal(failure.stage, 'render');
    assert.ok(failure.diagnostics.some((entry) => entry.code === 'engineering/deployment-crossing-mechanism'));

    const validated = spawnSync(process.execPath, [cli, 'validate', 'architecture', examplePath, '--json'], {
      cwd: tmp,
      encoding: 'utf8',
    });
    assert.equal(validated.status, 0, validated.stderr);
    assert.equal(JSON.parse(validated.stdout).engineeringProfile, 'deployment-ownership');

    const output = path.join(tmp, 'deployment.html');
    const delivered = spawnSync(process.execPath, [cli, 'deliver', 'architecture', examplePath, output, '--json'], {
      cwd: tmp,
      encoding: 'utf8',
    });
    assert.equal(delivered.status, 0, delivered.stderr);
    const receipt = JSON.parse(delivered.stdout);
    assert.equal(receipt.validation.engineeringProfile, 'deployment-ownership');
    assert.match(fs.readFileSync(output, 'utf8'), /data-engineering-profile="deployment-ownership"/);

    const secondOutput = path.join(tmp, 'deployment-second.html');
    const repeated = spawnSync(process.execPath, [
      cli, 'deliver', 'architecture', examplePath, secondOutput, '--json',
    ], { cwd: tmp, encoding: 'utf8' });
    assert.equal(repeated.status, 0, repeated.stderr);
    const digest = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
    assert.equal(digest(output), digest(secondOutput));

    const ordinaryInput = path.join(skillRoot, 'examples', 'web-app.architecture.json');
    const ordinaryOutput = path.join(tmp, 'ordinary.html');
    const ordinary = spawnSync(process.execPath, [
      cli, 'render', 'architecture', ordinaryInput, ordinaryOutput,
    ], { cwd: tmp, encoding: 'utf8' });
    assert.equal(ordinary.status, 0, ordinary.stderr);
    assert.doesNotMatch(fs.readFileSync(ordinaryOutput, 'utf8'), /data-engineering-profile=/);
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});
```

## test/export-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;

test('Export preserves menu, clipboard, semantic cards and recording lifecycles', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser Export checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-export-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_EXPORT_RUNTIME_EVIDENCE;
  const records = [];
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  t.after(() => { if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n'); });
  const input = path.join(scratch, 'motion.json');
  const file = path.join(scratch, 'motion.html');
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  source.meta.animation = 'trace';
  source.meta.visual_preset = 'signal-flow';
  fs.writeFileSync(input, JSON.stringify(source));
  execFileSync(process.execPath, [path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'), input, file]);
  const browser = new ChromeVisualBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  await send('Emulation.setFocusEmulationEnabled', { enabled: true });
  async function run(expression) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  // Instrument browser boundaries only: all serialization, drawing and encoding
  // remain production code. Each navigation restores the original environment.
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.exportErrors=[];window.exportAlerts=[];window.exportConsole=[];
    addEventListener('error',e=>exportErrors.push(e.message));
    addEventListener('unhandledrejection',e=>exportErrors.push(String(e.reason)));
    window.alert=message=>exportAlerts.push(message);
    console.error=(...args)=>exportConsole.push(args.map(String).join(' '));
    window.exportUrls=new Map();window.exportDownloads=[];window.exportTracks=[];window.exportCancelled=[];
    const create=URL.createObjectURL.bind(URL),revoke=URL.revokeObjectURL.bind(URL);
    URL.createObjectURL=blob=>{const url=create(blob);exportUrls.set(url,{blob,revoked:false});return url;};
    URL.revokeObjectURL=url=>{const item=exportUrls.get(url);if(item)item.revoked=true;revoke(url);};
    const click=HTMLAnchorElement.prototype.click;
    HTMLAnchorElement.prototype.click=function(){if(this.download){exportDownloads.push({name:this.download,blob:exportUrls.get(this.href).blob,attached:this.isConnected});return;}return click.call(this);};
    const capture=HTMLCanvasElement.prototype.captureStream;
    if(capture)HTMLCanvasElement.prototype.captureStream=function(...args){const stream=capture.apply(this,args);exportTracks.push(...stream.getTracks());return stream;};
    const cancel=window.cancelAnimationFrame;
    window.cancelAnimationFrame=id=>{exportCancelled.push(id);return cancel(id);};
    // Activation can survive asynchronous work. Observe the actual handler
    // stack so even a microtask cannot masquerade as synchronous construction.
    window.copyInClickHandler=false;
    const listen=EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener=function(type,listener,options){
      if(this.id==='export-menu'&&type==='click'&&typeof listener==='function'){
        const handler=listener;
        listener=function(event){
          copyInClickHandler=true;
          try{return handler.call(this,event);}finally{copyInClickHandler=false;}
        };
      }
      return listen.call(this,type,listener,options);
    };
    window.exportWait=predicate=>new Promise((resolve,reject)=>{const start=performance.now();function poll(){if(predicate())return resolve();if(performance.now()-start>12000)return reject(new Error('Export observation timed out'));setTimeout(poll,20);}poll();});
    const fault=new URL(location.href).searchParams.get('fault');
    if(fault==='unsupported'){window.MediaRecorder=undefined;window.ClipboardItem=undefined;HTMLCanvasElement.prototype.toDataURL=()=> 'data:image/png;base64,';}
  ` });
  async function load({ width = 1440, theme = 'dark', extra = '' } = {}) {
    await send('Emulation.setDeviceMetricsOverride', { width, height: 900, deviceScaleFactor: 1, mobile: false });
    await send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] });
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    await send('Page.navigate', { url: pathToFileURL(file).href + '?theme=' + theme + extra });
    await loaded;
    await run('document.fonts.ready');
    await run('Archify.readerLayout.whenStable()');
    await run('Archify.viewerChromeLayout.whenStable()');
  }
  async function key(key, code, windowsVirtualKeyCode) {
    await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode });
    await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode });
  }
  async function click(selector) {
    const point = await run(`(()=>{const r=document.querySelector(${JSON.stringify(selector)}).getBoundingClientRect();return {x:r.left+r.width/2,y:r.top+r.height/2};})()`);
    await send('Input.dispatchMouseEvent', { type: 'mousePressed', ...point, button: 'left', clickCount: 1 });
    await send('Input.dispatchMouseEvent', { type: 'mouseReleased', ...point, button: 'left', clickCount: 1 });
  }
  async function record(label) {
    const value = await run(`({open:Archify.exportMenu.isOpen(),expanded:document.getElementById('btn-export').getAttribute('aria-expanded'),
      active:document.activeElement.id||document.activeElement.dataset.action||document.activeElement.dataset.format,
      downloads:exportDownloads.map(d=>({name:d.name,type:d.blob.type,attached:d.attached})),
      urls:[...exportUrls.values()].map(u=>({type:u.blob.type,revoked:u.revoked})),tracks:exportTracks.map(t=>t.readyState),
      receipt:Object.fromEntries([...document.documentElement.attributes].filter(a=>a.name.startsWith('data-last-export-')&&!a.name.endsWith('-bytes')).map(a=>[a.name,a.value])),
      errors:exportErrors,alerts:exportAlerts,console:exportConsole,
      external:performance.getEntriesByType('resource').map(r=>r.name).filter(n=>/^https?:/.test(n))})`);
    assert.deepEqual(value.errors, [], label);
    assert.deepEqual(value.external, [], label);
    records.push({ label, ...value });
    return value;
  }
  const route = `Archify.routeProbe.begin({source:'users',focusNode:false});if(!Archify.routeProbe.choose('db',{updateUrl:false}))throw new Error('route fixture failed');`;
  const reach = `Archify.focus.set('api',{toggle:false,updateUrl:false});if(!Archify.focus.reach('downstream',{toggle:false,updateUrl:false,reveal:false}))throw new Error('reach fixture failed');`;

  await t.test('native menu input skips unavailable entries and preserves focus and mutual exclusion', async () => {
    for (const width of [390, 720, 1440]) {
      await load({ width, extra: '&fault=unsupported' });
      assert.deepEqual(await run('Object.keys(Archify.exportMenu).sort()'), ['close','copyShareCard','downloadReachShareCard','downloadRouteShareCard','isOpen','open','run','shareCard','syncReachShare','syncRouteShare'].sort());
      assert.deepEqual(await run('Object.keys(Archify.motion).sort()'), ['canRecord','recordWebm']);
      assert.deepEqual(await run(`[...document.querySelectorAll('#export-menu [data-format="jpeg"],#export-menu [data-format="webp"],#export-menu [data-format="webm"],#export-menu [data-action="copy"]')].map(e=>e.disabled)`), [true,true,true,true]);
      await run(`document.getElementById('btn-export').focus()`);
      await key('ArrowUp', 'ArrowUp', 38);
      const last = await run(`document.activeElement.dataset.format||document.activeElement.dataset.action`);
      await key('Home', 'Home', 36);
      const first = await run(`document.activeElement.dataset.format||document.activeElement.dataset.action`);
      assert.notEqual(first, last);
      await key('ArrowUp', 'ArrowUp', 38);
      assert.equal(await run(`document.activeElement.dataset.format||document.activeElement.dataset.action`), last);
      await key('ArrowDown', 'ArrowDown', 40);
      assert.equal(await run(`document.activeElement.dataset.format||document.activeElement.dataset.action`), first);
      await key('End', 'End', 35);
      assert.equal(await run(`document.activeElement.dataset.format||document.activeElement.dataset.action`), last);
      await key('Escape', 'Escape', 27);
      assert.equal((await record('escape-' + width)).active, 'btn-export');
      assert.equal(await run('Archify.exportMenu.isOpen()'), false);
      await click('#btn-export');
      await key('Tab', 'Tab', 9);
      assert.equal(await run('Archify.exportMenu.isOpen()'), false);
      await click('#btn-export');
      assert.equal(await run(`document.getElementById('export-menu').contains(document.elementFromPoint(5,5))`), false);
      await send('Input.dispatchMouseEvent', { type: 'mousePressed', x: 5, y: 5, button: 'left', clickCount: 1 });
      await send('Input.dispatchMouseEvent', { type: 'mouseReleased', x: 5, y: 5, button: 'left', clickCount: 1 });
      assert.equal(await run('Archify.exportMenu.isOpen()'), false);
      await run('Archify.preset.open();Archify.exportMenu.open()');
      assert.equal(await run('Archify.preset.isOpen()'), false);
      await run('Archify.semanticLens.open();Archify.exportMenu.open()');
      assert.equal(await run('Archify.semanticLens.isOpen()'), false);
      const state = await record('menu-' + width);
      assert.equal(state.open, true);assert.equal(state.expanded, 'true');assert.deepEqual(state.console, []);
    }
  });

  await t.test('auto-open and themed menu rendering keep stable layout', async () => {
    for (const theme of ['dark', 'light']) {
      await load({ theme, extra: '&openExport=1' });
      await run('exportWait(()=>Archify.exportMenu.isOpen())');
      const state = await record('auto-' + theme);
      assert.equal(state.expanded, 'true');assert.deepEqual(state.console, []);
      if (evidence) {
        await run(`Promise.all(document.getAnimations().filter(a=>Number.isFinite(a.effect.getTiming().iterations)).map(a=>a.finished.catch(()=>{})))`);
        const shot = await send('Page.captureScreenshot', { format: 'png' });
        fs.writeFileSync(path.join(evidence, theme + '-menu.png'), Buffer.from(shot.data, 'base64'));
      }
    }
  });

  await t.test('semantic card downloads retain snapshots, receipts and URL lifetime', async () => {
    for (const variant of ['route', 'reach']) {
      await load();await run(variant === 'route' ? route : reach);
      await run(`Archify.exportMenu.open();document.querySelector('[data-action="${variant}-share-card"]').focus()`);
      const result = await run(`(async()=>{let triggerFocus=0;const trigger=document.getElementById('btn-export');const onFocus=()=>triggerFocus++;trigger.addEventListener('focus',onFocus);const blob=await Archify.exportMenu.${variant === 'route' ? 'downloadRouteShareCard' : 'downloadReachShareCard'}();const image=await createImageBitmap(blob);const dimensions=[image.width,image.height];image.close();trigger.removeEventListener('focus',onFocus);return {dimensions,triggerFocus,type:blob.type};})()`);
      assert.deepEqual(result, { dimensions: [1200,630], triggerFocus: 0, type: 'image/png' });
      const live = await record(variant + '-download');
      assert.equal(live.receipt['data-last-export-variant'], variant);
      assert.equal(live.receipt['data-last-export-canonical'], 'false');
      assert.equal(live.receipt['data-last-export-' + variant + '-state-clean'], 'true');
      assert.ok(live.downloads[0].name.endsWith(variant === 'route' ? '-route-share-card.png' : '-downstream-reach-share-card.png'));
      assert.deepEqual(live.urls.map(u=>u.revoked), [true,false]);
      assert.equal(await run(`document.querySelectorAll('a[download]').length`), 0);
      await run('exportWait(()=>[...exportUrls.values()].every(u=>u.revoked))');
      assert.deepEqual((await record(variant + '-released')).urls.map(u=>u.revoked), [true,true]);
      await run(`Archify.exportMenu.open();${variant === 'route' ? 'Archify.routeProbe.clear()' : 'Archify.focus.clearReach()'};`);
      assert.equal(await run(`document.querySelector('[data-action="${variant}-share-card"]').hidden`), true);
      await run(`Archify.exportMenu.${variant === 'route' ? 'downloadRouteShareCard' : 'downloadReachShareCard'}()`);
      const missing = await record(variant + '-invalidated');
      assert.equal(missing.receipt['data-last-export-error-format'], 'share-card');
      assert.equal(missing.receipt['data-last-export-variant'], undefined);assert.equal(missing.alerts.length, 1);
      // An invalid provider snapshot must not be sanitized into a different route.
      await run(variant === 'route' ? route : reach);
      await run(`(()=>{const provider=${variant === 'route' ? 'Archify.routeProbe' : 'Archify.focus'};const method=${JSON.stringify(variant === 'route' ? 'exportSnapshot' : 'reachabilitySnapshot')};const snapshot=provider[method]();snapshot.nodeIds=['missing'];provider[method]=()=>snapshot;})()`);
      assert.equal(await run(`Archify.exportMenu.shareCard({variant:${JSON.stringify(variant)}}).then(()=>false,()=>true)`), true);
      assert.equal((await record(variant + '-malformed')).downloads.length, 1);
    }
  });

  await t.test('clipboard keeps promise construction in the click and distinct fallback/error receipts', async () => {
    for (const action of ['copy','copy-share-card']) for (const mode of ['promise','fallback','reject']) {
      await load();
      await run(`window.copyCalls=[];window.copyDone=false;window.copyBlob=null;
        window.ClipboardItem=class {constructor(data){const value=data['image/png'];copyCalls.push({promise:value instanceof Promise,gesture:navigator.userActivation.isActive,inClickHandler:copyInClickHandler});if(${JSON.stringify(mode)}==='fallback'&&value instanceof Promise)throw new Error('promise unsupported');this.value=value;}};
        Object.defineProperty(navigator,'clipboard',{configurable:true,value:{write(items){copyCalls.push({write:true});if(${JSON.stringify(mode)}==='reject'){copyDone=true;return Promise.reject(new Error('clipboard denied'));}return Promise.resolve(items[0].value).then(blob=>{copyBlob=blob;copyDone=true;});}}});
        document.querySelector('[data-action="${action}"]').disabled=false;Archify.exportMenu.open();`);
      await click(`[data-action="${action}"]`);
      await run(`exportWait(()=>copyDone&&(${JSON.stringify(mode)}==='reject'?exportAlerts.length>0:${JSON.stringify(action)}==='copy-share-card'?document.documentElement.hasAttribute('data-last-export-format'):document.querySelector('.archify-toast').textContent.length>0))`);
      const calls = await run('copyCalls');
      assert.deepEqual(calls[0], { promise: true, gesture: true, inClickHandler: true });
      if (mode === 'fallback') assert.equal(calls[1].inClickHandler, false, 'Blob fallback remains asynchronous');
      assert.deepEqual(calls.map(c=>c.write?'write':c.promise?'promise':'blob'), mode === 'fallback' ? ['promise','blob','write'] : ['promise','write']);
      const state = await record(action + '-' + mode);
      assert.equal(state.active, 'btn-export');
      if (mode !== 'reject') {
        assert.equal(await run('copyBlob.type'), 'image/png');
        const dims = await run('(async()=>{const b=await createImageBitmap(copyBlob);const size=[b.width,b.height];b.close();return size;})()');
        if (action === 'copy-share-card') assert.deepEqual(dims,[1200,630]);
        else assert.ok(dims[0] > 1200);
        assert.equal(state.receipt['data-last-export-format'], action === 'copy-share-card' ? 'share-card' : undefined);
      } else {
        assert.equal(state.alerts.length,1);
        assert.equal(state.receipt['data-last-export-error-format'], action === 'copy-share-card' ? 'share-card' : undefined);
      }
      await run('exportWait(()=>[...exportUrls.values()].every(u=>u.revoked))');
    }
    await load({extra:'&fault=unsupported'});
    assert.equal(await run('Archify.exportMenu.copyShareCard() === undefined'), true);
    assert.equal((await record('clipboard-unavailable')).alerts.length, 1);
  });

  await t.test('raster failures release sources and retain retry and synchronous SVG behavior', async () => {
    for (const fault of ['image','context','null-blob']) {
      await load();
      await run(`window.savedImage=Image;window.savedContext=HTMLCanvasElement.prototype.getContext;window.savedToBlob=HTMLCanvasElement.prototype.toBlob;`);
      if (fault === 'image') await run(`window.Image=class {set src(value){queueMicrotask(()=>this.onerror(new Error('image failed')));}}`);
      if (fault === 'context') await run('HTMLCanvasElement.prototype.getContext=()=>null');
      if (fault === 'null-blob') await run('HTMLCanvasElement.prototype.toBlob=function(callback){callback(null)}');
      await run(`Archify.exportMenu.run('png')`);
      const state = await record('raster-' + fault);
      assert.equal(state.receipt['data-last-export-error-format'],'png');assert.equal(state.alerts.length,1);
      assert.equal(state.urls.length,1);assert.equal(state.urls[0].revoked,true);assert.deepEqual(state.downloads,[]);
      await run(`window.Image=savedImage;HTMLCanvasElement.prototype.getContext=savedContext;HTMLCanvasElement.prototype.toBlob=savedToBlob;Archify.exportMenu.run('png')`);
      await run('exportWait(()=>exportDownloads.length===1)');
      assert.equal((await record('retry-' + fault)).receipt['data-last-export-error-format'],undefined);
    }
    await load();
    const sync = await run(`(()=>{const svg=document.querySelector('.diagram-container > svg'),clone=svg.cloneNode;svg.cloneNode=()=>{throw new Error('sync serialization')};try{Archify.exportMenu.run('svg');return false;}catch(e){return e.message==='sync serialization';}finally{svg.cloneNode=clone;}})()`);
    assert.equal(sync,true);assert.deepEqual((await record('svg-sync-throw')).receipt,{});
  });

  await t.test('recording succeeds with real encoding and releases tracks and the background URL', async () => {
    await load();assert.equal(await run('Archify.motion.canRecord()'),true);
    const result = await run(`(async()=>{const blob=await Archify.motion.recordWebm({duration:500,fps:10});window.recordedBlob=blob;const url=URL.createObjectURL(blob),video=document.createElement('video');video.muted=true;video.src=url;await new Promise((resolve,reject)=>{video.onloadeddata=resolve;video.onerror=()=>reject(new Error('WebM decode failed'));});const dimensions=[video.videoWidth,video.videoHeight];await video.play();await new Promise(resolve=>video.requestVideoFrameCallback(resolve));video.pause();video.removeAttribute('src');video.load();URL.revokeObjectURL(url);return {type:blob.type,nonempty:blob.size>0,dimensions,cancelled:exportCancelled.length>0};})()`);
    assert.match(result.type,/^video\/webm/);assert.equal(result.nonempty,true);assert.equal(result.cancelled,true);assert.ok(result.dimensions.every(n=>n>0&&n%2===0));
    const state = await record('webm-real');assert.ok(state.tracks.length>0);assert.ok(state.tracks.every(s=>s==='ended'));assert.ok(state.urls.every(u=>u.revoked));assert.deepEqual(state.console,[]);
    if(evidence){const bytes=await run(`(async()=>Array.from(new Uint8Array(await recordedBlob.arrayBuffer())))()`);fs.writeFileSync(path.join(evidence,'recording.webm'),Buffer.from(bytes));}
  });

  await t.test('menu recording writes its receipt and downloads the real default-duration WebM', async () => {
    await load();await run('Archify.exportMenu.open()');
    await click('[data-format="webm"]');
    await run('exportWait(()=>exportDownloads.length===1)');
    const state=await record('webm-download');
    assert.equal(state.active,'btn-export');assert.equal(state.receipt['data-last-export-format'],'webm');
    assert.equal(state.receipt['data-last-export-canonical'],'true');assert.deepEqual(state.console,[]);
    assert.ok(state.downloads[0].name.endsWith('.webm'));assert.match(state.downloads[0].type,/^video\/webm/);
    assert.equal(await run('Number(document.documentElement.dataset.lastMotionBytes)===exportDownloads[0].blob.size&&exportDownloads[0].blob.size>0'),true);
    assert.ok(state.tracks.length>0);assert.ok(state.tracks.every(s=>s==='ended'));
    assert.deepEqual(state.urls.map(u=>u.revoked),[true,false]);
    await run('exportWait(()=>[...exportUrls.values()].every(u=>u.revoked))');
  });

  await t.test('recording constructor/error/empty failures clean up and public run disables WebM', async () => {
    for (const fault of ['constructor','error','empty']) {
      await load();
      await run(`window.MediaRecorder=class {
        static isTypeSupported(){return true;}
        constructor(){if(${JSON.stringify(fault)}==='constructor')throw new Error('recorder constructor');this.state='inactive';this.mimeType='video/webm';}
        start(){this.state='recording';if(${JSON.stringify(fault)}==='error')setTimeout(()=>{this.state='inactive';this.onerror({error:new Error('recorder error')});},20);}
        requestData(){} stop(){this.state='inactive';this.onstop();}
      };`);
      assert.equal(await run('Archify.motion.recordWebm({duration:250,fps:10}).then(()=>false,()=>true)'),true);
      const state=await record('webm-'+fault);assert.ok(state.urls.every(u=>u.revoked));assert.ok(state.tracks.length>0);assert.ok(state.tracks.every(s=>s==='ended'));
    }
    await load({extra:'&fault=unsupported'});
    await run(`Archify.exportMenu.run('webm')`);
    const state=await record('webm-unavailable');assert.equal(state.receipt['data-last-export-error-format'],'webm');assert.deepEqual(state.alerts,[]);
    assert.deepEqual(await run(`(()=>{const b=document.querySelector('[data-format="webm"]');return {disabled:b.disabled,opacity:b.style.opacity};})()`),{disabled:true,opacity:'0.5'});
  });
});
```

## test/export-cleanup-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;
const cases = {
  architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

test('Export cleanup preserves canonical artifacts and live interaction state', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser export checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-export-cleanup-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_EXPORT_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const records = [];
  t.after(() => {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n');
  });
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    const file = path.join(scratch, `${mode}.html`);
    if (process.env.ARCHIFY_EXPORT_BASELINE_DIR) {
      fs.copyFileSync(path.join(process.env.ARCHIFY_EXPORT_BASELINE_DIR, `${mode}.html`), file);
    } else {
      execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
        path.join(skillRoot, 'examples', example), file]);
    }
    files[mode] = file;
  }
  const browser = new ChromeVisualBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `window.exportTestErrors = [];
    addEventListener('error', e => exportTestErrors.push(e.message));
    addEventListener('unhandledrejection', e => exportTestErrors.push(String(e.reason)));` });
  async function evaluate(expression, awaitPromise = false) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  async function load(file, theme = 'dark', reduced = false) {
    await send('Emulation.setDeviceMetricsOverride', { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
    await send('Emulation.setEmulatedMedia', { features: [
      { name: 'prefers-color-scheme', value: theme },
      { name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' },
    ] });
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    const result = await send('Page.navigate', { url: pathToFileURL(file).href + `?theme=${theme}` });
    assert.equal(result.errorText, undefined);
    await loaded;
    await evaluate('(async () => { await document.fonts.ready; await Archify.readerLayout.whenStable(); await Archify.viewerChromeLayout.whenStable(); })()', true);
    assert.deepEqual(await evaluate('exportTestErrors'), []);
  }
  async function exported(label, action = '') {
    // Capture the SVG synchronously at the public export call. Timers and
    // animation frames cannot explain away a live-DOM mutation by cleanup.
    const value = await evaluate(`(async () => {
      ${action}
      const svg = document.querySelector('.diagram-container > svg');
      const before = svg.outerHTML;
      const original = URL.createObjectURL;
      let blob;
      URL.createObjectURL = function (value) {
        if (value.type.startsWith('image/svg+xml')) blob = value;
        return original.call(URL, value);
      };
      let after;
      try {
        const pending = Archify.exportMenu.run('svg');
        after = svg.outerHTML;
        await pending;
      } finally { URL.createObjectURL = original; }
      if (!blob) throw new Error('Export did not produce SVG');
      const text = await blob.text();
      const root = new DOMParser().parseFromString(text, 'image/svg+xml').documentElement;
      const transient = root.querySelectorAll('[data-focus-selected], [data-reach-match], [data-lens-selected], [data-intent-trace-overlay], [data-route-probe-overlay], [data-route-journey-overlay], [data-story-overlay], [data-chapter-preview-role], [data-source-evidence-beacon], [data-relationship-hit-overlay]');
      return { text, liveUnchanged: before === after, transientCount: transient.length,
        geometry: root.getAttribute('viewBox'), liveGeometry: svg.getAttribute('viewBox'),
        rootClean: !['data-view-scale', 'data-focus-active', 'data-reach-active', 'data-route-active', 'data-lens-active', 'data-story-active', 'data-share-route', 'data-share-reach'].some(a => root.hasAttribute(a)),
        resources: performance.getEntriesByType('resource').map(e => e.name).filter(n => /^https?:/.test(n)), errors: exportTestErrors };
    })()`, true);
    assert.equal(value.liveUnchanged, true, `${label}: live SVG changed`);
    assert.equal(value.transientCount, 0, label);
    assert.equal(value.rootClean, true, label);
    assert.equal(value.geometry, value.liveGeometry, label);
    assert.deepEqual(value.resources, [], label);
    assert.deepEqual(value.errors, [], label);
    if (evidence) fs.writeFileSync(path.join(evidence, `${label}.svg`), value.text);
    const { text, ...observation } = value;
    records.push({ label, ...observation });
    return text;
  }

  await t.test('five modes export standalone SVG in both themes and reduced motion', async () => {
    for (const [mode, file] of Object.entries(files)) {
      for (const theme of ['dark', 'light']) {
        await load(file, theme, theme === 'light');
        await exported(`${mode}-${theme}`);
      }
    }
  });

  await t.test('real zoom, focus, preview, lens, story and route actions leave exports clean', async () => {
    await load(files.architecture);
    const pristine = await exported('interaction-pristine');
    const actions = {
      zoom: `Archify.view.zoomIn();`,
      focus: `if (!Archify.focus.set('api', { toggle: false, updateUrl: false })) throw new Error('focus failed');`,
      preview: `if (!Archify.intentTrace.show('api')) throw new Error('preview failed');`,
      lens: `Archify.semanticLens.select('backend'); if (!Archify.semanticLens.active()) throw new Error('lens failed');`,
      story: `Archify.guidedViews.activate('request-path', { updateUrl: false }); Archify.guidedViews.playCurrent(); if (!Archify.guidedViews.isPlaying()) throw new Error('story failed');`,
      route: `Archify.routeProbe.begin({ source: 'users', focusNode: false }); if (!Archify.routeProbe.choose('db', { updateUrl: false })) throw new Error('route failed');`,
      upstream: `Archify.focus.set('api', { toggle: false, updateUrl: false }); if (!Archify.focus.reach('upstream', { toggle: false, updateUrl: false, reveal: false })) throw new Error('reach failed');`,
      downstream: `Archify.focus.set('api', { toggle: false, updateUrl: false }); if (!Archify.focus.reach('downstream', { toggle: false, updateUrl: false, reveal: false })) throw new Error('reach failed');`,
    };
    for (const [name, action] of Object.entries(actions)) {
      await load(files.architecture);
      const text = await exported(`active-${name}`, action);
      // Existing style.removeProperty calls can leave empty style attributes.
      // Apart from that inert serialization detail, compare the entire SVG.
      assert.equal(text.replace(/ style=""/g, ''), pristine.replace(/ style=""/g, ''), name);
    }
  });

  await t.test('raster and share-card exports preserve dimensions and current theme', async () => {
    for (const theme of ['dark', 'light']) {
      await load(files.architecture, theme, theme === 'light');
      for (const format of ['png', 'jpeg', 'webp', 'share-card']) {
        const result = await evaluate(`(async () => {
          Archify.focus.set('api', { toggle: false, updateUrl: false });
          const format = ${JSON.stringify(format)};
          const original = URL.createObjectURL;
          let blob;
          URL.createObjectURL = function (value) {
            if (value.type.startsWith('image/') && value.type !== 'image/svg+xml') blob = value;
            return original.call(URL, value);
          };
          try {
            if (format === 'share-card') blob = await Archify.exportMenu.shareCard();
            else await Archify.exportMenu.run(format);
          } finally { URL.createObjectURL = original; }
          if (!blob) throw new Error('Missing raster export');
          const bitmap = await createImageBitmap(blob);
          const dimensions = [bitmap.width, bitmap.height];
          bitmap.close();
          const svg = document.querySelector('.diagram-container > svg');
          const expected = format === 'share-card' ? [1200, 630] : [svg.viewBox.baseVal.width * 4, svg.viewBox.baseVal.height * 4];
          const data = await new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => resolve(reader.result.split(',')[1]);
            reader.onerror = reject;
            reader.readAsDataURL(blob);
          });
          return { data, dimensions, expected, type: blob.type, errors: exportTestErrors };
        })()`, true);
        assert.deepEqual(result.dimensions, result.expected);
        assert.equal(result.type, format === 'share-card' ? 'image/png' : `image/${format}`);
        assert.deepEqual(result.errors, []);
        const label = `${theme}-${format}`;
        if (evidence) fs.writeFileSync(path.join(evidence, `${label}.${format === 'share-card' ? 'png' : format}`), Buffer.from(result.data, 'base64'));
        const { data, ...observation } = result;
        records.push({ label, ...observation });
      }
    }
  });

  // Private checks supplement the real public exports. The hook exists only in
  // this disposable test artifact; production receives no testing interface.
  const html = fs.readFileSync(files.architecture, 'utf8');
  let cleanup = html.match(/function cleanExportClone\(clone\) \{[\s\S]*?\n      \}/)?.[0];
  if (!cleanup && process.env.ARCHIFY_EXPORT_BASELINE_DIR) {
    const start = html.indexOf('        // View transforms and neighborhood focus');
    const end = html.indexOf('        var vb = svg.viewBox.baseVal;', start);
    assert.ok(start >= 0 && end > start);
    cleanup = `function cleanExportClone(clone) {\n${html.slice(start, end)}return canonicalStateClean;\n}`;
  }
  assert.ok(cleanup, 'the extracted cleanup implementation is present');
  const cleanupReference = html.includes('function cleanExportClone(clone)') ? 'cleanExportClone' : cleanup;
  const hooked = html.replace('      function download(blob, filename) {',
    `      window.exportCleanupTest = { serialize: serializeSvg, clean: ${cleanupReference} };\n      function download(blob, filename) {`);
  assert.notEqual(hooked, html);
  const privateFile = path.join(scratch, 'private.html');
  fs.writeFileSync(privateFile, hooked);

  await t.test('clone restoration, preservation and repeated cleanup are independent of live DOM', async () => {
    await load(privateFile);
    const result = await evaluate(`(() => {
      const svg = document.querySelector('.diagram-container > svg');
      const before = svg.outerHTML;
      const clone = svg.cloneNode(true);
      // A small authored fragment is the independent preservation oracle.
      const group = document.createElementNS(svg.namespaceURI, 'g');
      group.setAttribute('id', 'authored-preservation');
      group.setAttribute('data-node-id', 'authored');
      group.setAttribute('data-animate', 'node');
      group.innerHTML = '<path d="M 0 0 L 10 20" transform="translate(3 4)"/><text>Keep 中文 &amp; text</text>';
      clone.appendChild(group);
      const authored = group.outerHTML;
      const labels = ['original', '', null].map((value, i) => {
        const node = document.createElementNS(svg.namespaceURI, 'g');
        node.setAttribute('id', 'restore-' + i);
        node.setAttribute('aria-label', 'runtime label');
        node.setAttribute('data-source-evidence-count', '2');
        if (value !== null) node.setAttribute('data-source-evidence-original-label', value);
        clone.appendChild(node);
        return node;
      });
      clone.setAttribute('data-share-route', 'true');
      clone.setAttribute('data-share-reach', 'upstream');
      group.setAttribute('data-share-route-match', '');
      group.setAttribute('data-share-reach-match', '');
      const clean = exportCleanupTest.clean(clone);
      const once = clone.outerHTML;
      const twiceClean = exportCleanupTest.clean(clone);
      const empty = document.createElementNS(svg.namespaceURI, 'svg');
      empty.setAttribute('viewBox', '0 0 100 50');
      const emptyBefore = empty.outerHTML;
      const emptyClean = exportCleanupTest.clean(empty);
      const orphan = document.createElementNS(svg.namespaceURI, 'g');
      orphan.setAttribute('data-story-carrier-token', 'true');
      const residual = empty.cloneNode(true);
      residual.appendChild(orphan);
      const residualClean = exportCleanupTest.clean(residual);
      return { clean, twiceClean, unchanged: before === svg.outerHTML, idempotent: once === clone.outerHTML,
        authored: group.outerHTML === authored, labels: labels.map(n => n.getAttribute('aria-label')),
        restorationRecords: clone.querySelectorAll('[data-source-evidence-count], [data-source-evidence-original-label]').length,
        shares: clone.hasAttribute('data-share-route') || clone.hasAttribute('data-share-reach'),
        residualClean, emptyClean, emptyUnchanged: emptyBefore === empty.outerHTML };
    })()`);
    assert.deepEqual(result, { clean: true, twiceClean: true, unchanged: true, idempotent: true,
      authored: true, labels: ['original', null, null], restorationRecords: 0, shares: false,
      residualClean: false, emptyClean: true, emptyUnchanged: true });
    records.push({ label: 'restoration', ...result });
  });

  await t.test('share decoration follows cleanup and preserves snapshot rejection behavior', async () => {
    for (const variant of ['route', 'upstream', 'downstream']) {
      await load(privateFile);
      const result = await evaluate(`(() => {
        const variant = ${JSON.stringify(variant)};
        let snapshot;
        if (variant === 'route') {
          Archify.routeProbe.begin({ source: 'users', focusNode: false });
          Archify.routeProbe.choose('db', { updateUrl: false });
          snapshot = Archify.routeProbe.exportSnapshot();
        } else {
          Archify.focus.set('api', { toggle: false, updateUrl: false });
          Archify.focus.reach(variant, { toggle: false, updateUrl: false, reveal: false });
          snapshot = Archify.focus.reachabilitySnapshot();
        }
        if (!snapshot) throw new Error('missing ' + variant + ' snapshot');
        const key = variant === 'route' ? 'routeSnapshot' : 'reachSnapshot';
        const field = variant === 'route' ? 'routeStateClean' : 'reachStateClean';
        const svg = document.querySelector('.diagram-container > svg');
        const before = svg.outerHTML;
        const data = exportCleanupTest.serialize(1, { [key]: snapshot });
        const rejected = exportCleanupTest.serialize(1, { [key]: { ...snapshot, nodeIds: [] } });
        const viewBox = svg.getAttribute('viewBox');
        svg.setAttribute('viewBox', '0 0 0 0');
        const invalidDimensions = exportCleanupTest.serialize(1, { [key]: snapshot });
        svg.setAttribute('viewBox', viewBox);
        const root = new DOMParser().parseFromString(data.svgString, 'image/svg+xml').documentElement;
        return { text: data.svgString, canonical: data.canonicalStateClean, accepted: data[field],
          rejected: rejected[field], invalidDimensions: invalidDimensions[field], unchanged: before === svg.outerHTML,
          route: root.hasAttribute('data-share-route'), reach: root.getAttribute('data-share-reach'),
          ids: [...root.querySelectorAll('[data-share-route-match][data-node-id], [data-share-reach-match][data-node-id]')].map(n => n.getAttribute('data-node-id')).sort(),
          expectedIds: snapshot.nodeIds.slice().sort() };
      })()`);
      assert.equal(result.canonical, true);
      assert.equal(result.accepted, true);
      assert.equal(result.rejected, false);
      assert.equal(result.invalidDimensions, false);
      assert.equal(result.unchanged, true);
      assert.equal(result.route, variant === 'route');
      assert.equal(result.reach, variant === 'route' ? null : variant);
      assert.deepEqual(result.ids, result.expectedIds);
      if (evidence) fs.writeFileSync(path.join(evidence, `share-${variant}.svg`), result.text);
      const { text, ...observation } = result;
      records.push({ label: `share-${variant}`, ...observation });
    }
  });
});
```

## test/finder-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';
import { createViewerClick } from './helpers/viewer-click.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;

test('Finder preserves search, keyboard, contextual Route selection and cleanup', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser Finder checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-finder-browser-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_FINDER_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const records = [];
  t.after(() => {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n');
  });
  const cases = {
    architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    files[mode] = path.join(scratch, mode + '.html');
    execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      path.join(skillRoot, 'examples', example), files[mode]]);
  }
  // A controlled metadata fixture exercises the Finder's input boundary;
  // repository verification and brand rendering have their own tests.
  files.metadata = path.join(scratch, 'metadata.html');
  const metadataSource = fs.readFileSync(files.architecture, 'utf8');
  assert.ok(metadataSource.includes('    Archify.finder = (function () {'), 'Finder fixture anchor');
  fs.writeFileSync(files.metadata, metadataSource.replace(
    '    Archify.finder = (function () {', `
    document.querySelector('[data-node-id="api"]').setAttribute('data-node-brand', 'finder-brand-token');
    var finderOriginalSources = Archify.sourceEvidence.node;
    Archify.sourceEvidence.node = function (id) {
      return id === 'api' ? [{path:'src/finder-proof.js',label:'Finder proof',line:12,endLine:18}] : finderOriginalSources(id);
    };
    Archify.finder = (function () {`));
  const browser = new ChromeVisualBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  // Match the focused-page setup used by the WebM browser harness so that
  // programmatic SVG focus also dispatches native focusin/focusout events.
  await send('Emulation.setFocusEmulationEnabled', { enabled: true });
  async function run(expression) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  const click = await createViewerClick({ send, run, timeout: 10000 });
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.finderErrors = [];
    addEventListener('error', e => finderErrors.push(e.message));
    addEventListener('unhandledrejection', e => finderErrors.push(String(e.reason)));
    window.finderWait = (predicate, description = 'Finder observation') => new Promise((resolve, reject) => {
      const start = performance.now();
      function sample() {
        if (predicate()) return resolve();
        if (performance.now()-start > 10000) return reject(new Error(description + ' timed out; open=' + Archify.finder.isOpen() + ', active=' + document.activeElement.id));
        requestAnimationFrame(sample);
      }
      requestAnimationFrame(sample);
    });
  ` });
  async function load(mode = 'architecture', { theme = 'dark', width = 1440, height = 900, reduced = false, query = '' } = {}) {
    await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 0, y: 0 });
    await send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: false });
    await send('Emulation.setEmulatedMedia', { media: '', features: [
      { name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' },
    ] });
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    await send('Page.navigate', { url: pathToFileURL(files[mode]).href + `?theme=${theme}${query}` });
    await loaded;
    await run('document.fonts.ready');
    await run('Archify.viewerChromeLayout.whenStable()');
  }
  async function key(key, code, windowsVirtualKeyCode) {
    await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode });
    await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode });
  }
  async function opened() {
    await run(`finderWait(() => Archify.finder.isOpen(), 'Finder panel opening')`);
    await run(`finderWait(() => document.activeElement.id === 'node-finder-input', 'Finder input focus')`);
  }
  async function search(text) {
    await run(`document.getElementById('node-finder-input').select()`);
    await send('Input.insertText', { text });
  }
  async function snapshot(scenario) {
    const value = await run(`(() => {
      const panel=document.getElementById('node-finder'), input=document.getElementById('node-finder-input');
      return {open:Archify.finder.isOpen(), context:Archify.finder.context(), count:Archify.finder.count,
        expanded:document.getElementById('btn-node-finder').getAttribute('aria-expanded'),
        panelContext:panel.getAttribute('data-context'), title:document.getElementById('node-finder-title').textContent,
        query:input.value, placeholder:input.placeholder, empty:document.getElementById('node-finder-empty').hidden,
        status:document.getElementById('node-finder-status').textContent,
        results:[...panel.querySelectorAll('.node-finder-result')].map(el=>({id:el.dataset.nodeId,badge:el.querySelector('em').textContent,aria:el.getAttribute('aria-label')})),
        activeId:document.activeElement.id, activeNode:document.activeElement.getAttribute('data-node-id'),
        focus:Archify.focus.active(), route:Archify.routeProbe.active(),
        routeFinder:document.getElementById('route-probe').getAttribute('data-finder-open'),
        errors:finderErrors,
        external:performance.getEntriesByType('resource').map(e=>e.name).filter(n=>/^https?:/.test(n))};
    })()`);
    assert.deepEqual(value.errors, [], scenario); assert.deepEqual(value.external, [], scenario);
    records.push({ scenario, ...value });
    return value;
  }
  const ids = state => state.results.map(item => item.id);

  await t.test('five modes initialize one index and no external resources', async () => {
    for (const mode of Object.keys(cases)) {
      await load(mode);
      const state = await snapshot(mode + '-initial');
      assert.equal(state.open, false); assert.equal(state.count, state.results.length);
      assert.equal(state.count, await run(`document.querySelectorAll('.diagram-container svg [data-node-id]').length`));
      assert.equal(new Set(ids(state)).size, state.count);
      // One full open/close cycle covers the shared controller; retain
      // initialization and index checks for all five renderer inputs.
      if (mode === 'architecture') {
        await click('#btn-node-finder'); await opened();
        assert.equal((await snapshot(mode + '-open')).expanded, 'true');
        await key('Escape', 'Escape', 27);
        assert.equal((await snapshot(mode + '-closed')).activeId, 'btn-node-finder');
      }
    }
  });

  await t.test('search normalizes queries and preserves contextual lists and retained context', async () => {
    await load('metadata'); await click('#btn-node-finder'); await opened();
    for (const query of ['  API SERVER  ', 'finder-brand-token', 'src/finder-proof.js']) {
      await search(query); const state = await snapshot('query-' + query.trim());
      assert.deepEqual(ids(state), ['api']); assert.equal(state.empty, true);
      assert.match(state.status, /1/);
    }
    await search('no-matching-node');
    const empty = await snapshot('query-empty'); assert.deepEqual(ids(empty), []); assert.equal(empty.empty, false);
    await key('Enter', 'Enter', 13); assert.equal(await run('Archify.finder.isOpen()'), true);
    await run(`Archify.finder.open({context:{kind:'fixture',allowedIds:['db','api'],badges:{api:'custom badge'}}})`); await opened();
    const restricted = await snapshot('restricted');
    assert.deepEqual(ids(restricted), ['api', 'db']); assert.equal(restricted.results[0].badge, 'custom badge');
    await run(`Archify.finder.close()`);
    const closed = await snapshot('retained-context');
    assert.equal(closed.context, 'fixture'); assert.deepEqual(closed.results, restricted.results);
    await run(`Archify.finder.open({context:{allowedIds:[]}})`); await opened();
    assert.deepEqual(ids(await snapshot('no-allowed-items')), []);
    assert.equal(await run(`Archify.finder.select('missing')`), false);
    // Filtering is a list contract; the public select method uses all items.
    assert.equal(await run(`Archify.finder.select('api')`), true);
    assert.equal(await run('Archify.focus.active()'), 'api');
    await run('Archify.finder.open()'); await opened();
    const reset = await snapshot('default-restored'); assert.equal(reset.context, 'focus'); assert.equal(reset.results.length, reset.count);
  });

  await t.test('trusted keyboard navigation, selection and Escape preserve focus boundaries', async () => {
    await load();
    await key('/', 'Slash', 191); await opened();
    await key('ArrowDown', 'ArrowDown', 40);
    const first = await snapshot('keyboard-first'); assert.equal(first.activeNode, first.results[0].id);
    await key('ArrowUp', 'ArrowUp', 38);
    assert.equal((await snapshot('keyboard-wrap')).activeNode, first.results.at(-1).id);
    await key('Home', 'Home', 36); assert.equal((await snapshot('keyboard-home')).activeNode, first.results[0].id);
    await key('End', 'End', 35); assert.equal((await snapshot('keyboard-end')).activeNode, first.results.at(-1).id);
    await key('Escape', 'Escape', 27);
    assert.equal((await snapshot('keyboard-escape')).activeId, 'btn-node-finder');
    await run(`Archify.guidedViews.activate('request-path')`);
    await run(`finderWait(() => !Archify.guidedViews.handoff())`);
    await key('/', 'Slash', 191); await opened(); await search('API Server');
    // Slash in an input must not reopen the panel and erase the query.
    await key('/', 'Slash', 191); assert.equal(await run(`document.getElementById('node-finder-input').value`), 'API Server');
    await key('Enter', 'Enter', 13);
    await run(`finderWait(() => !document.querySelector('.diagram-container').hasAttribute('data-camera-transaction'))`);
    const selected = await snapshot('normal-selection');
    assert.equal(selected.open, false); assert.equal(selected.focus, 'api'); assert.equal(selected.activeNode, 'api');
    assert.equal(await run('Archify.guidedViews.active()'), null);
    assert.match(await run('location.hash'), /focus=api/);
    assert.equal(await run(`(() => {const n=document.querySelector('.diagram-container svg [data-node-id="api"]').getBoundingClientRect(),s=Archify.viewerChromeLayout.stageRect();return n.right>s.left&&n.left<s.right&&n.bottom>s.top&&n.top<s.bottom;})()`), true);
  });

  await t.test('real Route controls select source and target; cancellation restores Route focus', async () => {
    await load(); await click('#btn-route-probe'); await click('#route-probe-find'); await opened();
    let state = await snapshot('route-source');
    assert.equal(state.context, 'route-source'); assert.equal(state.routeFinder, 'true');
    assert.ok(ids(state).includes('users')); assert.ok(!ids(state).includes('db'));
    await key('Escape', 'Escape', 27);
    state = await snapshot('route-source-cancel'); assert.equal(state.route, 'source'); assert.equal(state.routeFinder, null); assert.equal(state.activeId, 'route-probe-find');
    await click('#route-probe-find'); await opened(); await search('Users');
    await click('.node-finder-result[data-node-id="users"]');
    state = await snapshot('route-source-selected'); assert.equal(state.route, 'target'); assert.equal(state.open, false); assert.equal(state.routeFinder, null);
    await click('#route-probe-find'); await opened();
    state = await snapshot('route-target'); assert.equal(state.context, 'route-target');
    assert.ok(ids(state).includes('db')); assert.ok(!ids(state).includes('users')); assert.ok(!ids(state).includes('auth'));
    assert.match(state.results.find(item => item.id === 'db').badge, /4/);
    assert.equal(await run(`Archify.finder.select('auth')`), false);
    assert.equal(await run('Archify.finder.isOpen()'), true);
    await key('Escape', 'Escape', 27);
    assert.equal((await snapshot('route-target-cancel')).activeId, 'route-probe-find');
    await click('#route-probe-find'); await opened(); await search('PostgreSQL');
    await key('Enter', 'Enter', 13);
    state = await snapshot('route-result'); assert.equal(state.route, 'result'); assert.equal(state.routeFinder, null); assert.equal(state.open, false);
    const result = await run('Archify.routeProbe.result()');
    assert.deepEqual(result.nodes, ['users', 'cdn', 'lb', 'api', 'db']); assert.equal(result.hops, 4);
    // A sink is allowed by Route's public API even though the source picker
    // filters it out. Its actual target context must render an empty list.
    await run(`Archify.routeProbe.begin({source:'db'})`);
    await click('#route-probe-find'); await opened();
    state = await snapshot('route-empty-target');
    assert.equal(state.context, 'route-target'); assert.deepEqual(ids(state), []); assert.equal(state.empty, false);
    await key('Enter', 'Enter', 13); assert.equal(await run('Archify.routeProbe.active()'), 'target');
    await key('Escape', 'Escape', 27);
    await click('#route-probe-clear'); await key('/', 'Slash', 191); await opened();
    assert.equal((await snapshot('route-cleared-default')).context, 'focus');
  });

  await t.test('panel coordination, outside clicks, repeated close and embed retain baseline semantics', async () => {
    await load();
    await run('Archify.exportMenu.open(); Archify.finder.open()'); await opened();
    assert.equal(await run('Archify.exportMenu.isOpen()'), false);
    await run('Archify.semanticLens.open(); Archify.finder.open()'); await opened();
    assert.equal(await run('Archify.semanticLens.isOpen()'), false);
    await run(`Archify.finder.close(); document.querySelector('[data-legend-kind][role="button"]').focus()`);
    assert.equal(await run(`document.querySelector('.diagram-container > svg').hasAttribute('data-legend-preview-active')`), true);
    await run('Archify.finder.open()'); await opened();
    assert.equal(await run(`document.querySelector('.diagram-container > svg').hasAttribute('data-legend-preview-active')`), false);
    await click('#btn-node-finder'); assert.equal(await run('Archify.finder.isOpen()'), false);
    await click('#btn-node-finder'); await opened();
    await click('h1'); assert.equal(await run('Archify.finder.isOpen()'), false);
    const rapid = await run(`(() => {Archify.finder.open(); const a=Archify.finder.close({restoreFocus:false}); const b=Archify.finder.close({restoreFocus:false}); return {a:a===undefined,b:b===undefined,open:Archify.finder.isOpen()};})()`);
    assert.deepEqual(rapid, { a: true, b: true, open: false });
    await run('new Promise(resolve => requestAnimationFrame(resolve))');
    await snapshot('rapid-close');
    await load('architecture', { query: '&embed=1' });
    assert.equal(await run('Archify.finder.open()'), false); assert.equal((await snapshot('embed')).open, false);
  });

  await t.test('themes, constrained viewport, reduced motion and exported SVG', async () => {
    for (const theme of ['dark', 'light']) {
      await load('architecture', { theme, width: 640, height: 600, reduced: true });
      await click('#btn-node-finder'); await opened();
      const layout = await run(`(() => {const p=document.getElementById('node-finder').getBoundingClientRect(),r=document.getElementById('node-finder-results'); return {visible:p.width>0&&p.height>0,within:p.left>=-1&&p.right<=innerWidth+1&&p.top>=-1&&p.bottom<=innerHeight+1,scroll:getComputedStyle(r).overflowY};})()`);
      assert.equal(layout.visible, true); assert.equal(layout.within, true); assert.match(layout.scroll, /auto|scroll/);
      await snapshot('constrained-' + theme);
      if (evidence) {
        await run(`Promise.all(document.querySelector('.node-finder-search').getAnimations({subtree:true}).map(animation=>animation.finished.catch(()=>{})))`);
        await run('new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))');
        const shot = await send('Page.captureScreenshot', { format: 'png' });
        fs.writeFileSync(path.join(evidence, theme + '.png'), Buffer.from(shot.data, 'base64'));
      }
      await search('API Server'); await key('Enter', 'Enter', 13);
      await run(`finderWait(() => !document.querySelector('.diagram-container').hasAttribute('data-camera-transaction'))`);
      assert.equal(await run('Archify.focus.active()'), 'api');
      const exported = await run(`(async () => {
        const original=URL.createObjectURL; let blob;
        URL.createObjectURL=function(value){if(value.type.startsWith('image/svg+xml'))blob=value;return original.call(URL,value);};
        try {await Archify.exportMenu.run('svg');} finally {URL.createObjectURL=original;}
        const text=await blob.text(),root=new DOMParser().parseFromString(text,'image/svg+xml').documentElement;
        return {clean:!root.querySelector('[id^="node-finder"],.node-finder-result,[data-focus-selected]')&&!root.hasAttribute('data-focus-active'),geometry:root.getAttribute('viewBox')===document.querySelector('.diagram-container > svg').getAttribute('viewBox')};
      })()`);
      assert.deepEqual(exported, { clean: true, geometry: true });
      await snapshot('export-' + theme);
    }
  });
});
```

## test/finder.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-finder-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function svg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers ship the same geometry-neutral node finder', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="btn-node-finder"[^>]+aria-label="Find a node"[^>]+aria-haspopup="dialog"/, mode);
    assert.match(html, /id="node-finder" hidden role="dialog" aria-modal="false"/, mode);
    assert.match(html, /id="node-finder-input" type="search"/, mode);
    assert.match(html, /Archify\.finder = \(function \(\)/, mode);
    assert.match(html, /svg\.querySelectorAll\('\[data-node-id\]'\)/, mode);
    assert.doesNotMatch(svg(html), /node-finder|Archify\.finder|Find a node/, mode);
  }
});

test('finder searches semantic ids and labels, then delegates to focus and reveal', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /search: \(id \+ ' ' \+ label \+ ' ' \+ type \+ ' ' \+ sublabel \+ ' ' \+ context \+ ' ' \+ tag \+ ' ' \+ sourceSearch \+ ' ' \+ text\)\.toLowerCase\(\)/);
  assert.match(html, /item\.search\.indexOf\(query\) !== -1/);
  assert.match(html, /Archify\.guidedViews\.showAll\(\{ clearFocus: false, updateUrl: false \}\)/);
  assert.match(html, /Archify\.view\.reset\(\{ automatic: true \}\)/);
  assert.match(html, /Archify\.focus\.set\(id, \{ toggle: false \}\)/);
  assert.match(html, /Archify\.view\.reveal\(\[id\], \{ includeNeighbors: true, reason: 'finder' \}\)/);
  assert.match(html, /item\.node\.focus\(\{ preventScroll: true \}\)/);
  assert.match(html, /var key = from \+ '\\u0000' \+ to/);
});

test('finder presents one focused search control and a structured result list', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /id="node-finder-input"[^>]+aria-label="Search diagram nodes"/);
  assert.match(html, /\.node-finder-search:focus-within\s*\{/);
  assert.match(html, /\.node-finder-input:focus-visible\s*\{\s*outline:\s*none;/);
  assert.match(html, /\.node-finder\s*\{[\s\S]*?display:\s*flex;[\s\S]*?max-height:\s*calc\(100% - 2rem\);/);
  assert.match(html, /\.node-finder-results\s*\{[\s\S]*?flex:\s*1 1 auto;[\s\S]*?min-height:\s*0;/);
  assert.match(html, /\.node-finder-result:not\(:last-child\)\s*\{/);
  assert.match(html, /context\.kind === 'focus'\s*\? viewerCount\('viewer\.finder\.link', item\.links\)/);
  assert.match(html, /\[viewerKindLabel\(item\.type\), item\.id, item\.sublabel, item\.tag\]/);
  assert.doesNotMatch(html, /\[item\.type, item\.context, item\.sublabel, item\.tag, item\.id\]/);
  assert.match(html, /viewerText\('viewer\.finder\.status\.filtered'/);
});

test('finder becomes a contextual Route Probe endpoint picker without changing semantic focus', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /function resolveContext\(options\)/);
  assert.match(html, /Archify\.routeProbe\.finderContext\(\)/);
  assert.match(html, /context\.allowedIds\.indexOf\(item\.id\) !== -1/);
  assert.match(html, /context\.kind === 'route-source' \|\| context\.kind === 'route-target'/);
  assert.match(html, /Archify\.routeProbe\.choose\(id\)/);
  assert.match(html, /reason: 'route-pick'/);
  assert.match(html, /data-context="route-source"/);
  assert.match(html, /data-context="route-target"/);
  assert.match(html, /viewerText\('viewer\.finder\.result\.routeTarget'/);
  assert.match(html, /links: badge/);
  assert.match(html, /viewerText\('viewer\.finder\.status\.all'/);
});

test('finder is keyboard accessible, mobile-pinned, and subordinate to embed mode', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /e\.key === '\/'/);
  assert.match(html, /Archify\.finder\.open\(\)/);
  assert.match(html, /event\.key === 'ArrowDown'/);
  assert.match(html, /event\.key === 'ArrowUp'/);
  assert.match(html, /event\.key === 'Escape'/);
  assert.match(html, /event\.stopPropagation\(\)/);
  assert.match(html, /Archify\.exportMenu\.isOpen\(\)\) Archify\.exportMenu\.close\(false\)/);
  assert.match(html, /data-wide-diagram="true"\] \.node-finder/);
  assert.match(html, /html\[data-embed="true"\] \.node-finder/);
  assert.match(html, /html\.getAttribute\('data-embed'\) === 'true'/);
  assert.match(html, /data-node-finder-trigger/);
  assert.match(html, /!event\.target\.closest\('\[data-node-finder-trigger\]'\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/fixtures

```

```

## test/fixtures/automatic-routing-node-border-clearance.workflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "workflow",
  "meta": {
    "title": "自动连线节点边框避让回归",
    "quality_profile": "showcase",
    "viewBox": [720, 360]
  },
  "lanes": [
    { "id": "orchestrator", "label": "Orchestrator PTY" },
    { "id": "runtime", "label": "Hive Runtime" }
  ],
  "nodes": [
    {
      "id": "team_send",
      "lane": "orchestrator",
      "col": 2,
      "type": "backend",
      "label": "team send 派单",
      "sublabel": "选择目标 Worker"
    },
    {
      "id": "inject",
      "lane": "runtime",
      "col": 1,
      "type": "security",
      "label": "校验并注入输入",
      "sublabel": "UI token → PTY stdin"
    }
  ],
  "edges": [
    {
      "id": "stdin",
      "from": "inject",
      "to": "team_send",
      "label": "PTY stdin",
      "variant": "emphasis"
    }
  ]
}
```

## test/fixtures/fail-migration-cleanup.mjs

```js
import fs from 'node:fs';
import path from 'node:path';

const originalRmSync = fs.rmSync.bind(fs);
let injectedFailure = false;

fs.rmSync = function failMigrationCleanupOnce(target, options) {
  const isMigrationStagingDirectory = path.basename(String(target)).startsWith('.archify-migration-');
  if (!injectedFailure && isMigrationStagingDirectory) {
    injectedFailure = true;
    originalRmSync(target, options);
    const error = new Error('simulated migration cleanup failure');
    error.code = 'EPERM';
    throw error;
  }
  return originalRmSync(target, options);
};
```

## test/fixtures/issue-126

```

```

## test/fixtures/issue-126/custom-widths.workflow.json

```json
{
  "schema_version": 2,
  "diagram_type": "workflow",
  "meta": {
    "title": "Sanitized custom widths",
    "legend": { "mode": "hidden" }
  },
  "lanes": [
    { "id": "main", "label": "Main flow" }
  ],
  "nodes": [
    { "id": "compact", "lane": "main", "col": 1, "type": "frontend", "label": "In", "width": 32 },
    { "id": "wide", "lane": "main", "col": 2, "type": "database", "label": "Sanitized downstream service", "width": 240 }
  ],
  "edges": [
    { "id": "compact-wide", "from": "compact", "to": "wide" }
  ]
}
```

## test/fixtures/issue-126/explicit-route.workflow.json

```json
{
  "schema_version": 2,
  "diagram_type": "workflow",
  "meta": {
    "title": "Sanitized explicit route",
    "viewBox": [900, 420],
    "legend": { "mode": "hidden" }
  },
  "lanes": [
    { "id": "source", "label": "Source" },
    { "id": "target", "label": "Target" }
  ],
  "nodes": [
    { "id": "producer", "lane": "source", "col": 1, "type": "backend", "label": "Producer" },
    { "id": "consumer", "lane": "target", "col": 1, "type": "backend", "label": "Consumer" }
  ],
  "edges": [
    {
      "id": "producer-consumer",
      "from": "producer",
      "to": "consumer",
      "fromSide": "right",
      "toSide": "right",
      "route": "outside-right",
      "channelX": 720
    }
  ]
}
```

## test/fixtures/issue-126/explicit-viewbox.workflow.json

```json
{
  "schema_version": 2,
  "diagram_type": "workflow",
  "meta": {
    "title": "Sanitized explicit viewBox",
    "viewBox": [1600, 520],
    "legend": { "mode": "hidden" }
  },
  "lanes": [
    { "id": "main", "label": "Main flow" }
  ],
  "nodes": [
    { "id": "start", "lane": "main", "col": 0, "type": "frontend", "label": "Start" },
    { "id": "finish", "lane": "main", "col": 5, "type": "backend", "label": "Finish" }
  ],
  "edges": [
    { "id": "start-finish", "from": "start", "to": "finish", "label": "complete" }
  ]
}
```

## test/fixtures/issue-126/labels.workflow.json

```json
{
  "schema_version": 2,
  "diagram_type": "workflow",
  "meta": {
    "title": "Sanitized semantic labels",
    "legend": { "mode": "hidden" }
  },
  "lanes": [
    { "id": "main", "label": "Main flow" }
  ],
  "nodes": [
    { "id": "request", "lane": "main", "col": 3, "type": "frontend", "label": "Request" },
    { "id": "result", "lane": "main", "col": 4, "type": "backend", "label": "Result" }
  ],
  "edges": [
    { "id": "request-result", "from": "request", "to": "result", "label": "同步 ✅ ready" }
  ]
}
```

## test/fixtures/v1-baseline

```

```

## test/fixtures/v1-baseline/agent-run.lifecycle.json

```json
{
  "schema_version": 1,
  "diagram_type": "lifecycle",
  "meta": {
    "title": "Agent Run Lifecycle",
    "subtitle": "State machine for planning, tool execution, human approval, retries, and terminal outcomes",
    "output": "examples/lifecycle-agent-run.html",
    "viewBox": [980, 660]
  },
  "lanes": [
    { "id": "main", "label": "Lifecycle phases" },
    { "id": "waiting", "label": "Interruptions" },
    { "id": "exceptions", "label": "Recovery loop" },
    { "id": "terminal", "label": "Terminal exits" }
  ],
  "states": [
    { "id": "queued", "type": "start", "label": "Queued", "sublabel": "request accepted", "lane": "main", "col": 0, "step": "01", "tag": "entry" },
    { "id": "planning", "type": "active", "label": "Planning", "sublabel": "build task graph", "lane": "main", "col": 1, "step": "02", "tag": "model" },
    { "id": "executing", "type": "active", "label": "Executing", "sublabel": "tool calls", "lane": "main", "col": 2, "step": "03", "tag": "work" },
    { "id": "reviewing", "type": "decision", "label": "Reviewing", "sublabel": "quality gate", "lane": "main", "col": 3, "step": "04", "tag": "check" },
    { "id": "completed", "type": "success", "label": "Completed", "sublabel": "final response", "lane": "main", "col": 4, "step": "05", "tag": "done" },
    { "id": "approval", "type": "waiting", "label": "Needs Approval", "sublabel": "human gate", "lane": "waiting", "col": 0, "tag": "pause" },
    { "id": "blocked", "type": "waiting", "label": "Blocked", "sublabel": "missing input", "lane": "waiting", "col": 1, "tag": "wait" },
    { "id": "failed", "type": "failure", "label": "Failed", "sublabel": "recoverable error", "lane": "exceptions", "col": 0, "yOffset": 78, "tag": "retryable" },
    { "id": "cancelled", "type": "failure", "label": "Cancelled", "sublabel": "user stopped", "lane": "terminal", "col": 0, "tag": "terminal" },
    { "id": "expired", "type": "failure", "label": "Expired", "sublabel": "timeout", "lane": "terminal", "col": 1, "tag": "terminal" }
  ],
  "transitions": [
    { "from": "executing", "to": "approval", "variant": "security", "fromSide": "bottom", "toSide": "top", "route": "straight" },
    { "from": "reviewing", "to": "blocked", "variant": "default", "route": "drop" },
    { "from": "executing", "to": "failed", "variant": "security", "fromSide": "left", "toSide": "top", "via": [[320, 157], [320, 342], [402, 342]] },
    { "from": "blocked", "to": "expired", "variant": "security", "fromSide": "bottom", "toSide": "top", "route": "straight" },
    { "from": "approval", "to": "cancelled", "variant": "security", "fromSide": "bottom", "toSide": "top", "via": [[320, 336], [320, 430], [402, 430]] }
  ],
  "cards": [
    {
      "dot": "emerald",
      "title": "Main Path",
      "items": [
        "The run has five ordered phases from queue to completion",
        "The primary lifecycle is carried by one horizontal rail",
        "Completion is a phase, not a detached side box"
      ]
    },
    {
      "dot": "amber",
      "title": "Human + Input Gates",
      "items": [
        "Approval pauses execution without ending the run",
        "Blocked waits for missing user input",
        "Wait states can resume back into planning or execution"
      ]
    },
    {
      "dot": "rose",
      "title": "Terminal + Recovery",
      "items": [
        "Failed loops back while retry budget remains",
        "Cancelled and Expired are exits from the lifecycle",
        "Terminal exits do not point back into active execution"
      ]
    }
  ]
}
```

## test/fixtures/v1-baseline/agent-tool-call.workflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "workflow",
  "meta": {
    "title": "Agent Tool Call Workflow",
    "subtitle": "Renderer-driven workflow prototype with lanes, anchored nodes, and orthogonal edges",
    "output": "examples/workflow-agent-tool-call-rendered.html",
    "viewBox": [720, 900]
  },
  "lanes": [
    { "id": "ui", "label": "User Interface" },
    { "id": "agent", "label": "Agent Runtime" },
    { "id": "policy", "label": "Policy Boundary" },
    { "id": "exceptions", "label": "Exception Handling", "variant": "exception" },
    { "id": "tools", "label": "Tool Execution" },
    { "id": "trace", "label": "Observability" }
  ],
  "phases": [
    { "id": "intake", "label": "Intake", "fromCol": 0, "toCol": 1 },
    { "id": "reasoning", "label": "Plan + route", "fromCol": 2, "toCol": 3, "variant": "emphasis" },
    { "id": "execution", "label": "Execute + report", "fromCol": 4, "toCol": 5, "variant": "dashed" }
  ],
  "groups": [
    { "id": "agent_loop", "label": "Planning loop", "lane": "agent", "fromCol": 2, "toCol": 3, "variant": "emphasis" },
    { "id": "tool_work", "label": "Tool work", "lane": "tools", "fromCol": 4, "toCol": 5, "variant": "dashed" },
    { "id": "exception_path", "label": "Human or policy stop", "lane": "exceptions", "fromCol": 3, "toCol": 5, "variant": "security" }
  ],
  "mainPath": ["user", "chat", "planner", "router", "approval", "tool", "external", "final"],
  "nodes": [
    { "id": "user", "lane": "ui", "col": 0, "type": "external", "label": "User", "sublabel": "asks for work" },
    { "id": "chat", "lane": "ui", "col": 1, "type": "frontend", "label": "Chat Surface", "sublabel": "thread + files" },
    { "id": "final", "lane": "ui", "col": 5, "type": "backend", "label": "Final Reply", "sublabel": "answer + changes" },
    { "id": "planner", "lane": "agent", "col": 2, "type": "backend", "label": "Agent Planner", "sublabel": "plan next step", "tag": "context aware" },
    { "id": "router", "lane": "agent", "col": 3, "type": "backend", "label": "Tool Router", "sublabel": "choose capability" },
    { "id": "approval", "lane": "policy", "col": 3, "type": "security", "label": "Approval Gate", "sublabel": "scope + consent", "tag": "block risky ops" },
    { "id": "blocked", "lane": "exceptions", "col": 4, "type": "security", "label": "Blocked", "sublabel": "wait or reject" },
    { "id": "retry", "lane": "exceptions", "col": 5, "type": "messagebus", "label": "Retry Path", "sublabel": "revise request" },
    { "id": "tool", "lane": "tools", "col": 4, "type": "messagebus", "label": "Tool Call", "sublabel": "shell / browser / MCP", "tag": "structured result" },
    { "id": "external", "lane": "tools", "col": 5, "type": "cloud", "label": "External API", "sublabel": "network service" },
    { "id": "store", "lane": "trace", "col": 1, "type": "database", "label": "Context Store", "sublabel": "repo + memory" },
    { "id": "trace", "lane": "trace", "col": 4, "type": "database", "label": "Trace Log", "sublabel": "events + output" }
  ],
  "edges": [
    { "from": "user", "to": "chat", "variant": "default" },
    { "from": "chat", "to": "planner", "label": "plan", "variant": "emphasis", "fromSide": "bottom", "toSide": "top", "route": "drop", "labelSegment": 1 },
    { "from": "planner", "to": "router", "variant": "default" },
    { "from": "router", "to": "approval", "label": "needs approval?", "variant": "security", "fromSide": "bottom", "toSide": "top", "route": "drop", "labelSegment": 0, "labelDx": 34, "labelDy": 18 },
    { "from": "approval", "to": "tool", "variant": "emphasis", "fromSide": "left", "toSide": "left", "route": "return-left" },
    { "from": "approval", "to": "blocked", "label": "denied", "variant": "security", "role": "error", "fromSide": "bottom", "toSide": "top", "route": "drop", "labelSegment": 1, "labelDy": 12 },
    { "from": "blocked", "to": "retry", "variant": "dashed", "role": "branch" },
    { "from": "tool", "to": "external", "variant": "default" },
    { "from": "external", "to": "final", "variant": "emphasis", "role": "return", "fromSide": "right", "toSide": "right", "route": "outside-right", "width": 1.2 },
    { "from": "external", "to": "trace", "label": "record result", "variant": "dashed", "fromSide": "bottom", "toSide": "bottom", "route": "bottom-channel", "labelSegment": 1 },
    { "from": "store", "to": "trace", "label": "trace + memory", "variant": "dashed", "labelAt": [365, 735] }
  ],
  "cards": [
    {
      "dot": "cyan",
      "title": "Renderer Rules",
      "items": [
        "Lanes and columns determine node placement",
        "Edges attach to explicit node anchors",
        "Cross-lane paths use orthogonal routing",
        "Short adjacent links stay unlabeled"
      ]
    },
    {
      "dot": "rose",
      "title": "Workflow Semantics",
      "items": [
        "Approval is a first-class policy step",
        "Consent gates are visible in the main path",
        "External calls stay inside the tool lane",
        "Trace writes are separate from the hot path"
      ]
    },
    {
      "dot": "emerald",
      "title": "Why It Matters",
      "items": [
        "This is closer to a diagram_type renderer",
        "The graph can be edited without SVG surgery",
        "Layout rules can be tested and improved",
        "A future IR can reuse this shape directly"
      ]
    }
  ]
}
```

## test/fixtures/v1-baseline/cache-miss-request.sequence.json

```json
{
  "schema_version": 1,
  "diagram_type": "sequence",
  "meta": {
    "title": "Cache Miss Request Sequence",
    "subtitle": "Frontend request path with auth, cache fallback, persistence, and async trace",
    "output": "examples/sequence-cache-miss-request.html",
    "viewBox": [820, 760]
  },
  "participants": [
    { "id": "user", "type": "external", "label": "User", "sublabel": "browser session" },
    { "id": "web", "type": "frontend", "label": "Web App", "sublabel": "React UI" },
    { "id": "api", "type": "backend", "label": "API", "sublabel": "request handler" },
    { "id": "auth", "type": "security", "label": "Auth", "sublabel": "JWT verify" },
    { "id": "redis", "type": "database", "label": "Redis", "sublabel": "cache" },
    { "id": "db", "type": "database", "label": "Postgres", "sublabel": "source of truth" },
    { "id": "trace", "type": "messagebus", "label": "Trace", "sublabel": "async event" }
  ],
  "segments": [
    { "from": 150, "to": 295, "label": "Request" },
    { "from": 315, "to": 505, "label": "Fallback" },
    { "from": 525, "to": 665, "label": "Response + trace" }
  ],
  "messages": [
    { "from": "user", "to": "web", "y": 185, "label": "open page", "variant": "default" },
    { "from": "web", "to": "api", "y": 228, "label": "GET /dashboard", "variant": "emphasis" },
    { "from": "api", "to": "auth", "y": 270, "label": "verify JWT", "variant": "security" },
    { "from": "auth", "to": "api", "y": 305, "label": "claims ok", "variant": "return" },
    { "from": "api", "to": "redis", "y": 354, "label": "read cache", "variant": "default" },
    { "from": "redis", "to": "api", "y": 391, "label": "miss", "variant": "return" },
    { "from": "api", "to": "db", "y": 443, "label": "query profile + metrics", "variant": "emphasis" },
    { "from": "db", "to": "api", "y": 489, "label": "rows", "variant": "return" },
    { "from": "api", "to": "redis", "y": 536, "label": "set cache", "variant": "dashed" },
    { "from": "api", "to": "trace", "y": 580, "label": "emit trace", "variant": "dashed" },
    { "from": "api", "to": "web", "y": 625, "label": "200 JSON", "variant": "return" },
    { "from": "web", "to": "user", "y": 662, "label": "render", "variant": "return" }
  ],
  "activations": [
    { "participant": "web", "from": 220, "to": 668, "type": "frontend" },
    { "participant": "api", "from": 228, "to": 632, "type": "backend" },
    { "participant": "auth", "from": 265, "to": 310, "type": "security" },
    { "participant": "redis", "from": 349, "to": 398, "type": "database" },
    { "participant": "db", "from": 438, "to": 496, "type": "database" },
    { "participant": "trace", "from": 575, "to": 630, "type": "messagebus" }
  ],
  "cards": [
    {
      "dot": "emerald",
      "title": "Happy Path",
      "items": [
        "The main request is Web App -> API -> data source -> response",
        "Return messages are quieter than forward calls",
        "Activation bars make ownership duration visible"
      ]
    },
    {
      "dot": "rose",
      "title": "Policy + Fallback",
      "items": [
        "JWT verification is colored as a security interaction",
        "Cache miss is visible without overpowering the main path",
        "Database access only appears after cache fallback"
      ]
    },
    {
      "dot": "orange",
      "title": "Async Trace",
      "items": [
        "Trace emission is dashed and secondary",
        "It does not block the response path",
        "The diagram separates user-facing latency from observability"
      ]
    }
  ]
}
```

## test/fixtures/v1-baseline/event-stream.dataflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "dataflow",
  "meta": {
    "title": "Order Event-stream Topology",
    "output": "examples/event-stream.html",
    "viewBox": [1080, 780],
    "animation": "trace",
    "visual_preset": "signal-flow",
    "quality_profile": "showcase",
    "views": [
      { "id": "order-transit", "label": "Order event transit", "focus": ["checkout", "orders", "validate", "state", "fulfillment"], "note": "Follow an order from producer through ordered processing to fulfillment." },
      { "id": "payment-transit", "label": "Payment event transit", "focus": ["billing", "payments", "enrich", "state", "analytics"], "note": "Track payment facts into the shared materialized state and analytics." },
      { "id": "failure-and-replay", "label": "Failure and replay", "focus": ["validate", "enrich", "dlq", "replay", "ops"], "note": "Isolate dead letters, operator review, and controlled replay ownership." }
    ]
  },
  "stages": [
    { "label": "Producers" },
    { "label": "Transit" },
    { "label": "Processors" },
    { "label": "State + recovery" },
    { "label": "Consumers" }
  ],
  "nodes": [
    { "id": "checkout", "type": "frontend", "label": "Checkout API", "sublabel": "order producer", "stage": 0, "row": 0, "tag": "team commerce" },
    { "id": "billing", "type": "backend", "label": "Billing API", "sublabel": "payment producer", "stage": 0, "row": 2, "tag": "team money" },
    { "id": "orders", "type": "messagebus", "label": "orders.v1", "sublabel": "12 partitions", "stage": 1, "row": 0, "tag": "key: order_id" },
    { "id": "payments", "type": "messagebus", "label": "payments.v2", "sublabel": "8 partitions", "stage": 1, "row": 2, "tag": "key: order_id" },
    { "id": "validate", "type": "backend", "label": "Order Validate", "sublabel": "group fulfillment", "stage": 2, "row": 0, "tag": "ordered" },
    { "id": "enrich", "type": "backend", "label": "Payment Enrich", "sublabel": "group analytics", "stage": 2, "row": 2, "tag": "at-least-once" },
    { "id": "state", "type": "database", "label": "Order State", "sublabel": "materialized view", "stage": 3, "row": 1, "tag": "idempotent" },
    { "id": "dlq", "type": "messagebus", "label": "events.dlq", "sublabel": "poison events", "stage": 3, "row": 4, "tag": "7-day retention" },
    { "id": "fulfillment", "type": "backend", "label": "Fulfillment", "sublabel": "shipping workflow", "stage": 4, "row": 0, "tag": "consumer" },
    { "id": "analytics", "type": "database", "label": "Analytics", "sublabel": "streaming facts", "stage": 4, "row": 2, "tag": "consumer" },
    { "id": "replay", "type": "security", "label": "Replay Tool", "sublabel": "approved batch", "stage": 4, "row": 4, "tag": "operator gate" },
    { "id": "ops", "type": "external", "label": "On-call", "sublabel": "DLQ owner", "stage": 4, "row": 3, "yOffset": -18, "tag": "SRE" }
  ],
  "flows": [
    { "from": "checkout", "to": "orders", "label": "OrderPlaced", "classification": "schema v1", "variant": "emphasis", "route": "straight" },
    { "from": "billing", "to": "payments", "label": "PaymentCaptured", "classification": "schema v2", "variant": "emphasis", "route": "straight" },
    { "from": "orders", "to": "validate", "label": "ordered orders", "classification": "consumer group", "variant": "emphasis", "route": "straight" },
    { "from": "payments", "to": "enrich", "label": "payment facts", "classification": "at-least-once", "variant": "emphasis", "route": "straight" },
    { "from": "validate", "to": "state", "label": "valid order", "classification": "idempotent", "variant": "emphasis", "route": "vertical-channel" },
    { "from": "enrich", "to": "state", "label": "enriched payment", "classification": "idempotent", "variant": "default", "route": "vertical-channel" },
    { "from": "state", "to": "fulfillment", "label": "ready orders", "classification": "read model", "variant": "emphasis", "route": "vertical-channel" },
    { "from": "state", "to": "analytics", "label": "order facts", "classification": "non-PII", "variant": "default", "route": "vertical-channel" },
    { "from": "validate", "to": "dlq", "label": "invalid event", "classification": "dead letter", "variant": "security", "fromSide": "top", "toSide": "top", "via": [[530, 80], [20, 80], [20, 550], [745, 550]], "labelAt": [300, 550] },
    { "from": "enrich", "to": "dlq", "label": "poison event", "classification": "dead letter", "variant": "security", "route": "bottom-channel", "labelDy": 30 },
    { "from": "dlq", "to": "ops", "label": "failure sample", "classification": "restricted", "variant": "security", "route": "vertical-channel" },
    { "from": "dlq", "to": "replay", "label": "approved replay", "classification": "audited batch", "variant": "dashed", "route": "straight", "labelDy": 30 }
  ],
  "cards": [
    { "dot": "amber", "title": "Transit Contract", "items": ["Every event and topic is named", "Partition keys preserve per-order ordering", "Consumer groups expose processing ownership"] },
    { "dot": "emerald", "title": "State + Delivery", "items": ["Processors write an idempotent materialized view", "Fulfillment and analytics consume distinct assets", "At-least-once delivery never implies duplicate business effects"] },
    { "dot": "rose", "title": "Failure Ownership", "items": ["Poison events land in a retained dead-letter topic", "On-call inspects samples before replay", "Replay is gated, batched, and auditable"] }
  ]
}
```

## test/fixtures/v1-baseline/product-analytics.dataflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "dataflow",
  "meta": {
    "title": "Product Analytics Data Flow",
    "subtitle": "Events, consent, PII isolation, warehouse sync, and downstream analytics",
    "output": "examples/dataflow-product-analytics.html",
    "viewBox": [1080, 760]
  },
  "stages": [
    { "label": "Sources" },
    { "label": "Ingest" },
    { "label": "Process" },
    { "label": "Store" },
    { "label": "Consume" }
  ],
  "nodes": [
    { "id": "web", "type": "frontend", "label": "Web App", "sublabel": "browser SDK", "stage": 0, "row": 0, "tag": "events" },
    { "id": "mobile", "type": "frontend", "label": "Mobile", "sublabel": "iOS / Android", "stage": 0, "row": 2, "tag": "events" },
    { "id": "edge", "type": "cloud", "label": "Edge API", "sublabel": "collector", "stage": 1, "row": 1, "tag": "TLS" },
    { "id": "consent", "type": "security", "label": "Consent Gate", "sublabel": "policy filter", "stage": 2, "row": 0, "tag": "PII guard" },
    { "id": "stream", "type": "messagebus", "label": "Event Stream", "sublabel": "Kafka topic", "stage": 2, "row": 2, "tag": "ordered" },
    { "id": "pii", "type": "security", "label": "PII Vault", "sublabel": "encrypted", "stage": 3, "row": 0, "tag": "restricted" },
    { "id": "warehouse", "type": "database", "label": "Warehouse", "sublabel": "analytics tables", "stage": 3, "row": 2, "tag": "curated" },
    { "id": "features", "type": "database", "label": "Feature Store", "sublabel": "daily batch", "stage": 3, "row": 4, "tag": "derived" },
    { "id": "dashboard", "type": "backend", "label": "Dashboards", "sublabel": "product metrics", "stage": 4, "row": 1, "tag": "SQL" },
    { "id": "model", "type": "backend", "label": "ML Model", "sublabel": "ranking job", "stage": 4, "row": 4, "tag": "features" }
  ],
  "flows": [
    { "from": "web", "to": "edge", "label": "clickstream", "classification": "user events", "variant": "emphasis", "fromSide": "right", "toSide": "left", "via": [[184, 157], [184, 271]], "labelAt": [204, 190] },
    { "from": "mobile", "to": "edge", "label": "app events", "classification": "device events", "variant": "default", "fromSide": "right", "toSide": "left", "via": [[222, 385], [222, 271]], "labelAt": [220, 342] },
    { "from": "edge", "to": "consent", "label": "identity + consent", "classification": "PII touch", "variant": "security", "fromSide": "top", "toSide": "left", "via": [[315, 112], [450, 112], [450, 157]], "labelAt": [382, 100] },
    { "from": "edge", "to": "stream", "label": "accepted events", "classification": "append-only", "variant": "emphasis", "fromSide": "right", "toSide": "left", "via": [[420, 271], [420, 385]], "labelAt": [438, 324] },
    { "from": "consent", "to": "pii", "label": "identity map", "classification": "encrypted PII", "variant": "security", "route": "straight", "labelAt": [638, 144] },
    { "from": "stream", "to": "warehouse", "label": "normalized facts", "classification": "non-PII", "variant": "emphasis", "route": "straight", "labelAt": [638, 372] },
    { "from": "warehouse", "to": "features", "label": "daily aggregates", "classification": "batch", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "route": "straight", "labelAt": [745, 496] },
    { "from": "warehouse", "to": "dashboard", "label": "metrics SQL", "classification": "read-only", "variant": "default", "fromSide": "right", "toSide": "left", "via": [[852, 385], [852, 271]], "labelAt": [830, 326] },
    { "from": "features", "to": "model", "label": "feature vectors", "classification": "derived", "variant": "dashed", "route": "straight", "labelAt": [852, 598] },
    { "from": "pii", "to": "dashboard", "label": "restricted join", "classification": "approved only", "variant": "security", "fromSide": "right", "toSide": "top", "via": [[878, 157], [878, 212], [960, 212]], "labelAt": [880, 198] }
  ],
  "cards": [
    {
      "dot": "emerald",
      "title": "Primary Data Path",
      "items": [
        "Events move left to right through source, ingest, process, store, and consume stages",
        "The hot path stays visually clear even with secondary batch flows",
        "Labels name data assets instead of generic API verbs"
      ]
    },
    {
      "dot": "rose",
      "title": "Sensitive Boundary",
      "items": [
        "Consent and PII paths are styled as security flows",
        "PII lands in a restricted vault, separate from the analytics warehouse",
        "Restricted joins are visible without implying default access"
      ]
    },
    {
      "dot": "orange",
      "title": "Derived Consumers",
      "items": [
        "Dashboards read curated facts from the warehouse",
        "Feature vectors are derived by batch from analytics tables",
        "Consumption paths stay distinct from collection and consent handling"
      ]
    }
  ]
}
```

## test/fixtures/v1-baseline/production-deployment.architecture.json

```json
{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Production Deployment Ownership",
    "output": "examples/production-deployment.html",
    "visual_preset": "blueprint",
    "animation": "trace",
    "quality_profile": "showcase",
    "engineering_profile": "deployment-ownership",
    "views": [
      {
        "id": "request-boundary",
        "label": "Request crosses the edge",
        "focus": ["clients", "edge", "gateway", "api_a", "api_b"],
        "note": "Follow public traffic into the private application network."
      },
      {
        "id": "state-ownership",
        "label": "State and ownership",
        "focus": ["api_a", "api_b", "redis", "postgres", "replica"],
        "note": "Separate stateless platform workloads from data-team-owned state."
      },
      {
        "id": "async-operations",
        "label": "Async and operations",
        "focus": ["api_b", "events", "worker", "audit", "observability"],
        "note": "See the asynchronous work and the evidence it emits."
      }
    ]
  },
  "components": [
    { "id": "clients", "type": "external", "label": "Customers", "sublabel": "web + mobile", "pos": [38, 300], "size": [122, 60] },
    { "id": "edge", "type": "cloud", "label": "Global Edge", "sublabel": "CDN + WAF", "pos": [230, 300], "size": [126, 60], "tag": "edge team" },
    { "id": "gateway", "type": "security", "label": "API Gateway", "sublabel": "public :443", "pos": [430, 300], "size": [128, 60], "tag": "platform" },
    { "id": "api_a", "type": "backend", "label": "API Pods / AZ-a", "sublabel": "private subnet", "pos": [630, 195], "size": [136, 62], "tag": "app team" },
    { "id": "api_b", "type": "backend", "label": "API Pods / AZ-b", "sublabel": "private subnet", "pos": [630, 405], "size": [136, 62], "tag": "app team" },
    { "id": "redis", "type": "database", "label": "Redis", "sublabel": "multi-AZ cache", "pos": [840, 195], "size": [126, 62], "tag": "platform" },
    { "id": "postgres", "type": "database", "label": "PostgreSQL", "sublabel": "primary / encrypted", "pos": [840, 405], "size": [126, 62], "tag": "data team" },
    { "id": "events", "type": "messagebus", "label": "Event Bus", "sublabel": "orders.v1", "pos": [1040, 300], "size": [126, 60], "tag": "platform" },
    { "id": "worker", "type": "backend", "label": "Workers", "sublabel": "private workload", "pos": [1240, 300], "size": [126, 60], "tag": "app team" },
    { "id": "replica", "type": "database", "label": "DR Replica", "sublabel": "eu-west-1", "pos": [1040, 610], "size": [126, 62], "tag": "data team" },
    { "id": "audit", "type": "cloud", "label": "Audit Archive", "sublabel": "immutable objects", "pos": [1240, 465], "size": [126, 62], "tag": "security" },
    { "id": "observability", "type": "external", "label": "Observability", "sublabel": "metrics + traces", "pos": [1240, 85], "size": [126, 62], "tag": "SRE" }
  ],
  "boundaries": [
    { "kind": "region", "label": "AWS us-east-1 / production", "wraps": ["edge", "gateway", "api_a", "api_b", "redis", "postgres", "events", "worker", "audit"] },
    { "kind": "security-group", "label": "private application network", "wraps": ["api_a", "api_b", "redis", "postgres", "events", "worker"] },
    { "kind": "region", "label": "AWS eu-west-1 / disaster recovery", "wraps": ["replica"] },
    { "kind": "security-group", "label": "DR private subnet", "wraps": ["replica"], "pad": 14 }
  ],
  "connections": [
    { "from": "clients", "to": "edge", "label": "HTTPS", "variant": "emphasis" },
    { "from": "edge", "to": "gateway", "label": "mTLS", "variant": "security" },
    { "from": "gateway", "to": "api_a", "label": "VPC route", "variant": "emphasis", "route": "orthogonal-h", "labelAt": [594, 275] },
    { "from": "gateway", "to": "api_b", "label": "VPC route", "variant": "emphasis", "route": "orthogonal-h", "labelAt": [594, 385] },
    { "from": "api_a", "to": "redis", "label": "cache", "route": "straight" },
    { "from": "api_b", "to": "postgres", "label": "SQL", "route": "straight" },
    { "from": "api_a", "to": "events", "label": "publish", "variant": "dashed", "fromSide": "top", "toSide": "top", "via": [[698, 170], [1103, 170]] },
    { "from": "api_b", "to": "events", "variant": "dashed", "fromSide": "top", "toSide": "bottom", "via": [[698, 380], [1103, 380]] },
    { "from": "events", "to": "worker", "variant": "emphasis" },
    { "from": "postgres", "to": "replica", "label": "cross-region WAL", "variant": "security", "route": "orthogonal-v", "labelAt": [1003, 529] },
    { "from": "worker", "to": "audit", "label": "evidence", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 },
    { "from": "worker", "to": "observability", "label": "OTLP", "variant": "dashed", "route": "orthogonal-v" }
  ],
  "cards": [
    { "dot": "cyan", "title": "Runtime Ownership", "items": ["Platform owns the edge, gateway, cache, and event bus", "Application teams own API pods and workers", "Data owns primary and disaster-recovery state"] },
    { "dot": "rose", "title": "Named Crossings", "items": ["Public HTTPS terminates at the managed edge", "mTLS crosses into the application network", "Cross-region WAL is explicit and encrypted"] },
    { "dot": "emerald", "title": "Operational Evidence", "items": ["Workers emit traces to SRE-owned observability", "Audit evidence lands in immutable storage", "Unknown placement should remain marked, never invented"] }
  ]
}
```

## test/fixtures/v1-baseline/web-app.architecture.json

```json
{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Sample Web App",
    "subtitle": "Classic 3-tier SaaS on AWS — rendered by Archify",
    "output": "web-app-rendered.html"
  },
  "components": [
    { "id": "users", "type": "external", "label": "Users", "sublabel": "Browser / Mobile", "pos": [40, 300], "size": [120, 60] },
    { "id": "auth", "type": "security", "label": "Auth Provider", "sublabel": "OAuth 2.0", "pos": [40, 110], "size": [120, 64], "tag": "JWT + PKCE" },
    { "id": "cdn", "type": "cloud", "label": "CloudFront", "sublabel": "CDN", "pos": [250, 300], "size": [130, 60] },
    { "id": "lb", "type": "cloud", "label": "Load Balancer", "sublabel": "HTTPS :443", "pos": [460, 300], "size": [130, 60] },
    { "id": "api", "type": "backend", "label": "API Server", "sublabel": "FastAPI :8000", "pos": [670, 300], "size": [130, 60] },
    { "id": "cache", "type": "database", "label": "Redis", "sublabel": "cache :6379", "pos": [670, 150], "size": [130, 60] },
    { "id": "db", "type": "database", "label": "PostgreSQL", "sublabel": "primary :5432", "pos": [880, 300], "size": [130, 60] },
    { "id": "s3", "type": "cloud", "label": "S3", "sublabel": "static assets", "pos": [250, 440], "size": [130, 60], "tag": "OAI protected" },
    { "id": "queue", "type": "messagebus", "label": "SQS", "sublabel": "job queue", "pos": [670, 440], "size": [130, 60] },
    { "id": "worker", "type": "backend", "label": "Worker", "sublabel": "async jobs", "pos": [880, 440], "size": [130, 60] }
  ],
  "boundaries": [
    { "kind": "region", "label": "AWS Region: us-west-2", "wraps": ["cdn", "lb", "api", "cache", "db", "s3", "queue", "worker"] },
    { "kind": "security-group", "label": "sg-api :443/:8000", "wraps": ["lb", "api"] }
  ],
  "connections": [
    { "from": "users", "to": "cdn", "label": "HTTPS", "variant": "emphasis" },
    { "from": "auth", "to": "api", "label": "verify JWT", "variant": "security", "fromSide": "right", "toSide": "left", "via": [[620, 142], [620, 330]] },
    { "from": "cdn", "to": "lb" },
    { "from": "cdn", "to": "s3", "label": "static", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 },
    { "from": "lb", "to": "api" },
    { "from": "api", "to": "cache", "label": "read-through", "fromSide": "top", "toSide": "bottom", "labelDy": -68 },
    { "from": "api", "to": "db", "label": "SQL" },
    { "from": "api", "to": "queue", "label": "enqueue", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 },
    { "from": "queue", "to": "worker" }
  ],
  "cards": [
    { "dot": "cyan", "title": "Edge", "items": ["CloudFront CDN fronts all traffic", "S3 serves static assets via OAI"] },
    { "dot": "emerald", "title": "Application", "items": ["FastAPI behind an HTTPS load balancer", "Redis read-through cache", "Async work drained from SQS by a worker"] },
    { "dot": "rose", "title": "Security", "items": ["OAuth 2.0 with JWT + PKCE", "API + LB isolated in a security group"] }
  ]
}
```

## test/fixtures/v1-workflow-700x400.workflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "workflow",
  "meta": {
    "title": "Legacy narrow workflow",
    "viewBox": [700, 400]
  },
  "lanes": [
    { "id": "first", "label": "First" },
    { "id": "second", "label": "Second" }
  ],
  "nodes": [
    { "id": "node_0", "lane": "first", "col": 0, "type": "frontend", "label": "frontend" },
    { "id": "node_1", "lane": "first", "col": 2, "type": "backend", "label": "backend" },
    { "id": "node_2", "lane": "first", "col": 4, "type": "database", "label": "database" },
    { "id": "node_3", "lane": "second", "col": 0, "type": "cloud", "label": "cloud" },
    { "id": "node_4", "lane": "second", "col": 2, "type": "security", "label": "security" },
    { "id": "node_5", "lane": "second", "col": 4, "type": "messagebus", "label": "messagebus" },
    { "id": "node_6", "lane": "second", "col": 5, "type": "external", "label": "external" }
  ],
  "edges": []
}
```

## test/fixtures/v1-workflow-explicit-coordinates.workflow.json

```json
{
  "schema_version": 1,
  "diagram_type": "workflow",
  "meta": {
    "title": "Pinned coordinate migration",
    "viewBox": [720, 700],
    "legend": { "mode": "hidden" }
  },
  "lanes": [
    { "id": "route", "label": "Explicit route" },
    { "id": "label", "label": "Explicit label" },
    { "id": "channel-source", "label": "Channel source" },
    { "id": "channel-target", "label": "Channel target" }
  ],
  "nodes": [
    { "id": "route-a", "lane": "route", "col": 0, "type": "backend", "label": "A" },
    { "id": "route-b", "lane": "route", "col": 2, "type": "backend", "label": "B" },
    { "id": "label-a", "lane": "label", "col": 2, "type": "backend", "label": "C" },
    { "id": "label-b", "lane": "label", "col": 3, "type": "backend", "label": "D" },
    { "id": "channel-a", "lane": "channel-source", "col": 1, "type": "backend", "label": "E" },
    { "id": "channel-b", "lane": "channel-target", "col": 1, "type": "backend", "label": "F" }
  ],
  "edges": [
    {
      "id": "pinned-via",
      "from": "route-a",
      "to": "route-b",
      "via": [[220, 119]]
    },
    {
      "id": "pinned-label",
      "from": "label-a",
      "to": "label-b",
      "label": "ok",
      "labelAt": [365, 203]
    },
    {
      "id": "pinned-channel",
      "from": "channel-a",
      "to": "channel-b",
      "fromSide": "right",
      "toSide": "right",
      "route": "outside-right",
      "channelX": 500
    }
  ]
}
```

## test/focus-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { findChrome } from '../bin/visual-check.mjs';
import { desktopBrowser, desktopPointerCheck } from './helpers/desktop-browser.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;

test('Focus preserves semantic selection, relationships, reachability and shared flow tokens', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser Focus checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-focus-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_FOCUS_EVIDENCE;
  const records = [];
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  t.after(() => { if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n'); });
  const cases = { architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json', sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json', lifecycle: 'agent-run.lifecycle.json' };
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    files[mode] = path.join(scratch, mode + '.html');
    execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), path.join(skillRoot, 'examples', example), files[mode]]);
  }
  // The graph fixture deliberately exercises DOM fragment contracts outside IR validation.
  const graph = `<g data-edge-key="a" data-edge-id="edge-a" data-edge-from="users" data-edge-to="cdn"></g>
    <path data-edge-key="a" data-edge-id="edge-a" data-edge-from="users" data-edge-to="cdn" d="M 110 180 L 245 180"/>
    <line data-edge-key="b" data-edge-id="edge-b" data-edge-from="cdn" data-edge-to="lb" x1="245" y1="180" x2="380" y2="180"/>
    <polyline data-edge-key="c" data-edge-id="edge-c" data-edge-from="lb" data-edge-to="users" points="380,210 110,210 110,180"/>
    <path data-edge-key="d" data-edge-id="edge-d" data-edge-from="cdn" data-edge-to="api" d="M 245 180 Q 380 100 515 180"/>
    <path data-edge-key="e" data-edge-id="edge-e" data-edge-from="cdn" data-edge-to="api" d="M 245 180 Q 380 260 515 180"/>
    <g data-edge-key="f" data-edge-id="edge-f" data-edge-from="api" data-edge-to="db" transform="translate(0 3)"><path d="M 515 180 L 650 180"/></g>
    <path data-edge-key="g" data-edge-id="edge-g" data-edge-from="api" data-edge-to="api" d="M 515 180 C 470 100 560 100 515 180"/>
    <path data-edge-key="h" data-edge-id="edge-h" data-edge-from="db" data-edge-to="cache" d="M 650 180 L 785 180"/>
    <line data-edge-key="i" data-edge-id="edge-i" data-edge-from="worker" data-edge-to="cdn" x1="920" y1="230" x2="245" y2="230"/>
    ${['users','cdn','lb','api','db','cache','worker','isolated'].map((id,i)=>`<g data-node-id="${id}" data-node-label="${id}" data-node-kind="${id==='db'?'database':id==='worker'?'messagebus':'backend'}" tabindex="0" role="button"><rect x="${60+i*135}" y="150" width="100" height="60" fill="var(--backend-fill)"/><text x="${70+i*135}" y="185">${id}</text></g>`).join('')}`;
  const graphSetup = `var fixtureSvg=document.querySelector('.diagram-container > svg');fixtureSvg.setAttribute('viewBox','0 0 1200 500');fixtureSvg.setAttribute('data-animation','trace');fixtureSvg.innerHTML=${JSON.stringify(graph)};`;
  function variant(name, setup) {
    files[name] = path.join(scratch, name + '.html');
    const original = fs.readFileSync(files.architecture, 'utf8');
    assert.ok(original.includes('    var Archify = {};'), 'Focus fixture anchor');
    fs.writeFileSync(files[name], original.replace('    var Archify = {};', setup + '\n    var Archify = {};'));
  }
  variant('graph', graphSetup);
  variant('no-geometry', graphSetup + `document.querySelector('[data-edge-key="f"] path').remove();`);
  const browser = desktopBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  const checkPointer = await desktopPointerCheck(browser, session);
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  await send('Emulation.setFocusEmulationEnabled', { enabled: true });
  async function run(expression) {
    const r = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
    assert.equal(r.exceptionDetails, undefined, r.exceptionDetails?.exception?.description);
    return r.result?.value;
  }
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.focusErrors=[];window.pulseEvents=[];addEventListener('error',e=>focusErrors.push(e.message));addEventListener('unhandledrejection',e=>focusErrors.push(String(e.reason)));
    for(const type of ['animationend','animationcancel'])addEventListener(type,e=>{if(e.target.matches('.relationship-flow-pulse'))pulseEvents.push({type,trusted:e.isTrusted});},true);
    try{localStorage.removeItem('archify-motion');}catch(_){}
    window.focusWait=predicate=>new Promise((resolve,reject)=>{const start=performance.now();function poll(){if(predicate())return resolve();if(performance.now()-start>12000)return reject(new Error('Focus observation timed out'));requestAnimationFrame(poll);}requestAnimationFrame(poll);});
  ` });
  let navigationId=0;
  async function load(mode='graph', { theme='dark', reduced=true, width=1440, hash='' }={}) {
    await send('Input.dispatchMouseEvent',{type:'mouseMoved',x:0,y:0});
    await send('Emulation.setDeviceMetricsOverride',{width,height:900,deviceScaleFactor:1,mobile:false});
    await send('Emulation.setEmulatedMedia',{features:[{name:'prefers-reduced-motion',value:reduced?'reduce':'no-preference'}]});
    const loaded=browser.cdp.waitFor('Page.loadEventFired',session);
    await send('Page.navigate',{url:pathToFileURL(files[mode]).href+'?theme='+theme+'&keep=yes&run='+(++navigationId)+hash});await loaded;
    await checkPointer();
    await run('document.fonts.ready');await stable();
  }
  async function stable() {
    await run(`focusWait(()=>!document.querySelector('.diagram-container').hasAttribute('data-camera-transaction'))`);
    await run('Archify.viewerChromeLayout.whenStable()');
    await run('new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))');
  }
  const node=id=>`.diagram-container > svg [data-node-id="${id}"]`;
  const hit=key=>`[data-relationship-hit-key="${key}"]`;
  const relation=key=>`#relationship-lens-list [data-relationship-key="${key}"]`;
  async function point(selector) { return run(`(()=>{const r=document.querySelector(${JSON.stringify(selector)}).getBoundingClientRect();return {x:r.left+r.width/2,y:r.top+r.height/2};})()`); }
  async function move(selector) { await send('Input.dispatchMouseEvent',{type:'mouseMoved',...(selector?await point(selector):{x:0,y:0})}); }
  async function click(selector) {
    const p=await point(selector);await send('Input.dispatchMouseEvent',{type:'mousePressed',...p,button:'left',clickCount:1});await send('Input.dispatchMouseEvent',{type:'mouseReleased',...p,button:'left',clickCount:1});await stable();
  }
  async function key(key,code,windowsVirtualKeyCode) {
    await send('Input.dispatchKeyEvent',{type:'keyDown',key,code,windowsVirtualKeyCode,text:key==='Enter'?'\r':key===' '?' ':undefined});await send('Input.dispatchKeyEvent',{type:'keyUp',key,code,windowsVirtualKeyCode});await stable();
  }
  async function focus(selector) { await run(`document.querySelector(${JSON.stringify(selector)}).focus({preventScroll:true})`); }
  async function select(id,options={}) { assert.equal(await run(`Archify.focus.set(${JSON.stringify(id)},${JSON.stringify(options)})`),true);await stable(); }
  async function snapshot(scenario) {
    const s=await run(`(()=>{const svg=document.querySelector('.diagram-container > svg'),f=Archify.focus,chip=document.getElementById('focus-chip');
      const attrs=e=>Object.fromEntries([...e.attributes].filter(a=>/^(data-(focus|relationship|reach)|aria-(pressed|expanded|current))/.test(a.name)).map(a=>[a.name,a.value]));
      return {active:f.active(),relationship:f.relationship(),reach:f.reachability(),snapshot:f.reachabilitySnapshot(),hash:location.hash,svg:attrs(svg),chip:{hidden:chip.hidden,label:document.getElementById('focus-label').textContent,attrs:attrs(chip)},
        nodes:[...svg.querySelectorAll('[data-node-id]')].map(n=>({id:n.dataset.nodeId,attrs:attrs(n)})),edges:[...svg.querySelectorAll('[data-edge-from]')].map(n=>({key:n.dataset.edgeKey,attrs:attrs(n)})),
        hits:[...svg.querySelectorAll('[data-relationship-hit-key]')].map(n=>({key:n.dataset.relationshipHitKey,tab:n.tabIndex,attrs:attrs(n)})),
        rows:[...document.querySelectorAll('#relationship-lens-list [data-relationship-key]')].map(n=>({key:n.dataset.relationshipKey,target:n.dataset.relationshipTarget,attrs:attrs(n)})),pulse:svg.querySelectorAll('[data-relationship-pulse-overlay]').length,errors:focusErrors,external:performance.getEntriesByType('resource').map(r=>r.name).filter(n=>/^https?:/.test(n))};})()`);
    assert.deepEqual(s.errors,[],scenario);assert.deepEqual(s.external,[],scenario);records.push({scenario,...s});return s;
  }

  await t.test('five modes expose the same Focus and flowTokens surfaces at cold start',async()=>{
    const expected=['set','setMany','clear','copyLink','reach','clearReach','reachabilitySnapshot','inspectRelationship','inspectRelationshipById','reposition','relationship','reachability','active'].sort();
    for(const mode of Object.keys(cases)) {
      await load(mode);assert.deepEqual(await run('Object.keys(Archify.focus).sort()'),expected);assert.deepEqual(await run('Object.keys(Archify.flowTokens).sort()'),['create','kind','path']);
      const cold=await snapshot(mode+'-cold');assert.equal(cold.active,null);
      const id=await run(`document.querySelector('.diagram-container > svg [data-node-id]').dataset.nodeId`);await select(id);assert.equal((await snapshot(mode+'-selected')).active,id);
    }
  });
  await t.test('native node input and option variants preserve selection and cleanup side effects',async()=>{
    await load();await click(node('users'));assert.equal((await snapshot('node-click')).active,'users');
    await focus(node('users'));await key(' ','Space',32);assert.equal(await run('Archify.focus.active()'),null);
    await focus(node('cdn'));await key('Enter','Enter',13);assert.equal(await run('Archify.focus.active()'),'cdn');
    await run(`document.querySelector('.diagram-container').setAttribute('data-just-panned','true');document.querySelector('[data-node-id="users"]').dispatchEvent(new MouseEvent('click',{bubbles:true}))`);assert.equal(await run('Archify.focus.active()'),'cdn');await run(`document.querySelector('.diagram-container').removeAttribute('data-just-panned')`);
    const many=await run(`(()=>{const result=Archify.focus.setMany(['cdn','missing','users','cdn'],{toggle:false,hideChip:true,updateUrl:false});const copy=Archify.focus.active();copy.push('db');return {result,active:Archify.focus.active(),hidden:document.getElementById('focus-chip').hidden};})()`);
    assert.deepEqual(many,{result:true,active:['cdn','users'],hidden:true});const multi=await snapshot('multi-selection');assert.deepEqual(multi.edges.filter(e=>'data-focus-match' in e.attrs).map(e=>e.key),['a','a']);
    await run(`Archify.focus.setMany(['users','cdn'],{toggle:false,label:'Custom',urlKey:'view',urlValue:'custom'});`);assert.equal((await snapshot('custom-selection')).hash,'#view=custom');
    await run(`Archify.semanticLens.select('backend')`);assert.equal(await run(`Archify.focus.setMany([])`),false);assert.equal(await run('Archify.semanticLens.active()'),null);
    await run(`Archify.routeProbe.begin({source:'users'})`);assert.equal(await run(`Archify.focus.setMany(['missing'])`),false);assert.equal(await run('Archify.routeProbe.active()'),null);
    await run(`Archify.routeProbe.begin({source:'users'});Archify.focus.set('api',{preserveRoute:true,updateUrl:false})`);assert.notEqual(await run('Archify.routeProbe.active()'),null);
    await run(`Archify.routeProbe.clear();Archify.focus.set('api',{toggle:false});Archify.focus.clear({restoreFocus:true,updateUrl:false,preserveView:true})`);assert.equal(await run('document.activeElement.dataset.nodeId'),'api');assert.equal(await run('Archify.focus.active()'),null);
    await run('Archify.focus.clear();Archify.focus.clear()');assert.equal((await snapshot('repeated-clear')).hash,'');
  });
  await t.test('Passport rows preserve keyboard navigation, missing metadata and no-neighbor state',async()=>{
    await load();await select('cdn');await focus('#btn-focus-relations');await key('Enter','Enter',13);
    assert.equal(await run(`document.querySelectorAll('#relationship-lens-list [data-relationship-target]').length`),5);
    await focus(relation('a'));await key('End','End',35);assert.equal(await run('document.activeElement.dataset.relationshipKey'),'i');await key('Home','Home',36);assert.equal(await run('document.activeElement.dataset.relationshipKey'),'b');
    await key('ArrowDown','ArrowDown',40);assert.equal(await run('document.activeElement.dataset.relationshipKey'),'d');await key('Enter','Enter',13);assert.equal(await run('Archify.focus.active()'),'api');assert.equal(await run('document.activeElement.dataset.nodeId'),'api');
    await select('isolated');const s=await snapshot('isolated-passport');assert.equal(s.rows.length,0);assert.deepEqual(await run(`({up:document.getElementById('btn-reach-upstream').disabled,down:document.getElementById('btn-reach-downstream').disabled,evidence:document.getElementById('focus-evidence').hidden,detail:document.getElementById('focus-detail').hidden})`),{up:true,down:true,evidence:true,detail:true});assert.equal(await run(`Archify.focus.reach('upstream')`),false);
  });
  await t.test('relationship focus and pointer intents yield to pins and direct keyboard exploration',async()=>{
    await load();await select('cdn');await focus('#btn-focus-relations');await key('Enter','Enter',13);await move(relation('a'));await focus(relation('b'));await stable();assert.equal((await snapshot('focus-over-hover')).svg['data-relationship-preview-active'],'b');
    await run('document.activeElement.blur()');await stable();assert.equal((await snapshot('hover-restored')).svg['data-relationship-preview-active'],'a');
    await run('Archify.focus.clear()');await move(null);await focus(hit('d'));await key('Enter','Enter',13);assert.equal((await snapshot('direct-pin')).relationship.id,'edge-d');
    await focus(hit('e'));await stable();assert.equal(await run('Archify.focus.relationship().id'),'edge-d');await key('Escape','Escape',27);assert.equal(await run('Archify.focus.active()'),null);
    await focus(hit('a'));await key('ArrowLeft','ArrowLeft',37);assert.equal(await run('document.activeElement.dataset.relationshipKey'),'i');await key('Home','Home',36);assert.equal(await run('document.activeElement.dataset.relationshipKey'),'a');await key(' ','Space',32);assert.equal(await run('Archify.focus.relationship().id'),'edge-a');
    assert.equal(await run(`Archify.focus.inspectRelationshipById('unknown')`),false);await snapshot('direct-navigation');
  });
  await t.test('direct pointer delay, touch filtering and background input keep ownership rules',async()=>{
    await load('graph',{reduced:false});await move(hit('b'));
    await run(`focusWait(()=>document.querySelector('.diagram-container > svg').getAttribute('data-relationship-preview-active')==='b')`);
    await snapshot('direct-pointer-preview');await move(null);await stable();assert.equal((await snapshot('direct-pointer-left')).svg['data-relationship-preview-active'],undefined);
    await move(hit('b'));await move(null);await run('new Promise(resolve=>setTimeout(resolve,120))');assert.equal((await snapshot('cancelled-direct-delay')).svg['data-relationship-preview-active'],undefined);
    // Synthetic pointerType/owner fixtures complement the real mouse path above.
    await run(`document.querySelector('[data-relationship-hit-key="b"]').dispatchEvent(new PointerEvent('pointerover',{bubbles:true,pointerType:'touch'}))`);
    await run('new Promise(resolve=>setTimeout(resolve,120))');assert.equal((await snapshot('touch-fixture')).svg['data-relationship-preview-active'],undefined);
    await run(`Archify.semanticLens.select('backend');document.querySelector('[data-relationship-hit-key="b"]').dispatchEvent(new PointerEvent('pointerover',{bubbles:true,pointerType:'mouse'}))`);
    await run('new Promise(resolve=>setTimeout(resolve,120))');assert.equal((await snapshot('lens-blocked-fixture')).svg['data-relationship-preview-active'],undefined);
    await load();await click(node('users'));
    const p=await run(`(()=>{for(let y=30;y<innerHeight;y+=25)for(let x=10;x<innerWidth;x+=25){const e=document.elementFromPoint(x,y);if(e?.matches('.diagram-container > svg'))return {x,y};}throw new Error('No visible SVG background in fixture');})()`);
    await send('Input.dispatchMouseEvent',{type:'mousePressed',...p,button:'left',clickCount:1});await send('Input.dispatchMouseEvent',{type:'mouseReleased',...p,button:'left',clickCount:1});await stable();assert.equal((await snapshot('background-click')).active,null);
  });
  await t.test('authored graph geometry and shared token kinds preserve grouping and direction',async()=>{
    await load();const result=await run(`(()=>{const f=Archify.flowTokens,svg=document.querySelector('.diagram-container > svg');const edges=[...svg.querySelectorAll('[data-edge-from]')];return edges.filter(e=>e.matches('path,line,polyline')||e.querySelector('path')).map(e=>{const shape=e.matches('path,line,polyline')?e:e.querySelector('path');const token=f.create(e,shape,{duration:'0.78s',className:'story-token'});return {key:e.dataset.edgeKey,kind:f.kind(e),path:f.path(shape),duration:token.querySelector('animateMotion').getAttribute('dur'),cloned:!token.isConnected};});})()`);
    assert.equal(result.length,9);assert.deepEqual(result.map(r=>r.kind),['call','call','call','call','call','data','call','data','event']);assert.equal(result[1].path,'M 245 180 L 380 180');assert.equal(result[2].path,'M 380 210 L 110 210 L 110 180');assert.ok(result.every(r=>r.cloned&&r.duration==='0.78s'));records.push({scenario:'flow-tokens',result});
    assert.deepEqual(await run(`({missing:Archify.flowTokens.create(null,null),empty:Archify.flowTokens.path(null)})`),{missing:null,empty:''});
    await load('no-geometry');assert.equal(await run(`Archify.focus.inspectRelationshipById('edge-f')`),false);await snapshot('missing-geometry');
  });
  await t.test('reachability handles cycles, parallel edges, toggles and strict snapshot rejection',async()=>{
    await load();await select('cdn');assert.equal(await run(`Archify.focus.reach('downstream',{reveal:false})`),true);let s=await snapshot('reach-downstream');assert.deepEqual(s.reach.nodeIds,['cdn','lb','api','users','db','cache']);assert.equal(s.reach.maxDepth,3);assert.ok(s.snapshot);assert.equal(s.reach.edgeKeys.length,8);
    assert.equal(await run(`(()=>{const copy=Archify.focus.reachability();copy.nodeIds.length=0;return Archify.focus.reachability().nodeIds.length;})()`),6);
    await run(`Archify.focus.reach('downstream')`);assert.equal(await run('Archify.focus.reachabilitySnapshot()'),null);
    await run(`Archify.focus.reach('upstream',{reveal:false})`);s=await snapshot('reach-upstream');assert.deepEqual(s.reach.nodeIds,['cdn','users','worker','lb']);
    for(const mutation of [
      `svg.querySelector('[data-node-id="users"]').removeAttribute('data-reach-match')`,
      `svg.appendChild(svg.querySelector('[data-node-id="users"]').cloneNode(true))`,
      `svg.appendChild(svg.querySelector('path[data-edge-key="a"]').cloneNode(true))`,
    ]) { await load();await select('cdn');await run(`Archify.focus.reach('downstream',{reveal:false});const svg=document.querySelector('.diagram-container > svg');${mutation}`);assert.equal(await run('Archify.focus.reachabilitySnapshot()'),null); }
    await load();await select('cdn');await run(`Archify.focus.reach('downstream',{reveal:false});Archify.focus.clearReach({updateUrl:true})`);assert.equal((await snapshot('clear-reach')).hash,'#focus=cdn');await select('isolated');assert.equal(await run(`Archify.focus.reach('downstream')`),false);
  });
  await t.test('cold URLs and hashchange preserve focus, relation, reach and query semantics',async()=>{
    for(const [hash,active,relationId] of [['#focus=cdn&reach=downstream','cdn',null],['#relation=edge-d','cdn','edge-d'],['#relation=unknown',null,null],['#view=request-path','users',null],['#focus=isolated','isolated',null],['#route=users~db',null,null]]) {
      await load('graph',{hash});const s=await snapshot('cold-'+hash);if(hash.startsWith('#view=')){assert.ok(s.active);continue;}assert.equal(s.active,active);assert.equal(s.relationship?.id||null,relationId);
    }
    await load();await run(`new Promise(resolve=>{addEventListener('hashchange',()=>requestAnimationFrame(resolve),{once:true});location.hash='focus=cdn&reach=upstream';})`);await stable();assert.equal((await snapshot('hash-reach')).reach.direction,'upstream');
    await run(`new Promise(resolve=>{addEventListener('hashchange',()=>requestAnimationFrame(resolve),{once:true});location.hash='';})`);await stable();assert.equal(await run('Archify.focus.active()'),null);
    await load('graph',{hash:'&embed=1#relation=edge-a'});assert.equal(await run('Archify.focus.relationship()'),null);assert.equal(await run(`document.querySelectorAll('[data-relationship-hit-key]').length`),0);
  });
  await t.test('copy fallback and delayed feedback retain node, reach and relation links',async()=>{
    for(const mode of ['success','reject','missing','false','throw']) {
      await load();await select('cdn');await run(`Archify.focus.reach('downstream',{reveal:false})`);
      const r=await run(`(async()=>{let value;const exec=document.execCommand;Object.defineProperty(navigator,'clipboard',{configurable:true,value:${mode==='missing'?'undefined':`{writeText:text=>{value=text;return ${mode==='success'?'Promise.resolve()':'Promise.reject(new Error("denied"))'};}}`}});
        document.execCommand=()=>{value=document.querySelector('textarea[readonly]').value;${mode==='throw'?'throw new Error("copy denied");':`return ${mode==='false'?'false':'true'};`}};
        try{const copied=await Archify.focus.copyLink();return {copied,hash:new URL(value).hash,query:new URL(value).search,label:document.getElementById('btn-focus-copy').textContent,remaining:document.querySelectorAll('textarea[readonly]').length};}finally{document.execCommand=exec;}})()`);
      assert.equal(r.copied,!['false','throw'].includes(mode));assert.equal(r.hash,'#focus=cdn&reach=downstream');assert.match(r.query,/keep=yes/);assert.equal(r.remaining,0);assert.equal(r.label,r.copied?'Copied':'Copy failed');records.push({scenario:'copy-'+mode,...r});
      await run(`focusWait(()=>document.getElementById('btn-focus-copy').textContent===viewerText('viewer.passport.copy'))`);
    }
    await load();await run(`Archify.focus.inspectRelationshipById('edge-d');Object.defineProperty(navigator,'clipboard',{configurable:true,value:{writeText:value=>{window.copiedFocusUrl=value;return Promise.resolve();}}})`);assert.equal(await run('Archify.focus.copyLink()'),true);assert.equal(await run('new URL(copiedFocusUrl).hash'),'#relation=edge-d');await run('Archify.focus.clear()');await run('new Promise(resolve=>setTimeout(resolve,1650))');assert.equal(await run(`document.getElementById('btn-focus-copy').textContent`),await run(`viewerText('viewer.passport.copy')`));assert.equal(await run('Archify.focus.copyLink()'),false);
  });
  await t.test('real pulse completion and motion transitions preserve static relationship state',async()=>{
    await load('graph',{reduced:false});await run(`Archify.focus.inspectRelationshipById('edge-d')`);assert.equal(await run(`document.querySelectorAll('[data-relationship-pulse-overlay]').length`),1);
    await run(`focusWait(()=>pulseEvents.some(e=>e.type==='animationend'&&e.trusted))`);assert.equal((await snapshot('pulse-ended')).pulse,0);
    await run(`Archify.focus.clear();Archify.focus.inspectRelationshipById('edge-d')`);await send('Emulation.setEmulatedMedia',{features:[{name:'prefers-reduced-motion',value:'reduce'}]});await run(`focusWait(()=>!document.querySelector('[data-relationship-pulse-overlay]'))`);assert.equal((await snapshot('reduced-preserves-pin')).relationship.id,'edge-d');
    await load('graph',{reduced:false});await run(`Archify.focus.inspectRelationshipById('edge-d');focusWait(()=>document.querySelector('.relationship-flow-pulse').getAnimations().some(a=>a.currentTime>0))`);await run(`document.querySelector('.relationship-flow-pulse').style.animation='none'`);await run(`focusWait(()=>pulseEvents.some(e=>e.type==='animationcancel'&&e.trusted))`);assert.equal(await run(`document.querySelectorAll('[data-relationship-pulse-overlay]').length`),0);
    await run(`Archify.focus.clear();Object.defineProperty(document,'hidden',{configurable:true,value:true});Archify.focus.inspectRelationshipById('edge-d');document.dispatchEvent(new Event('visibilitychange'))`);assert.equal((await snapshot('hidden-fixture')).pulse,0);
    await load('graph',{reduced:false});await run(`Archify.motionGovernor.pause();Archify.focus.inspectRelationshipById('edge-d')`);assert.equal((await snapshot('still-preserves-pin')).pulse,0);
  });
  await t.test('real SVG export strips temporary Focus, Reach and relationship state from clones',async()=>{
    for(const [name,setup] of [['focus',`Archify.focus.set('cdn')`],['reach',`Archify.focus.set('cdn');Archify.focus.reach('downstream',{reveal:false})`],['pin',`Archify.focus.inspectRelationshipById('edge-d')`],['preview',`Archify.focus.set('cdn');document.getElementById('btn-focus-relations').click();document.querySelector('#relationship-lens-list [data-relationship-key="b"]').focus()`]]) {
      await load('graph',{reduced:false});await run(setup);
      const present=await run(`(()=>{const svg=document.querySelector('.diagram-container > svg');return {focus:svg.hasAttribute('data-focus-active'),reach:svg.hasAttribute('data-reach-active'),pin:svg.hasAttribute('data-relationship-pin-active'),preview:svg.hasAttribute('data-relationship-preview-active'),pulse:!!svg.querySelector('[data-relationship-pulse-overlay]'),hits:!!svg.querySelector('[data-relationship-hit-overlay]')};})()`);
      assert.deepEqual(present,{focus:true,reach:name==='reach',pin:name==='pin',preview:['pin','preview'].includes(name),pulse:['pin','preview'].includes(name),hits:true});
      const r=await run(`(async()=>{const svg=document.querySelector('.diagram-container > svg'),before=svg.outerHTML,create=URL.createObjectURL;let blob,after;
        const geometry=root=>[...root.querySelectorAll('[data-edge-from]')].map(n=>({tag:n.tagName,key:n.dataset.edgeKey,d:n.getAttribute('d'),points:n.getAttribute('points'),transform:n.getAttribute('transform')}));const source=geometry(svg);
        URL.createObjectURL=value=>{if(value.type.startsWith('image/svg+xml'))blob=value;return create.call(URL,value);};try{const pending=Archify.exportMenu.run('svg');after=svg.outerHTML;await pending;}finally{URL.createObjectURL=create;}
        const root=new DOMParser().parseFromString(await blob.text(),'image/svg+xml').documentElement;
        return {liveSame:before===after,geometrySame:JSON.stringify(source)===JSON.stringify(geometry(root)),viewBox:root.getAttribute('viewBox')===svg.getAttribute('viewBox'),clean:![root,...root.querySelectorAll('*')].some(n=>[...n.attributes].some(a=>/^data-(focus|reach|relationship)/.test(a.name)))};})()`);
      // Moving focus to the export trigger clears the focus-backed preview.
      assert.deepEqual(r,{liveSame:name!=='preview',geometrySame:true,viewBox:true,clean:true});records.push({scenario:'export-'+name,present,...r});
    }
  });
  await t.test('Passport layout and dark/light output remain stable through viewport and camera changes',async()=>{
    for(const width of [390,720,1440]) {
      await load('architecture',{width});await select('api');await focus('#btn-focus-relations');await key('Enter','Enter',13);await run(`Archify.view.reveal(['api'],{reason:'focus',instant:true})`);await stable();
      const r=await run(`(()=>{const chip=document.getElementById('focus-chip'),r=chip.getBoundingClientRect();return {hidden:chip.hidden,width:r.width,left:r.left,right:r.right,viewport:innerWidth,top:r.top,bottom:r.bottom,height:innerHeight};})()`);assert.equal(r.hidden,false);assert.ok(r.width>0&&r.left>=-1&&r.right<=r.viewport+1,JSON.stringify(r));assert.ok(r.top>=-1&&r.bottom<=r.height+1,JSON.stringify(r));records.push({scenario:'layout-'+width,...r});
    }
    for(const theme of ['dark','light']) {
      await load('architecture',{theme});await select('api');await focus('#btn-focus-relations');await key('Enter','Enter',13);await stable();await snapshot(theme+'-passport');
      if(evidence){await run(`Promise.all(document.getAnimations().filter(a=>Number.isFinite(a.effect.getTiming().iterations)).map(a=>a.finished.catch(()=>{})))`);const shot=await send('Page.captureScreenshot',{format:'png'});fs.writeFileSync(path.join(evidence,theme+'-passport.png'),Buffer.from(shot.data,'base64'));}
    }
  });
});
```

## test/gallery.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { SCENARIO_RECIPES } from '../recipes/scenarios.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-gallery-'));
const generatedRoot = path.join(tmp, 'docs');

function sha256(file) {
  return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
}

function normalize(text) {
  return text.replace(/\r\n?/g, '\n');
}

test('generated proof gallery matches its sources, receipts, and checked-in artifacts', () => {
  const output = execFileSync(process.execPath, [
    path.join(repoRoot, 'scripts', 'build-gallery.mjs'),
    generatedRoot,
  ], { encoding: 'utf8' });
  assert.match(output, /gallery 11 artifacts \/ 99 checks/);

  const manifestPath = path.join(generatedRoot, 'gallery', 'manifest.json');
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
  assert.equal(manifest.schemaVersion, 1);
  assert.equal(manifest.archifyVersion, JSON.parse(fs.readFileSync(path.join(skillRoot, 'package.json'))).version);
  assert.equal(manifest.entryCount, 11);
  assert.equal(manifest.checkCount, 99);
  assert.deepEqual(new Set(manifest.entries.map((entry) => entry.type)), new Set([
    'architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle',
  ]));
  assert.deepEqual(
    Object.fromEntries(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle'].map((type) => [
      type,
      manifest.entries.filter((entry) => entry.type === type).length,
    ])),
    { architecture: 2, workflow: 3, sequence: 2, dataflow: 2, lifecycle: 2 },
  );
  assert.deepEqual(
    new Set(manifest.entries.map((entry) => entry.id)),
    new Set(SCENARIO_RECIPES.map((recipe) => recipe.proof)),
  );

  const workflow = manifest.entries.find((entry) => entry.id === 'agent-tool-call');
  assert.equal(workflow.view, 'happy-path');
  assert.equal(workflow.viewCount, 3);
  assert.deepEqual(workflow.viewIds, ['happy-path', 'safety-gate', 'evidence-loop']);
  assert.equal(workflow.guidedPlayback, true);

  const deployment = manifest.entries.find((entry) => entry.id === 'deployment-ownership');
  assert.equal(deployment.engineeringProfile, 'deployment-ownership');
  assert.ok(manifest.entries.filter((entry) => entry.id !== 'deployment-ownership')
    .every((entry) => entry.engineeringProfile === null));

  for (const entry of manifest.entries) {
    const artifact = path.join(generatedRoot, entry.artifact.replace(/^gallery\//, 'gallery/'));
    const source = path.join(generatedRoot, entry.input.replace(/^gallery\//, 'gallery/'));
    assert.ok(fs.existsSync(artifact), `${entry.id}: artifact missing`);
    assert.ok(fs.existsSync(source), `${entry.id}: source missing`);
    assert.equal(sha256(artifact), entry.artifactSha256, `${entry.id}: artifact digest drift`);
    assert.equal(sha256(source), entry.sourceSha256, `${entry.id}: source digest drift`);
    assert.equal(entry.checks.length, 9);
    assert.ok(entry.checks.every((check) => check.ok), `${entry.id}: validation receipt not green`);
    assert.equal(entry.composition.profile, 'showcase', `${entry.id}: expected showcase composition profile`);
    assert.equal(entry.composition.status, 'pass', `${entry.id}: showcase composition is not green`);
    assert.equal(entry.composition.metrics.properCrossings, 0, `${entry.id}: proper crossing debt remains`);
    assert.equal(entry.composition.metrics.ambiguousCorridors, 0, `${entry.id}: ambiguous corridor debt remains`);
    assert.equal(entry.composition.metrics.containerBorderRuns, 0, `${entry.id}: container border-run debt remains`);
    assert.equal(entry.composition.metrics.labelRouteClearanceIssues, 0, `${entry.id}: label-route clearance debt remains`);
    assert.equal(entry.composition.metrics.shortInteriorSegmentCount, 0, `${entry.id}: cramped interior turn remains`);
    assert.equal(entry.composition.metrics.microSegmentCount, 0, `${entry.id}: micro segment remains`);
    assert.equal(entry.viewCount, 3, `${entry.id}: expected a three-step reader story`);
    assert.equal(entry.guidedPlayback, true, `${entry.id}: guided playback missing`);
  }

  const html = fs.readFileSync(path.join(generatedRoot, 'gallery.html'), 'utf8');
  assert.equal((html.match(/class="showcase-card/g) || []).length, 11);
  assert.match(html, /id="gallery-manifest" type="application\/json"/);
  assert.match(html, /data-src-base="gallery\/artifacts\/agent-tool-call\.workflow\.html"/);
  assert.match(html, /agent-tool-call\.workflow\.html\?present=1&amp;play=1#view=happy-path/);
  assert.match(html, /event-stream\.dataflow\.html\?present=1&amp;play=1#view=order-transit/);
  assert.match(html, /id="proof-deployment-lifecycle"/);
  assert.match(html, /Play named chapter/);
  assert.match(html, /3 views · play/);
  assert.match(html, /Proof,<br><em>not promises\.<\/em>/);
  assert.match(html, /Five lenses\. Eleven real stories\./);
  assert.match(html, /Composition<\/span><span class="receipt-value ok" title="0 crossings · 0 border runs · 0 micro segments · 0 cramped turns">SHOWCASE · PASS/);
  assert.match(html, /Engineering profile/);
  assert.match(html, /DEPLOYMENT OWNERSHIP · PASS/);
  assert.match(html, /<link rel="stylesheet" href="assets\/site-navigation\.css">/);
  assert.match(
    fs.readFileSync(path.join(generatedRoot, 'assets/site-navigation.css'), 'utf8'),
    /\.site-nav \.nav-logo \{[^}]*min-height: 44px;/,
  );
  assert.match(html, /\.filter-button \{\s+min-height: 44px;/);
  assert.match(html, /\.card-link \{ min-height: 44px;/);
  assert.equal((html.match(/class="card-link create-link"/g) || []).length, 11);
  for (const type of ['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']) {
    assert.match(html, new RegExp(`start\\.html\\?type=${type}&amp;source=gallery`), `${type}: gallery-to-start link missing`);
  }
  assert.match(html, /class="community-callout"/);
  assert.match(html, /href="https:\/\/github\.com\/tt-a1i\/archify\/issues\/new\?template=showcase\.yml"[^>]+rel="noopener noreferrer"/);
  assert.match(html, /Share a verified diagram/);
  assert.match(html, /提交已验证成品/);

  for (const relative of [
    'gallery.html',
    'assets/site-language.js',
    'assets/site-navigation.css',
    'gallery/manifest.json',
    ...manifest.entries.flatMap((entry) => [entry.artifact, entry.input]),
  ]) {
    const fresh = path.join(generatedRoot, relative);
    const checked = path.join(repoRoot, 'docs', relative);
    assert.ok(fs.existsSync(checked), `${relative}: checked-in gallery output missing`);
    assert.equal(normalize(fs.readFileSync(fresh, 'utf8')), normalize(fs.readFileSync(checked, 'utf8')),
      `${relative}: checked-in gallery output is stale; run node scripts/build-gallery.mjs`);
  }
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/generate-validators.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { workflow as validateWorkflow } from '../renderers/shared/generated-validators.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');

function workflowDocument(schemaVersion) {
  return {
    schema_version: schemaVersion,
    diagram_type: 'workflow',
    meta: { title: 'Schema compatibility' },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [{ id: 'step', lane: 'main', col: 0, type: 'backend', label: 'Step' }],
    edges: [],
  };
}

test('generated workflow validator accepts schema versions 1 and 2 only', () => {
  assert.equal(validateWorkflow(workflowDocument(1)), true, JSON.stringify(validateWorkflow.errors));
  assert.equal(validateWorkflow(workflowDocument(2)), true, JSON.stringify(validateWorkflow.errors));
  assert.equal(validateWorkflow(workflowDocument(3)), false);
  assert.deepEqual(validateWorkflow.errors?.[0]?.params.allowedValues, [1, 2]);
});

test('validator freshness check accepts CRLF checkouts', () => {
  const scratch = fs.mkdtempSync(path.join(skillRoot, '.validator-check-'));
  try {
    fs.mkdirSync(path.join(scratch, 'scripts'));
    fs.mkdirSync(path.join(scratch, 'renderers', 'shared'), { recursive: true });
    fs.cpSync(path.join(skillRoot, 'schemas'), path.join(scratch, 'schemas'), { recursive: true });
    fs.copyFileSync(
      path.join(skillRoot, 'scripts', 'generate-validators.mjs'),
      path.join(scratch, 'scripts', 'generate-validators.mjs'),
    );

    const validator = fs.readFileSync(
      path.join(skillRoot, 'renderers', 'shared', 'generated-validators.mjs'),
      'utf8',
    );
    fs.writeFileSync(
      path.join(scratch, 'renderers', 'shared', 'generated-validators.mjs'),
      validator.replace(/\r\n?|\n/g, '\r\n'),
    );

    const result = spawnSync(process.execPath, [
      path.join(scratch, 'scripts', 'generate-validators.mjs'),
      '--check',
    ], { encoding: 'utf8' });

    assert.equal(result.status, 0, result.stderr);
  } finally {
    fs.rmSync(scratch, { recursive: true, force: true });
  }
});
```

## test/generate-viewer.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const marker = '/* ARCHIFY:READER_LAYOUT */';
const exportMarker = '/* ARCHIFY:EXPORT */';
const cleanupMarker = '/* ARCHIFY:EXPORT_CLEANUP */';
const chromeMarker = '/* ARCHIFY:CHROME_LAYOUT */';
const cameraMarker = '/* ARCHIFY:CAMERA */';
const radarMarker = '/* ARCHIFY:RADAR */';
const motionMarker = '/* ARCHIFY:MOTION_GOVERNOR */';
const finderMarker = '/* ARCHIFY:NODE_FINDER */';
const intentMarker = '/* ARCHIFY:INTENT_TRACE */';
const lensMarker = '/* ARCHIFY:SEMANTIC_LENS */';
const routeMarker = '/* ARCHIFY:ROUTE_PROBE */';
const focusMarker = '/* ARCHIFY:FOCUS */';
const guidedMarker = '/* ARCHIFY:GUIDED_VIEWS */';
const fragments = { export: exportMarker, reader: marker, cleanup: cleanupMarker, chrome: chromeMarker, camera: cameraMarker, radar: radarMarker, motion: motionMarker, finder: finderMarker, intent: intentMarker, lens: lensMarker, route: routeMarker, guided: guidedMarker, focus: focusMarker };

function fixture(t) {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-viewer-build-'));
  t.after(() => fs.rmSync(root, { recursive: true, force: true }));
  fs.mkdirSync(path.join(root, 'scripts'));
  fs.mkdirSync(path.join(root, 'archify/assets'), { recursive: true });
  fs.cpSync(path.join(repoRoot, 'viewer'), path.join(root, 'viewer'), { recursive: true });
  fs.copyFileSync(path.join(repoRoot, 'scripts/generate-viewer.mjs'), path.join(root, 'scripts/generate-viewer.mjs'));
  const output = path.join(root, 'archify/assets/template.html');
  fs.copyFileSync(path.join(repoRoot, 'archify/assets/template.html'), output);
  return {
    root, output,
    shell: path.join(root, 'viewer/template.source.html'),
    export: path.join(root, 'viewer/export.js'),
    reader: path.join(root, 'viewer/reader-layout.js'),
    cleanup: path.join(root, 'viewer/export-cleanup.js'),
    chrome: path.join(root, 'viewer/viewer-chrome-layout.js'),
    camera: path.join(root, 'viewer/viewer-camera.js'),
    radar: path.join(root, 'viewer/semantic-radar.js'),
    motion: path.join(root, 'viewer/motion-governor.js'),
    finder: path.join(root, 'viewer/node-finder.js'),
    intent: path.join(root, 'viewer/intent-trace.js'),
    lens: path.join(root, 'viewer/semantic-lens.js'),
    route: path.join(root, 'viewer/route-probe.js'),
    guided: path.join(root, 'viewer/guided-views.js'),
    focus: path.join(root, 'viewer/focus.js'),
    run: (...args) => spawnSync(process.execPath, [path.join(root, 'scripts/generate-viewer.mjs'), ...args], {
      cwd: os.tmpdir(), encoding: 'utf8',
    }),
  };
}

test('the committed Viewer rebuilds deterministically outside the repository working directory', (t) => {
  const f = fixture(t);
  const baseline = fs.readFileSync(f.output);
  for (let attempt = 0; attempt < 2; attempt += 1) {
    const generated = f.run();
    assert.equal(generated.status, 0, generated.stderr);
    assert.deepEqual(fs.readFileSync(f.output), baseline);
  }
  const beforeCheck = fs.statSync(f.output).mtimeMs;
  const checked = f.run('--check');
  assert.equal(checked.status, 0, checked.stderr);
  assert.equal(fs.statSync(f.output).mtimeMs, beforeCheck, '--check must not rewrite output');
});

test('editing any authoritative source requires explicit regeneration', (t) => {
  const f = fixture(t);
  for (const input of [f.shell, f.export, f.reader, f.cleanup, f.chrome, f.camera, f.radar, f.motion, f.finder, f.intent, f.lens, f.route, f.guided, f.focus]) {
    const previous = fs.readFileSync(f.output);
    fs.appendFileSync(input, '\n/* source change */\n');
    const stale = f.run('--check');
    assert.equal(stale.status, 1);
    assert.match(stale.stderr, /stale.*generate:viewer/);
    assert.deepEqual(fs.readFileSync(f.output), previous);
    assert.equal(f.run().status, 0);
    assert.equal(f.run('--check').status, 0);
    assert.notDeepEqual(fs.readFileSync(f.output), previous);
  }
});

test('a missing generated template is stale and can be regenerated', (t) => {
  const f = fixture(t);
  fs.unlinkSync(f.output);
  assert.equal(f.run('--check').status, 1);
  assert.equal(fs.existsSync(f.output), false);
  assert.equal(f.run().status, 0);
  assert.equal(f.run('--check').status, 0);
});

for (const [fragment, slot] of Object.entries(fragments)) {
  for (const failure of ['missing shell', 'missing fragment', 'missing marker', 'duplicate marker', 'empty fragment', ...Object.keys(fragments).map(name => `${name} marker`)]) {
    test(`assembly rejects ${fragment}: ${failure} without overwriting a valid artifact`, (t) => {
      const f = fixture(t);
      const previous = fs.readFileSync(f.output);
      if (failure === 'missing shell') fs.unlinkSync(f.shell);
      if (failure === 'missing fragment') fs.unlinkSync(f[fragment]);
      const owner = fragment === 'cleanup' ? f.export : f.shell;
      if (failure === 'missing marker') fs.writeFileSync(owner, fs.readFileSync(owner, 'utf8').replace(slot, ''));
      if (failure === 'duplicate marker') fs.appendFileSync(owner, slot);
      if (failure === 'empty fragment') fs.writeFileSync(f[fragment], ' \n');
      const embeddedSlot = fragments[failure.replace(/ marker$/, '')];
      if (embeddedSlot) fs.appendFileSync(f[fragment], embeddedSlot);
      for (const args of [[], ['--check']]) {
        const result = f.run(...args);
        assert.equal(result.status, 1, failure);
        assert.match(result.stderr, /ENOENT|marker|empty/);
        assert.deepEqual(fs.readFileSync(f.output), previous);
        assert.deepEqual(fs.readdirSync(path.dirname(f.output)), ['template.html']);
      }
    });
  }
}

test('assembly preserves literal replacement tokens, Unicode and source line endings', (t) => {
  const f = fixture(t);
  const reader = '// $& $\' $` $$ 中文 \u{1f5fa}\r\n(function () {})();\r\n';
  fs.writeFileSync(f.shell, `<script>\r\n${focusMarker}${guidedMarker}${routeMarker}${lensMarker}${intentMarker}${finderMarker}${motionMarker}${radarMarker}${cameraMarker}${chromeMarker}${exportMarker}${marker}</script>\n`);
  fs.writeFileSync(f.export, reader + cleanupMarker);
  fs.writeFileSync(f.cleanup, reader);
  fs.writeFileSync(f.chrome, reader);
  fs.writeFileSync(f.camera, reader);
  fs.writeFileSync(f.radar, reader);
  fs.writeFileSync(f.motion, reader);
  fs.writeFileSync(f.finder, reader);
  fs.writeFileSync(f.intent, reader);
  fs.writeFileSync(f.lens, reader);
  fs.writeFileSync(f.route, reader);
  fs.writeFileSync(f.guided, reader);
  fs.writeFileSync(f.focus, reader);
  fs.writeFileSync(f.reader, reader);
  assert.equal(f.run().status, 0);
  assert.equal(fs.readFileSync(f.output, 'utf8'), `<script>\r\n${reader}${reader}${reader}${reader}${reader}${reader}${reader}${reader}${reader}${reader}${reader}${reader}${reader}</script>\n`);
  assert.equal(f.run('--check').status, 0);
});

test('an invalid invocation cannot silently regenerate the template', (t) => {
  const f = fixture(t);
  const previous = fs.readFileSync(f.output);
  const result = f.run('--chek');
  assert.equal(result.status, 1);
  assert.match(result.stderr, /Usage:/);
  assert.deepEqual(fs.readFileSync(f.output), previous);
});

for (const placement of ['additional shell slot', 'moved to shell']) {
  test(`Cleanup ownership rejects ${placement} without overwriting output`, (t) => {
    const f = fixture(t);
    const previous = fs.readFileSync(f.output);
    fs.appendFileSync(f.shell, cleanupMarker);
    if (placement === 'moved to shell') {
      fs.writeFileSync(f.export, fs.readFileSync(f.export, 'utf8').replace(cleanupMarker, ''));
    }
    for (const args of [[], ['--check']]) {
      const result = f.run(...args);
      assert.equal(result.status, 1, result.stderr);
      assert.match(result.stderr, /marker/);
      assert.deepEqual(fs.readFileSync(f.output), previous);
      assert.deepEqual(fs.readdirSync(path.dirname(f.output)), ['template.html']);
    }
  });
}
```

## test/generated-artifact-xml.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { extractSvgs, parseXml } from './helpers/xml.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const artifactRoots = [
  'archify/examples',
  'docs',
  'examples',
  'experiments',
];

function trackedHtmlArtifacts() {
  const tracked = spawnSync('git', ['ls-files', '-z', '--', ...artifactRoots], {
    cwd: repoRoot,
    encoding: 'buffer',
  });
  assert.equal(tracked.status, 0, tracked.stderr.toString());
  return tracked.stdout.toString()
    .split('\0')
    .filter((entry) => entry.endsWith('.html'))
    .sort();
}

test('artifact SVG extraction follows HTML quoting and preserves SVG document boundaries', () => {
  const extracted = extractSvgs(`
    <script>const ignored = '<svg data-node-label></svg>';</script>
    <template><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"/></template>
    <iframe srcdoc='&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;svg viewBox=&quot;0 0 1 1&quot;/&gt;&lt;/svg&gt;'></iframe>
  `);
  assert.equal(extracted.direct.length, 1, 'template SVG is markup while script text is not');
  assert.equal(extracted.embedded.length, 1, 'srcdoc contributes one top-level SVG document');
  for (const svg of [...extracted.direct, ...extracted.embedded]) assert.doesNotThrow(() => parseXml(svg));

  const inheritedNamespace = extractSvgs(`
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      <svg><use xlink:href="#icon"/></svg>
    </svg>
  `);
  assert.equal(inheritedNamespace.direct.length, 1, 'nested SVG remains inside its XML document');
  assert.doesNotThrow(() => parseXml(inheritedNamespace.direct[0]));
});

test('tracked browsable HTML embeds well-formed XML SVG', () => {
  const artifacts = trackedHtmlArtifacts();
  const checkoutArtifact = 'examples/checkout-platform-delta.html';
  assert.ok(artifacts.includes(checkoutArtifact), 'expected the tracked Checkout compare artifact');
  let checkoutSvgs;

  for (const relative of artifacts) {
    const html = fs.readFileSync(path.join(repoRoot, relative), 'utf8');
    const extracted = extractSvgs(html);
    if (relative === checkoutArtifact) checkoutSvgs = extracted;
    const svgs = [...extracted.direct, ...extracted.embedded];
    if (svgs.length === 0) continue;
    for (const [index, svg] of svgs.entries()) {
      assert.doesNotThrow(
        () => parseXml(svg),
        `${relative}: SVG ${index + 1} must be well-formed XML`,
      );
    }
  }

  assert.equal(checkoutSvgs?.direct.length, 1, 'Checkout must contain one comparison SVG');
  assert.equal(checkoutSvgs?.embedded.length, 2, 'Checkout must retain its base/head SVG snapshots');
});

test('legacy example URLs redirect to the current canonical artifacts', () => {
  for (const [legacy, canonical] of [
    ['examples/workflow-agent-tool-call.html', 'workflow-agent-tool-call-rendered.html'],
    ['examples/sequence-cache-miss.html', 'sequence-cache-miss-request.html'],
  ]) {
    const html = fs.readFileSync(path.join(repoRoot, legacy), 'utf8');
    assert.match(html, new RegExp(`<link rel="canonical" href="${canonical}">`));
    assert.match(
      html,
      new RegExp(`window\\.location\\.replace\\("${canonical}" \\+ window\\.location\\.search \\+ window\\.location\\.hash\\)`),
      `${legacy} must preserve query parameters and deep-link fragments`,
    );
    assert.equal(fs.existsSync(path.join(repoRoot, 'examples', canonical)), true);
  }
});
```

## test/geometry.test.mjs

```js
// Unit tests for the pure geometry/text helpers that every renderer leans on.
// These are exercised only transitively by the golden byte-compares, which
// can't distinguish a geometry regression from an intentional layout change —
// so they get a direct oracle here. Zero deps: node:test + node:assert.
//
//   node --test test/*.test.mjs   (or: npm test)

import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
  rectsOverlap,
  segmentIntersectsRect,
  segmentRectClearance,
  segmentRectIntersectionLength,
  collectLabelRouteClearance,
  cleanEndpointSideProblems,
  cleanFlowProblems,
  cleanCrossingProblems,
  collectAmbiguousCorridors,
  cleanAmbiguousCorridorProblems,
  collectBorderRuns,
  cleanBorderRunProblems,
  collectRouteRhythmIssues,
  cleanRouteRhythmProblems,
  routeBudgetMetrics,
  asArray,
  isFinitePoint,
  anchor,
  automaticPortRhythmBridge,
  defaultFromSide,
  defaultToSide,
  chosenSide,
  routeHonorsEndpointSides,
  polylinePath,
  roundedPath,
  labelPoint,
  suggestLabelObstacleFix,
  suggestComponentSeparation,
} from '../renderers/shared/geometry.mjs';
import { textUnits, applyTemplate, renderSemanticSigil } from '../renderers/shared/utils.mjs';

const rect = (x, y, w, h) => ({ x, y, width: w, height: h, cx: x + w / 2, cy: y + h / 2 });

test('automaticPortRhythmBridge: near parallel ports use readable outside runs', () => {
  const points = automaticPortRhythmBridge(
    [742, 300],
    [735, 180],
    'top',
    'bottom',
  );

  assert.deepEqual(points, [
    [742, 300],
    [742, 276],
    [758, 276],
    [758, 204],
    [735, 204],
    [735, 180],
  ]);
  assert.deepEqual(collectRouteRhythmIssues({
    routedRelations: [{ relation: { id: 'read' }, points }],
  }), []);
});

test('rectsOverlap: separated rects do not overlap', () => {
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(20, 0, 10, 10)), false);
});

test('rectsOverlap: clearly overlapping rects overlap', () => {
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(5, 5, 10, 10)), true);
});

test('rectsOverlap: edge-touching is NOT overlap at gap 0 (<= boundary)', () => {
  // a ends at x=10, b starts at x=10 — exactly touching.
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(10, 0, 10, 10), 0), false);
});

test('rectsOverlap: positive gap flags rects within that gap as too close', () => {
  // 8px apart, required gap 8 → touching the threshold counts as too close.
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(18, 0, 10, 10), 8), false);
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(17, 0, 10, 10), 8), true);
});

test('rectsOverlap: negative gap shrinks the hit box (label-collision convention)', () => {
  // gap -2 means rects must overlap by MORE than 2px to count — a 1px sliver
  // does not. This is the sign convention the label checks rely on.
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(9, 0, 10, 10), -2), false);
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(7, 0, 10, 10), -2), true);
});

test('rectsOverlap: non-finite geometry is not an overlap', () => {
  // A component authored without pos lands here as NaN. Every comparison in the
  // negated form is false for NaN, so the unguarded version reported a collision
  // for every pair and buried the real "must include pos" diagnostic.
  const nan = rect(Number.NaN, Number.NaN, 120, 60);
  assert.equal(rectsOverlap(nan, nan, 8), false);
  assert.equal(rectsOverlap(nan, rect(0, 0, 10, 10), 8), false);
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), nan, 8), false);
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(20, 0, Number.NaN, 10)), false);
  assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(5, 5, 10, Number.POSITIVE_INFINITY)), false);
});

test('segmentIntersectsRect: detects an edge crossing a node box', () => {
  assert.equal(segmentIntersectsRect({ start: [0, 5], end: [20, 5] }, rect(8, 0, 4, 10)), true);
  assert.equal(segmentIntersectsRect({ start: [0, 20], end: [20, 20] }, rect(8, 0, 4, 10)), false);
});

test('segmentRectClearance measures horizontal, vertical, and reversed diagonal segments', () => {
  const box = rect(10, 10, 10, 10);
  assert.equal(segmentRectClearance({ start: [0, 6], end: [30, 6] }, box), 4);
  assert.equal(segmentRectClearance({ start: [6, 0], end: [6, 30] }, box), 4);
  assert.equal(segmentRectClearance({ start: [0, 0], end: [8, 8] }, box), Math.sqrt(8));
  assert.equal(segmentRectClearance({ start: [8, 8], end: [0, 0] }, box), Math.sqrt(8));
  assert.equal(segmentRectClearance({ start: [0, 15], end: [30, 15] }, box), 0);
});

test('label-route clearance locks tangent, sub-threshold, boundary, and reversed coordinates', () => {
  const box = rect(10, 10, 10, 10);
  const cases = [
    { segment: { start: [0, 10], end: [30, 10] }, clearance: 0, intersection: 10 },
    { segment: { start: [10, 0], end: [10, 30] }, clearance: 0, intersection: 10 },
    { segment: { start: [0, 0], end: [30, 30] }, clearance: 0, intersection: Math.sqrt(200) },
    { segment: { start: [0, 0], end: [10, 10] }, clearance: 0, intersection: 0 },
    { segment: { start: [0, 8.1], end: [30, 8.1] }, clearance: 1.9, intersection: 0 },
    { segment: { start: [0, 8], end: [30, 8] }, clearance: 2, intersection: 0 },
    { segment: { start: [0, 6.1], end: [30, 6.1] }, clearance: 3.9, intersection: 0 },
    { segment: { start: [0, 6], end: [30, 6] }, clearance: 4, intersection: 0 },
    { segment: { start: [0, 0], end: [5, 0] }, clearance: Math.sqrt(125), intersection: 0 },
  ];
  for (const { segment, clearance, intersection } of cases) {
    assert.ok(Math.abs(segmentRectClearance(segment, box) - clearance) < 0.000001);
    assert.ok(Math.abs(segmentRectIntersectionLength(segment, box) - intersection) < 0.000001);
    const reversed = { start: segment.end, end: segment.start };
    assert.ok(Math.abs(segmentRectClearance(reversed, box) - clearance) < 0.000001);
    assert.ok(Math.abs(segmentRectIntersectionLength(reversed, box) - intersection) < 0.000001);
  }
});

test('collectLabelRouteClearance exempts only the owning relationship at an exact threshold', () => {
  const owner = { id: 'owner', from: 'a', to: 'b' };
  const sharedSource = { id: 'other', from: 'a', to: 'c' };
  const labels = [{ relation: owner, relationIndex: 0, label: 'handoff', ...rect(80, 48, 60, 14) }];
  const routedRelations = [
    { relation: owner, relationIndex: 0, points: [[20, 60], [200, 60]] },
    { relation: sharedSource, relationIndex: 1, points: [[70, 64], [150, 64]] },
  ];
  assert.deepEqual(collectLabelRouteClearance({ labels, routedRelations, threshold: 2 }), []);
  const hits = collectLabelRouteClearance({ labels, routedRelations, threshold: 4 });
  assert.equal(hits.length, 1);
  assert.equal(hits[0].clearance, 2);
  assert.equal(hits[0].otherRelation, sharedSource);
});

test('endpoint-side direction distinguishes perpendicular entry from a tangent border run', () => {
  const clean = [[350, 160], [350, 200], [150, 200], [150, 240]];
  const tangent = [[350, 160], [350, 200], [100, 200], [100, 240], [150, 240]];
  assert.equal(routeHonorsEndpointSides(clean, 'bottom', 'top'), true);
  assert.equal(routeHonorsEndpointSides(tangent, 'bottom', 'top'), false);

  const relation = { id: 'tasks-file', from: 'cli-agents', to: 'tasks-watch', fromSide: 'bottom', toSide: 'top' };
  const problems = cleanEndpointSideProblems({
    relations: [relation],
    endpointIds: new Set(['cli-agents', 'tasks-watch']),
    pathFor: () => ({ points: tangent }),
    diagramType: 'architecture',
    relationCollection: 'connections',
  });
  assert.equal(problems.length, 1);
  assert.match(problems[0], /\[clean-flow\/endpoint-side-direction\] architecture connections\[0\] id "tasks-file"/);
  assert.match(problems[0], /final segment 3 \[100, 240\] -> \[150, 240\]/);
  assert.match(problems[0], /toSide "top".*vertical downward from above/);
});

test('endpoint-side direction can fail closed on renderer-inferred automatic sides', () => {
  const relation = { id: 'terminal-return', from: 'stream-hub', to: 'workspace' };
  const problems = cleanEndpointSideProblems({
    relations: [relation],
    endpointIds: new Set(['stream-hub', 'workspace']),
    pathFor: () => ({ points: [[700, 130], [700, 230], [160, 230], [160, 330]] }),
    diagramType: 'architecture',
    relationCollection: 'connections',
    fromSideFor: () => 'left',
    toSideFor: () => 'right',
  });
  assert.equal(problems.length, 2);
  assert.match(problems[0], /inferred fromSide "left"/);
  assert.match(problems[1], /inferred toSide "right"/);
});

test('cleanFlowProblems reports collection index, ids, segment, clearance, and fix', () => {
  const relations = [{ id: 'checkout', from: 'client', to: 'database' }];
  const obstacles = [
    { id: 'client', ...rect(0, 0, 20, 20) },
    { id: 'proxy', ...rect(40, 0, 20, 20) },
    { id: 'database', ...rect(80, 0, 20, 20) },
  ];
  const problems = cleanFlowProblems({
    relations,
    obstacles,
    pathFor: () => ({ points: [[20, 10], [80, 10]] }),
    diagramType: 'architecture',
    relationCollection: 'connections',
    obstacleKind: 'component',
    routeHint: 'set route/via'
  });
  assert.equal(problems.length, 1);
  assert.match(problems[0], /\[clean-flow\/edge-through-node\] architecture connections\[0\] id "checkout" "client" -> "database"/);
  assert.match(problems[0], /crosses component "proxy"/);
  assert.match(problems[0], /segment 0 \[20, 10\] -> \[80, 10\] \(2px clearance\)/);
  assert.match(problems[0], /set route\/via/);
});

test('cleanFlowProblems exempts endpoints and ignores missing endpoint geometry', () => {
  const endpointOnly = cleanFlowProblems({
    relations: [{ from: 'a', to: 'b' }],
    obstacles: [{ id: 'a', ...rect(0, 0, 20, 20) }, { id: 'b', ...rect(80, 0, 20, 20) }],
    pathFor: () => ({ points: [[20, 10], [80, 10]] }),
    diagramType: 'workflow',
    relationCollection: 'edges',
    obstacleKind: 'node'
  });
  assert.deepEqual(endpointOnly, []);

  let pathCalled = false;
  const missingEndpoint = cleanFlowProblems({
    relations: [{ from: 'a', to: 'ghost' }],
    obstacles: [{ id: 'a', ...rect(0, 0, 20, 20) }],
    pathFor: () => { pathCalled = true; return { points: [] }; },
    diagramType: 'workflow',
    relationCollection: 'edges',
    obstacleKind: 'node'
  });
  assert.deepEqual(missingEndpoint, []);
  assert.equal(pathCalled, false);
});

test('cleanFlowProblems uses clearance, reports the first segment, and deduplicates an obstacle', () => {
  const problems = cleanFlowProblems({
    relations: [{ from: 'a', to: 'b' }],
    obstacles: [
      { id: 'a', ...rect(-20, -10, 20, 20) },
      { id: 'near', ...rect(8, 1, 4, 2) },
      { id: 'b', ...rect(20, -10, 20, 20) },
    ],
    // Both segment 0 (within the 2px halo) and segment 2 intersect `near`.
    pathFor: () => ({ points: [[0, -1], [20, -1], [0, 5], [20, 5]] }),
    diagramType: 'workflow',
    relationCollection: 'edges',
    obstacleKind: 'node'
  });
  assert.equal(problems.length, 1);
  assert.match(problems[0], /segment 0 \[0, -1\] -> \[20, -1\]/);
});

test('cleanCrossingProblems reports one deterministic proper X in showcase', () => {
  const first = { id: 'first', from: 'a', to: 'b' };
  const second = { id: 'second', from: 'c', to: 'd' };
  const routes = new Map([
    [first, { points: [[0, 0], [100, 0], [100, 100]] }],
    [second, { points: [[50, -50], [50, 50], [150, 50], [150, -50], [50, -50]] }],
  ]);
  const problems = cleanCrossingProblems({
    relations: [first, second],
    endpointIds: new Set(['a', 'b', 'c', 'd']),
    pathFor: (relation) => routes.get(relation),
    diagramType: 'architecture',
    relationCollection: 'connections',
    profile: 'showcase',
    routeHint: 'move a via point',
  });
  assert.equal(problems.length, 1);
  assert.match(problems[0], /\[composition\/proper-crossing\] showcase architecture/);
  assert.match(problems[0], /connections\[0\] id "first" "a" -> "b" crosses connections\[1\] id "second" "c" -> "d"/);
  assert.match(problems[0], /at \[50, 0\] \(segments 0 and 0\)/);
  assert.match(problems[0], /move a via point/);
});

test('cleanCrossingProblems keeps proper X as non-blocking in standard', () => {
  const relations = [{ from: 'a', to: 'b' }, { from: 'c', to: 'd' }];
  const routes = [[[0, 50], [100, 50]], [[50, 0], [50, 100]]];
  const problems = cleanCrossingProblems({
    relations,
    endpointIds: new Set(['a', 'b', 'c', 'd']),
    pathFor: (relation) => ({ points: routes[relations.indexOf(relation)] }),
    diagramType: 'workflow',
    relationCollection: 'edges',
    profile: 'standard',
  });
  assert.deepEqual(problems, []);
});

test('cleanCrossingProblems exempts shared endpoints', () => {
  const relations = [{ from: 'a', to: 'b' }, { from: 'a', to: 'c' }];
  const routes = [[[0, 50], [100, 50]], [[50, 0], [50, 100]]];
  const problems = cleanCrossingProblems({
    relations,
    endpointIds: new Set(['a', 'b', 'c']),
    pathFor: (relation) => ({ points: routes[relations.indexOf(relation)] }),
    diagramType: 'dataflow',
    relationCollection: 'flows',
    profile: 'showcase',
  });
  assert.deepEqual(problems, []);
});

test('cleanCrossingProblems exempts endpoint touches and collinear corridors', () => {
  const relations = [
    { from: 'a', to: 'b' },
    { from: 'c', to: 'd' },
    { from: 'e', to: 'f' },
  ];
  const routes = [
    [[0, 0], [100, 0]],
    [[50, 0], [50, 50]],
    [[25, 0], [75, 0]],
  ];
  const problems = cleanCrossingProblems({
    relations,
    endpointIds: new Set(['a', 'b', 'c', 'd', 'e', 'f']),
    pathFor: (relation) => ({ points: routes[relations.indexOf(relation)] }),
    diagramType: 'lifecycle',
    relationCollection: 'transitions',
    profile: 'showcase',
  });
  assert.deepEqual(problems, []);
});

test('ambiguous corridor gate reports unrelated collinear overlap with exact identities', () => {
  const first = { id: 'first', from: 'a', to: 'b' };
  const second = { id: 'second', from: 'c', to: 'd' };
  const routes = new Map([
    [first, { points: [[0, 20], [100, 20], [100, 80]] }],
    [second, { points: [[40, 20], [140, 20], [140, 80]] }],
  ]);
  const hits = collectAmbiguousCorridors({
    routedRelations: [first, second].map((relation, relationIndex) => ({
      relation,
      relationIndex,
      points: routes.get(relation).points,
    })),
  });
  assert.equal(hits.length, 1);
  assert.equal(hits[0].overlapLength, 60);
  assert.deepEqual(hits[0].overlapStart, [40, 20]);
  assert.deepEqual(hits[0].overlapEnd, [100, 20]);

  const problems = cleanAmbiguousCorridorProblems({
    relations: [first, second],
    endpointIds: new Set(['a', 'b', 'c', 'd']),
    pathFor: (relation) => routes.get(relation),
    diagramType: 'workflow',
    relationCollection: 'edges',
    profile: 'showcase',
    routeHint: 'move a channel',
  });
  assert.equal(problems.length, 1);
  assert.match(problems[0], /\[composition\/ambiguous-corridor\] showcase workflow/);
  assert.match(problems[0], /edges\[0\] id "first" "a" -> "b" shares a 60px corridor with edges\[1\] id "second" "c" -> "d"/);
  assert.match(problems[0], /\[40, 20\] -> \[100, 20\].*move a channel/);
});

test('ambiguous corridor gate exempts shared endpoints, point touches, and overlaps below 8px', () => {
  const routedRelations = [
    { relation: { from: 'a', to: 'b' }, relationIndex: 0, points: [[0, 20], [100, 20]] },
    { relation: { from: 'a', to: 'c' }, relationIndex: 1, points: [[40, 20], [140, 20]] },
    { relation: { from: 'd', to: 'e' }, relationIndex: 2, points: [[100, 20], [100, 80]] },
    { relation: { from: 'f', to: 'g' }, relationIndex: 3, points: [[94, 60], [101, 60]] },
    { relation: { from: 'h', to: 'i' }, relationIndex: 4, points: [[98, 60], [110, 60]] },
  ];
  assert.deepEqual(collectAmbiguousCorridors({ routedRelations }), []);
});

test('ambiguous corridor gate keeps standard renderable', () => {
  const relations = [{ from: 'a', to: 'b' }, { from: 'c', to: 'd' }];
  const routes = [[[0, 20], [100, 20]], [[40, 20], [140, 20]]];
  assert.deepEqual(cleanAmbiguousCorridorProblems({
    relations,
    endpointIds: new Set(['a', 'b', 'c', 'd']),
    pathFor: (relation) => ({ points: routes[relations.indexOf(relation)] }),
    diagramType: 'architecture',
    relationCollection: 'connections',
    profile: 'standard',
  }), []);
});

test('cleanBorderRunProblems reports a deterministic long run on a rounded frame side', () => {
  const relation = { id: 'jwt', from: 'auth', to: 'api' };
  const problems = cleanBorderRunProblems({
    relations: [relation],
    frames: [{ id: 'private', label: 'Private tier', kind: 'security-group', x: 100, y: 80, width: 180, height: 120, radius: 8 }],
    pathFor: () => ({ points: [[40, 80], [220, 80], [220, 140]] }),
    diagramType: 'architecture',
    relationCollection: 'connections',
    profile: 'standard',
    routeHint: 'move the via point',
  });
  assert.equal(problems.length, 1);
  assert.match(problems[0], /\[composition\/container-border-run\] architecture connections\[0\] id "jwt" "auth" -> "api"/);
  assert.match(problems[0], /follows security-group "Private tier" top border for 112px on segment 0 \[108, 80\] -> \[220, 80\]/);
  assert.match(problems[0], /move the via point/);
});

test('border-run contract allows perpendicular crossings, point touches, and rounded corners', () => {
  const frame = { id: 'stage', kind: 'stage', x: 40, y: 40, width: 120, height: 100, radius: 10 };
  const routedRelations = [
    { relation: { from: 'a', to: 'b' }, relationIndex: 0, points: [[100, 10], [100, 80]] },
    { relation: { from: 'c', to: 'd' }, relationIndex: 1, points: [[20, 40], [40, 40], [40, 20]] },
    { relation: { from: 'e', to: 'f' }, relationIndex: 2, points: [[40, 40], [49, 40]] },
  ];
  assert.deepEqual(collectBorderRuns({ routedRelations, frames: [frame] }), []);
});

test('border-run contract detects vertical frames and merges hits per relation side', () => {
  const hits = collectBorderRuns({
    routedRelations: [{
      relation: { from: 'a', to: 'b' },
      relationIndex: 3,
      points: [[160, 60], [160, 110], [150, 110], [160, 110], [160, 135]],
    }],
    frames: [{ kind: 'lane', id: 'lane-1', x: 40, y: 40, width: 120, height: 100, radius: 10 }],
  });
  assert.equal(hits.length, 1);
  assert.equal(hits[0].side, 'right');
  assert.equal(hits[0].segmentIndex, 0);
  assert.equal(hits[0].overlapLength, 70);
});

test('border-run contract merges adjacent primitives and counts any positive straight overlap', () => {
  const hits = collectBorderRuns({
    routedRelations: [{
      relation: { from: 'a', to: 'b' },
      relationIndex: 0,
      points: [[52, 40], [70, 40], [90, 40], [90, 50]],
    }],
    frames: [{ kind: 'stage', id: 'source', x: 40, y: 40, width: 120, height: 100, radius: 10 }],
  });
  assert.equal(hits.length, 1);
  assert.equal(hits[0].overlapLength, 38);
  assert.deepEqual(hits[0].overlapStart, [52, 40]);
  assert.deepEqual(hits[0].overlapEnd, [90, 40]);
});

test('routeBudgetMetrics normalizes collinear points and records neutral route evidence', () => {
  const metrics = routeBudgetMetrics({
    routedRelations: [
      { points: [[0, 0], [10, 0], [30, 0], [30, 8], [50, 8], [50, 30]] },
      { points: [[5, 5], [5, 5]] },
    ],
  });
  assert.deepEqual(metrics, {
    maxBends: 3,
    routesOverSuggestedBends: 1,
    maxStretch: 80 / 80,
    routesOverSuggestedStretch: 0,
    minSegmentPx: 8,
    minInteriorSegmentPx: 8,
    shortSegmentCount: 1,
    shortEndpointSegmentCount: 0,
    shortInteriorSegmentCount: 1,
    microSegmentCount: 0,
  });
});

test('route rhythm separates ordinary endpoint stubs from cramped turns and micro segments', () => {
  const issues = collectRouteRhythmIssues({
    routedRelations: [
      { relation: { id: 'lane-hop', from: 'a', to: 'b' }, points: [[0, 0], [13, 0], [13, 40], [80, 40], [80, 53]] },
      { relation: { id: 'bad-turn', from: 'c', to: 'd' }, points: [[0, 80], [24, 80], [24, 89], [60, 89]] },
      { relation: { id: 'micro-stub', from: 'e', to: 'f' }, points: [[0, 120], [5, 120], [5, 180]] },
    ],
  });
  assert.deepEqual(issues.map((issue) => [issue.relation.id, issue.code, issue.position, issue.length]), [
    ['bad-turn', 'composition/short-interior-segment', 'interior', 9],
    ['micro-stub', 'composition/micro-segment', 'source-stub', 5],
  ]);
});

test('route rhythm is a showcase-only generation gate with actionable relationship identity', () => {
  const relations = [{ id: 'events', from: 'api', to: 'bus' }];
  const args = {
    relations,
    endpointIds: new Set(['api', 'bus']),
    pathFor: () => ({ points: [[10, 20], [15, 20], [15, 80]] }),
    diagramType: 'architecture',
    relationCollection: 'connections',
  };
  assert.deepEqual(cleanRouteRhythmProblems({ ...args, profile: 'standard' }), []);
  const problems = cleanRouteRhythmProblems({ ...args, profile: 'showcase' });
  assert.equal(problems.length, 1);
  assert.match(problems[0], /\[composition\/micro-segment\] showcase architecture connections\[0\] id "events"/);
  assert.match(problems[0], /5px source-stub segment 0/);
});

test('asArray coerces non-arrays to [] (degraded-mode guard)', () => {
  assert.deepEqual(asArray([1, 2]), [1, 2]);
  assert.deepEqual(asArray('oops'), []);
  assert.deepEqual(asArray(undefined), []);
  assert.deepEqual(asArray(null), []);
  assert.deepEqual(asArray({ length: 3 }), []);
});

test('isFinitePoint rejects NaN/undefined/Infinity', () => {
  assert.equal(isFinitePoint(1, 2, 3, 4), true);
  assert.equal(isFinitePoint(1, NaN), false);
  assert.equal(isFinitePoint(1, undefined), false);
  assert.equal(isFinitePoint(1, Infinity), false);
});

test('anchor returns the correct edge midpoint for each side', () => {
  const r = rect(100, 100, 40, 20); // cx=120 cy=110
  assert.deepEqual(anchor(r, 'left'), [100, 110]);
  assert.deepEqual(anchor(r, 'right'), [140, 110]);
  assert.deepEqual(anchor(r, 'top'), [120, 100]);
  assert.deepEqual(anchor(r, 'bottom'), [120, 120]);
});

test('anchor falls back to the right edge for unknown/auto sides', () => {
  const r = rect(100, 100, 40, 20);
  assert.deepEqual(anchor(r, 'auto'), [140, 110]);
  assert.deepEqual(anchor(r, undefined), [140, 110]);
});

test('defaultFromSide / defaultToSide are mirror pairs', () => {
  const a = { cx: 0, cy: 0 };
  const right = { cx: 100, cy: 0 };
  assert.equal(defaultFromSide(a, right), 'right');
  assert.equal(defaultToSide(a, right), 'left');
  const below = { cx: 0, cy: 100 };
  assert.equal(defaultFromSide(a, below), 'bottom');
  assert.equal(defaultToSide(a, below), 'top');
});

test('chosenSide treats explicit "auto" as "use the geometric fallback"', () => {
  assert.equal(chosenSide('left', 'right'), 'left');
  assert.equal(chosenSide('auto', 'right'), 'right');
  assert.equal(chosenSide(undefined, 'right'), 'right');
});

test('polylinePath emits M then L commands', () => {
  assert.equal(polylinePath([[0, 0], [10, 0], [10, 10]]), 'M 0 0 L 10 0 L 10 10');
});

test('roundedPath degrades to a polyline for <3 points or radius<=0', () => {
  assert.equal(roundedPath([[0, 0], [10, 0]], 10), 'M 0 0 L 10 0');
  assert.equal(roundedPath([[0, 0], [10, 0], [10, 10]], 0), 'M 0 0 L 10 0 L 10 10');
});

test('roundedPath inserts a quadratic corner and never emits NaN', () => {
  const d = roundedPath([[0, 0], [100, 0], [100, 100]], 10);
  assert.match(d, /Q 100 0/); // corner pivots on the bend point
  assert.doesNotMatch(d, /NaN/);
});

test('roundedPath clamps radius to half the shorter adjacent segment', () => {
  // 6px segments with radius 10 → r clamps to 3; no overshoot / NaN.
  const d = roundedPath([[0, 0], [6, 0], [6, 6]], 10);
  assert.doesNotMatch(d, /NaN/);
  assert.match(d, /^M 0 0/);
});

test('labelPoint: 2-point path is the midpoint lifted 10px, plus offsets', () => {
  assert.deepEqual(labelPoint({}, [[0, 100], [100, 100]]), [50, 90]);
  assert.deepEqual(labelPoint({ labelDx: 5, labelDy: -4 }, [[0, 100], [100, 100]]), [55, 86]);
});

test('labelPoint: labelSegment selects a segment and clamps to range', () => {
  const pts = [[0, 0], [100, 0], [100, 100], [200, 100]];
  // segment 0 → midpoint of pts[0],pts[1] = (50,0) lifted 10
  assert.deepEqual(labelPoint({ labelSegment: 0 }, pts), [50, -10]);
  // segment 99 clamps to the last segment
  assert.deepEqual(labelPoint({ labelSegment: 99 }, pts), [150, 90]);
});

test('labelPoint: explicit labelAt wins outright', () => {
  assert.deepEqual(labelPoint({ labelAt: [7, 8] }, [[0, 0], [100, 0]]), [7, 8]);
});

test('textUnits: ASCII=1, CJK=2, mixed sums, fullwidth supplementary=2', () => {
  assert.equal(textUnits('abc'), 3);
  assert.equal(textUnits('中文'), 4);
  assert.equal(textUnits('a中'), 3);
  assert.equal(textUnits(''), 0);
  assert.equal(textUnits(null), 0);
  assert.equal(textUnits('𠀀'), 2); // CJK Ext-B (supplementary plane)
  assert.equal(textUnits('🚀'), 2); // emoji
  assert.equal(textUnits('注入提示词'), 10); // issue #14 original label
  assert.equal(textUnits('！＠＃０１２'), 12); // fullwidth punctuation + digits
});

test('textUnits follows wide and halfwidth East Asian presentation boundaries', () => {
  assert.equal(textUnits('あカ'), 4); // Hiragana + Katakana are wide
  assert.equal(textUnits('ㄅㆠ'), 4); // Bopomofo + extended Bopomofo are wide
  assert.equal(textUnits('ㄱ'), 2); // Hangul compatibility letter is wide
  assert.equal(textUnits('︐︙'), 4); // vertical punctuation forms are wide
  assert.equal(textUnits('ｶﾀｶﾅ'), 4); // halfwidth Katakana stays one unit per glyph
  assert.equal(textUnits('ꥠ'), 2); // Hangul Jamo Extended-A is wide
});

test('textUnits counts emoji-presentation symbols in the BMP as wide', () => {
  // These render at the same square advance as the supplementary-plane emoji,
  // so counting them as one unit under-measures a label and lets it overflow
  // its node while the layout receipt still reads clean.
  assert.equal(textUnits('✅'), 2);
  assert.equal(textUnits('⭐'), 2);
  assert.equal(textUnits('⚡'), 2);
  assert.equal(textUnits('⌛'), 2);
  assert.equal(textUnits('⏰'), 2);
  assert.equal(textUnits('⛔'), 2);
  assert.equal(textUnits('❗'), 2);
  assert.equal(textUnits('⬛'), 2);
  assert.equal(textUnits('☕'), 2);
  assert.equal(textUnits('♿'), 2);
  assert.equal(textUnits('✅ Done'), 7);
  // Narrow and ambiguous neighbours in the same blocks stay one unit.
  assert.equal(textUnits('→'), 1); // rightwards arrow
  assert.equal(textUnits('☎'), 1); // black telephone
  assert.equal(textUnits('①'), 1); // circled digit one
  // Unicode 16.0 moved these from Neutral to Wide.
  assert.equal(textUnits('☰'), 2); // trigram for heaven
  assert.equal(textUnits('☷'), 2); // trigram for earth
  assert.equal(textUnits('⚊'), 2); // monogram for yang
  assert.equal(textUnits('⚏'), 2); // digram for greater yin
  // Hangul Jamo Extended-A stops at its last assigned jamo; the unassigned
  // tail of the block defaults to Neutral.
  assert.equal(textUnits('ꥼ'), 2);
  assert.equal(textUnits('꥽'), 1);
});

test('textUnits measures a variation-selector sequence from the selector', () => {
  // VS16 asks for emoji presentation: the pair renders as one square, so it
  // must stay two units even though the base is now counted wide on its own.
  assert.equal(textUnits('⭐️'), 2); // star
  assert.equal(textUnits('✅️'), 2); // check mark button
  assert.equal(textUnits('☕️'), 2); // hot beverage
  assert.equal(textUnits('⚡️'), 2); // high voltage
  // Same rule the other way: a narrow base forced to emoji presentation
  // renders as a square and is two units, not one.
  assert.equal(textUnits('✈️'), 2); // airplane
  assert.equal(textUnits('❤️'), 2); // red heart
  // VS15 asks for text presentation, which renders narrow.
  assert.equal(textUnits('⭐︎'), 1);
  assert.equal(textUnits('✈︎'), 1);
  // The selector never adds width of its own, alone or in a run.
  assert.equal(textUnits('️'), 0);
  assert.equal(textUnits('✅️ Done'), 7);
  assert.equal(textUnits('⭐️⭐️'), 4);
});

test('semantic sigils cover every component and lifecycle kind without literal color', () => {
  const kinds = [
    'frontend', 'backend', 'database', 'cloud', 'security', 'messagebus', 'external',
    'start', 'active', 'waiting', 'success', 'failure', 'neutral',
  ];
  for (const kind of kinds) {
    const sigil = renderSemanticSigil(kind, { x: 12, y: 18 });
    assert.match(sigil, new RegExp(`data-semantic-sigil="${kind}"`), kind);
    assert.match(sigil, /aria-hidden="true"/, kind);
    assert.match(sigil, /class="semantic-sigil s-[a-z]+"/, kind);
    assert.match(sigil, /transform="translate\(12 18\) scale\(0\.6875\)"/, kind);
    assert.doesNotMatch(sigil, /#[0-9a-f]{3,8}|rgba?\(/i, kind);
  }
});

test('unknown semantic sigils fail closed to a neutral role stamp', () => {
  const sigil = renderSemanticSigil('vendor-logo', { x: 0, y: 0, size: 16 });
  assert.match(sigil, /data-semantic-sigil="neutral"/);
  assert.match(sigil, /class="semantic-sigil s-external"/);
  assert.match(sigil, /scale\(1\)/);
});

test('suggestLabelObstacleFix includes rects and labelAt/labelDy hints', () => {
  const labelRect = { x: 100, y: 180, width: 48, height: 14, label: '写入' };
  const obstacle = { id: 'memtool', x: 30, y: 130, width: 230, height: 58 };
  const hint = suggestLabelObstacleFix(labelRect, 124, 188, obstacle);
  assert.match(hint, /label rect: \[100, 180, 48, 14\]/);
  assert.match(hint, /component "memtool"/);
  assert.match(hint, /Suggested fix: labelAt/);
  assert.match(hint, /labelDy \+\d+/);
});

test('suggestComponentSeparation proposes nudged pos', () => {
  const a = { id: 'api', x: 100, y: 200, width: 120, height: 60 };
  const b = { id: 'db', x: 150, y: 200, width: 120, height: 60 };
  const hint = suggestComponentSeparation(a, b, 8);
  assert.match(hint, /move "db" pos to \[228, 200\]/);
});

test('applyTemplate preserves dollar sequences in titles', () => {
  const template = `<html lang="en" data-theme="dark" data-preset="[VISUAL PRESET]">
<title>[PROJECT NAME] Architecture Diagram</title>
<h1>[PROJECT NAME] Architecture</h1>
<p class="subtitle">[Subtitle description]</p>
<!-- ARCHIFY:GUIDED_VIEWS_DATA -->
      <!-- ARCHIFY:SVG_SLOT_START --><svg></svg>      <!-- ARCHIFY:SVG_SLOT_END -->
    <!-- ARCHIFY:CARDS_SLOT_START --><div></div>    <!-- ARCHIFY:CARDS_SLOT_END -->`;
  const html = applyTemplate(template, {
    title: 'Plan $$50 tier',
    subtitle: 'test',
    svg: '<svg/>',
    cards: '',
  });
  assert.match(html, /Plan \$\$50 tier/);
  assert.match(html, /<p class="subtitle">test<\/p>/);
});

test('applyTemplate omits the subtitle row when no subtitle is authored', () => {
  const template = `<html lang="en" data-theme="dark" data-preset="[VISUAL PRESET]">
<title>[PROJECT NAME] Architecture Diagram</title>
<h1>[PROJECT NAME] Architecture</h1>
<p class="subtitle">[Subtitle description]</p>
<!-- ARCHIFY:GUIDED_VIEWS_DATA -->
      <!-- ARCHIFY:SVG_SLOT_START --><svg></svg>      <!-- ARCHIFY:SVG_SLOT_END -->
    <!-- ARCHIFY:CARDS_SLOT_START --><div></div>    <!-- ARCHIFY:CARDS_SLOT_END -->`;
  const html = applyTemplate(template, {
    title: 'Focused title',
    subtitle: '   ',
    svg: '<svg/>',
    cards: '',
  });
  assert.doesNotMatch(html, /class="subtitle"/);
  assert.doesNotMatch(html, /Subtitle description/);
});

test('applyTemplate requires the new evidence slot only when evidence is present', () => {
  const legacyTemplate = `<html lang="en" data-theme="dark" data-preset="[VISUAL PRESET]">
<title>[PROJECT NAME] Architecture Diagram</title>
<h1>[PROJECT NAME] Architecture</h1>
<p class="subtitle">[Subtitle description]</p>
<!-- ARCHIFY:GUIDED_VIEWS_DATA -->
      <!-- ARCHIFY:SVG_SLOT_START --><svg></svg>      <!-- ARCHIFY:SVG_SLOT_END -->
    <!-- ARCHIFY:CARDS_SLOT_START --><div></div>    <!-- ARCHIFY:CARDS_SLOT_END -->`;
  assert.doesNotThrow(() => applyTemplate(legacyTemplate, {
    title: 'Legacy', subtitle: '', svg: '<svg/>', cards: '',
  }));
  assert.throws(() => applyTemplate(legacyTemplate, {
    title: 'Evidence', subtitle: '', svg: '<svg/>', cards: '',
    sourceEvidence: { verified: true },
  }), /repository evidence requires placeholder/);
});
```

## test/golden.mjs

```js
// Golden-file harness for the archify renderers. No test framework needed:
// renderers are deterministic, so fresh renders must match both checked-in
// development and packaged example HTML aside from platform checkout line endings. Also covers schema enforcement (negative cases),
// template freshness of the architecture-mode example, and version sync.
//
// Run from the skill folder: npm test

import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-test-'));

let failures = 0;

function check(name, ok, detail) {
  if (ok) {
    console.log(`  ok    ${name}`);
  } else {
    failures += 1;
    console.error(`  FAIL  ${name}${detail ? ` — ${detail}` : ''}`);
  }
}

function render(mode, inputPath, outPath) {
  execFileSync('node', [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    inputPath,
    outPath,
  ], { stdio: ['ignore', 'ignore', 'pipe'] });
}

function normalizeNewlines(text) {
  return text.replace(/\r\n?/g, '\n');
}

function shieldsBadgeMessages(source, label) {
  const marker = `/badge/${label}-`;
  const messages = [];
  let searchFrom = 0;
  while (searchFrom < source.length) {
    const start = source.indexOf(marker, searchFrom);
    if (start === -1) break;
    let cursor = start + marker.length;
    let message = '';
    while (cursor < source.length) {
      const character = source[cursor];
      const next = source[cursor + 1];
      if (character === '-' && next === '-') {
        message += '-';
        cursor += 2;
      } else if (character === '_' && next === '_') {
        message += '_';
        cursor += 2;
      } else if (character === '-') {
        break;
      } else {
        message += character;
        cursor += 1;
      }
    }
    try { messages.push(decodeURIComponent(message)); } catch { messages.push(message); }
    searchFrom = cursor + 1;
  }
  return messages;
}

// ---------------------------------------------------------------------------
console.log('golden renders (renderer output must match checked-in examples)');

const GOLDEN = [
  ['workflow', 'agent-tool-call.workflow.json', 'workflow-agent-tool-call-rendered.html'],
  ['sequence', 'cache-miss-request.sequence.json', 'sequence-cache-miss-request.html'],
  ['dataflow', 'product-analytics.dataflow.json', 'dataflow-product-analytics.html'],
  ['lifecycle', 'agent-run.lifecycle.json', 'lifecycle-agent-run.html'],
  ['architecture', 'web-app.architecture.json', 'web-app-rendered.html'],
];

for (const [mode, input, golden] of GOLDEN) {
  const out = path.join(tmp, golden);
  try {
    render(mode, path.join(skillRoot, 'examples', input), out);
    const fresh = fs.readFileSync(out, 'utf8');
    const checked = fs.readFileSync(path.join(repoRoot, 'examples', golden), 'utf8');
    const packaged = fs.readFileSync(path.join(skillRoot, 'examples', golden), 'utf8');
    check(`${mode}: ${golden}`, normalizeNewlines(fresh) === normalizeNewlines(checked),
      `fresh render differs from examples/${golden}; if the change is intentional, re-render the examples and commit them`);
    check(`${mode}: packaged ${golden}`, normalizeNewlines(fresh) === normalizeNewlines(packaged),
      `fresh render differs from archify/examples/${golden}; re-render the packaged examples and rebuild archify.zip`);
  } catch (err) {
    check(`${mode}: ${golden}`, false, String(err.stderr || err.message).slice(0, 300));
  }
}

// ---------------------------------------------------------------------------
console.log('schema enforcement (invalid JSON must fail with a path-prefixed message)');

function expectFailure(name, mode, mutate, expectInMessage) {
  const base = JSON.parse(fs.readFileSync(
    path.join(skillRoot, 'examples', GOLDEN.find(([m]) => m === mode)[1]), 'utf8'));
  mutate(base);
  const input = path.join(tmp, `neg-${name.replace(/[^a-z0-9]+/gi, '-')}.json`);
  fs.writeFileSync(input, JSON.stringify(base));
  try {
    render(mode, input, path.join(tmp, 'neg-out.html'));
    check(name, false, 'renderer exited 0 on invalid input');
  } catch (err) {
    const message = String(err.stderr || err.message);
    check(name, message.includes(expectInMessage),
      `expected "${expectInMessage}" in:\n${message.slice(0, 300)}`);
  }
}

expectFailure('card dot outside enum', 'workflow',
  (d) => { d.cards[0].dot = 'pink'; }, '/cards/0/dot');
expectFailure('node id starting with a digit', 'workflow',
  (d) => { d.nodes[0].id = '1user'; }, 'pattern');
expectFailure('extra property rejected', 'workflow',
  (d) => { d.nodes[0].colour = 'red'; }, 'additional properties');
expectFailure('column beyond layout maximum', 'workflow',
  (d) => { d.nodes[0].col = 7; }, '<= 5');
expectFailure('missing schema_version', 'sequence',
  (d) => { delete d.schema_version; }, 'schema_version');
expectFailure('cross-lane state overlap', 'lifecycle',
  (d) => {
    const approval = d.states.find((s) => s.id === 'approval');
    const failed = d.states.find((s) => s.id === 'failed');
    delete failed.yOffset;
    failed.col = approval.col;
  }, 'less than 10px apart');
expectFailure('zero component width rejected by schema', 'architecture',
  (d) => { d.components[0].size = [0, 60]; }, '/components/0/size/0');
expectFailure('zero component height rejected by schema', 'architecture',
  (d) => { d.components[0].size = [120, 0]; }, '/components/0/size/1');
expectFailure('negative component width rejected by schema', 'architecture',
  (d) => { d.components[0].size = [-1, 60]; }, '/components/0/size/0');

// ---------------------------------------------------------------------------
console.log('template freshness (architecture example must carry the current template)');

function blocks(html, tag) {
  const re = new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, 'g');
  return html.match(re) || [];
}

const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8');
const webApp = fs.readFileSync(path.join(repoRoot, 'examples/web-app.html'), 'utf8');
// <style> and <script> blocks pass through applyTemplate untouched, so the
// architecture-mode example must contain them verbatim or it has drifted.
for (const tag of ['style', 'script']) {
  // The guided-view JSON script is generated from meta.views; compare only
  // template-owned executable scripts, not per-diagram data payloads.
  const isTemplateOwned = (block) => !block.includes('type="application/json"');
  const t = blocks(template, tag).filter((b) => !b.includes('[PROJECT NAME]') && isTemplateOwned(b));
  const w = blocks(webApp, tag).filter((b) => !b.includes('Sample Web App') && isTemplateOwned(b));
  check(`web-app.html ${tag} blocks match template`,
    JSON.stringify(t) === JSON.stringify(w),
    'examples/web-app.html was generated from a stale template — re-derive it');
}

// ---------------------------------------------------------------------------
console.log('version sync');

const pkg = JSON.parse(fs.readFileSync(path.join(skillRoot, 'package.json'), 'utf8'));
check('template generator meta matches package.json version',
  template.includes(`<meta name="generator" content="archify ${pkg.version}">`),
  `package.json says ${pkg.version}`);

const lock = JSON.parse(fs.readFileSync(path.join(skillRoot, 'package-lock.json'), 'utf8'));
check('package-lock.json version matches package.json',
  lock.version === pkg.version && lock.packages?.['']?.version === pkg.version,
  `lockfile says ${lock.version} — run npm install and rebuild the zip`);

const skillMd = fs.readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8');
const skillVersion = (skillMd.match(/^\s*version:\s*"([^"]+)"/m) || [])[1];
const packageMajorMinor = pkg.version.match(/^(\d+\.\d+)\./)?.[1];
check('SKILL.md metadata version matches package.json major.minor',
  !!packageMajorMinor && skillVersion === packageMajorMinor,
  `SKILL.md says ${skillVersion}, package.json says ${pkg.version}`);

for (const readmeName of ['README.md', 'README_EN.md', 'README_ZH.md']) {
  const readme = fs.readFileSync(path.join(repoRoot, readmeName), 'utf8');
  const badgeVersions = shieldsBadgeMessages(readme, 'version');
  check(`${readmeName} badge matches package.json version`,
    badgeVersions.length > 0 && badgeVersions.every((version) => version === pkg.version),
    `${readmeName} badge says ${[...new Set(badgeVersions)].join(', ') || '(missing)'} instead of ${pkg.version}`);
}

const landingPage = fs.readFileSync(path.join(repoRoot, 'docs/index.html'), 'utf8');
const landingVersions = [...landingPage.matchAll(/\bv\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?\b/g)]
  .map((match) => match[0]);
check('GitHub Pages version labels match package.json',
  landingVersions.length > 0 && landingVersions.every((v) => v === `v${pkg.version}`),
  `landing page says ${[...new Set(landingVersions)].join(', ') || '(no version)'}`);

// ---------------------------------------------------------------------------
fs.rmSync(tmp, { recursive: true, force: true });
if (failures) {
  console.error(`\n${failures} check(s) failed`);
  process.exit(1);
}
console.log('\nall checks passed');
```

## test/grid.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gridLayout, resolveComponentPos, validateGridPlacement } from '../renderers/architecture/grid.mjs';

test('gridLayout returns null for free placement', () => {
  assert.equal(gridLayout({}), null);
  assert.equal(gridLayout({ layout: undefined }), null);
});

test('resolveComponentPos prefers explicit pos over row/col', () => {
  const grid = gridLayout({ layout: { mode: 'grid' } });
  assert.deepEqual(resolveComponentPos({ pos: [9, 8], row: 0, col: 0 }, grid), [9, 8]);
});

test('resolveComponentPos maps row/col to pixel origin', () => {
  const grid = gridLayout({
    layout: { mode: 'grid', origin: [40, 80], gapX: 30, gapY: 40, cellW: 130, cellH: 64 },
  });
  assert.deepEqual(resolveComponentPos({ row: 0, col: 0 }, grid), [40, 80]);
  assert.deepEqual(resolveComponentPos({ row: 1, col: 2 }, grid), [40 + 2 * 160, 80 + 104]);
});

test('validateGridPlacement rejects duplicate cells and missing row/col', () => {
  const grid = gridLayout({ layout: { mode: 'grid', cols: 4 } });
  const problems = [];
  validateGridPlacement({
    components: [
      { id: 'a', row: 0, col: 0 },
      { id: 'b', row: 0, col: 0 },
      { id: 'c' },
    ],
  }, grid, problems);
  assert.ok(problems.some((p) => p.includes('share grid cell')));
  assert.ok(problems.some((p) => p.includes('"c" needs pos')));
});

test('validateGridPlacement ignores explicit pos overrides without row/col', () => {
  const grid = gridLayout({ layout: { mode: 'grid', cols: 4 } });
  const problems = [];
  validateGridPlacement({
    components: [
      { id: 'a', pos: [40, 80] },
      { id: 'b', pos: [200, 80] },
    ],
  }, grid, problems);
  assert.deepEqual(problems, []);
});
```

## test/guide-page.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import vm from 'node:vm';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');

test('guide page: checked-in HTML is reproducible from the shared recipe source', () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-guide-page-'));
  const generated = path.join(tmp, 'guide.html');
  try {
    execFileSync(process.execPath, [path.join(repoRoot, 'scripts/build-guide.mjs'), generated]);
    assert.equal(
      fs.readFileSync(generated, 'utf8'),
      fs.readFileSync(path.join(repoRoot, 'docs/guide.html'), 'utf8'),
    );
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('guide page: ships bilingual recipes and syntactically valid interaction code', () => {
  const html = fs.readFileSync(path.join(repoRoot, 'docs/guide.html'), 'utf8');
  const packageVersion = JSON.parse(
    fs.readFileSync(path.join(skillRoot, 'package.json'), 'utf8'),
  ).version;
  const releaseIdentity = packageVersion.includes('-') ? 'development' : 'stable';
  const staticVersionLabel = html.match(
    /<span data-i18n="versionLabel">([^<]+)<\/span>/,
  );
  assert.doesNotMatch(html, /\[\[[A-Z0-9_]+\]\]/);
  assert.equal(
    staticVersionLabel?.[1],
    `Scenario guide / ${releaseIdentity} / v${packageVersion}`,
  );
  assert.match(html, /Question-first diagramming/);
  assert.match(html, /先问题，后图表/);
  assert.match(html, /archify guide &quot;your scenario&quot;|archify guide "your scenario"/);

  const dataMatch = html.match(/<script id="guide-data" type="application\/json">([\s\S]*?)<\/script>/);
  assert.ok(dataMatch);
  const data = JSON.parse(dataMatch[1]);
  assert.equal(data.length, 11);
  assert.equal(data.filter((recipe) => recipe.type === 'workflow').length, 3);
  assert.ok(data.every((recipe) => recipe.en.prompt && recipe.zh.prompt && recipe.proof));
  assert.match(html, /gallery\.html#proof-/);
  assert.match(html, /Open verified example/);
  assert.match(html, /打开验证成品/);

  const scriptMatch = html.match(/<script>\n([\s\S]*?)\n  <\/script>\n<\/body>/);
  assert.ok(scriptMatch);
  assert.doesNotThrow(() => new vm.Script(scriptMatch[1]));
});
```

## test/guide.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
  SCENARIO_RECIPES,
  detectGuideLanguage,
  listScenarioRecipes,
  publicGuideData,
  recommendScenario,
} from '../recipes/scenarios.mjs';

test('guide: exposes 11 unique recipes across every diagram type', () => {
  assert.equal(SCENARIO_RECIPES.length, 11);
  assert.equal(new Set(SCENARIO_RECIPES.map((recipe) => recipe.id)).size, 11);
  assert.deepEqual(
    Object.fromEntries(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle'].map((type) => [
      type,
      SCENARIO_RECIPES.filter((recipe) => recipe.type === type).length,
    ])),
    { architecture: 2, workflow: 3, sequence: 2, dataflow: 2, lifecycle: 2 },
  );
});

test('guide: every recipe has complete English and Chinese decision copy', () => {
  for (const recipe of SCENARIO_RECIPES) {
    assert.match(recipe.id, /^[a-z0-9]+(?:-[a-z0-9]+)*$/);
    assert.ok(recipe.signals.length >= 8, recipe.id);
    assert.ok(['classic', 'signal-flow', 'blueprint', 'editorial'].includes(recipe.presentation.preset), recipe.id);
    for (const lang of ['en', 'zh']) {
      const copy = recipe[lang];
      assert.ok(copy.title.length >= 4, `${recipe.id}.${lang}.title`);
      for (const field of ['question', 'summary', 'useWhen', 'avoidWhen', 'prompt']) {
        assert.ok(copy[field].length > 10, `${recipe.id}.${lang}.${field}`);
      }
      assert.equal(copy.include.length, 4, `${recipe.id}.${lang}.include`);
    }
  }
});

test('guide: language detection and localization are deterministic', () => {
  assert.equal(detectGuideLanguage('show an API request'), 'en');
  assert.equal(detectGuideLanguage('展示 API 请求'), 'zh');
  assert.equal(listScenarioRecipes('zh')[0].title, '系统总览');
  assert.equal(listScenarioRecipes('en')[0].title, 'System overview');
});

test('guide: representative scenarios map to specialized recipes', () => {
  const cases = [
    ['Show an API request with Redis cache miss', 'api-request'],
    ['Show CI/CD build deploy rollback', 'delivery-workflow'],
    ['展示 Kafka topic 消费者组和死信队列', 'event-stream'],
    ['梳理 ETL 数仓 PII 数据血缘', 'data-lineage'],
    ['deployment lifecycle approval rollback state', 'deployment-lifecycle'],
    ['agent tool call approval gate MCP', 'agent-tool-call'],
  ];

  for (const [query, expected] of cases) {
    assert.equal(recommendScenario(query).recommendation.id, expected, query);
  }
});

test('guide: exact ids win and unknown questions fall back honestly', () => {
  const exact = recommendScenario('incident-runbook');
  assert.equal(exact.recommendation.id, 'incident-runbook');
  assert.equal(exact.confidence, 'high');

  const unknown = recommendScenario('make it delightful');
  assert.equal(unknown.recommendation.id, 'system-overview');
  assert.equal(unknown.confidence, 'low');
  assert.deepEqual(unknown.matchedSignals, []);
});

test('guide: public data includes both languages and weighted signals', () => {
  const data = publicGuideData();
  assert.equal(data.length, 11);
  for (const recipe of data) {
    assert.ok(recipe.en.title);
    assert.ok(recipe.zh.title);
    assert.ok(recipe.proof, `${recipe.id}: verified proof is required`);
    assert.ok(recipe.signals.every(([signal, weight]) => typeof signal === 'string' && weight > 0));
  }
});
```

## test/guided-views-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { findChrome } from '../bin/visual-check.mjs';
import { desktopBrowser, desktopPointerCheck } from './helpers/desktop-browser.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;

test('Guided Views preserves chapters, Story playback and handoff contracts', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser Guided Views checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-guided-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_GUIDED_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const records = [];
  t.after(() => {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n');
  });
  const cases = {
    architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    files[mode] = path.join(scratch, mode + '.html');
    execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      path.join(skillRoot, 'examples', example), files[mode]]);
  }
  const trace = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', cases.architecture), 'utf8'));
  trace.meta.animation = 'trace';
  const traceInput = path.join(scratch, 'trace.json');
  fs.writeFileSync(traceInput, JSON.stringify(trace)); files.trace = path.join(scratch, 'trace.html');
  execFileSync(process.execPath, [path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'), traceInput, files.trace]);
  // HTML fixtures isolate invalid payloads and graph shapes outside renderer validation.
  function variant(name, views, setup = '') {
    const original = fs.readFileSync(files.trace, 'utf8');
    assert.match(original, /<script id="archify-guided-views-data"[^>]*>[\s\S]*?<\/script>/, 'Guided Views data fixture anchor');
    assert.ok(original.includes('    var Archify = {};'), 'Guided Views setup fixture anchor');
    const html = original.replace(
      /(<script id="archify-guided-views-data"[^>]*>)[\s\S]*?(<\/script>)/,
      (_, start, end) => start + (typeof views === 'string' ? views : JSON.stringify(views)) + end,
    ).replace('    var Archify = {};', setup + '\n    var Archify = {};');
    files[name] = path.join(scratch, name + '.html'); fs.writeFileSync(files[name], html);
  }
  const chapter = (id, focus) => ({ id, label: id, focus, note: 'Chapter ' + id });
  const scrollNodes = ['users','cdn','lb','api','db','cache','worker'];
  variant('scroll', Array.from({length: 9}, (_, i) => chapter('chapter-' + i, scrollNodes)));
  variant('empty', []); variant('invalid', '{');
  variant('filtered', [chapter('filtered', ['users', 'unknown', 'users', 'cdn']), chapter('empty', ['unknown']), chapter('solo', ['db'])]);
  variant('short', [chapter('one', ['users', 'cdn']), chapter('two', ['cdn', 'lb'])]);
  variant('disjoint', [chapter('one', ['users','cdn']), chapter('two', ['api','db'])]);
  variant('relations', [chapter('relations', ['users','cdn','lb','api','db']), chapter('other', ['api','cache'])], `
    document.querySelector('.diagram-container > svg').innerHTML =
      '<g data-edge-from="users" data-edge-to="cdn" data-edge-key="a" data-edge-label="forward"></g>' +
      '<g data-edge-from="users" data-edge-to="cdn" data-edge-key="a" data-edge-label="forward" transform="translate(3 4)"><path d="M 90 100 L 230 100"/></g>' +
      '<path data-edge-from="lb" data-edge-to="cdn" data-edge-key="b" data-edge-label="reverse" d="M 370 100 L 230 100"/>' +
      '<line data-edge-from="lb" data-edge-to="api" data-edge-key="c" x1="370" y1="100" x2="510" y2="100"/>' +
      '<polyline data-edge-from="api" data-edge-to="lb" data-edge-key="d" points="510,110 370,110"/>' +
      ['users','cdn','lb','api','db','cache'].map((id,i)=>'<g tabindex="0" data-node-id="'+id+'" data-node-label="'+id+'" data-node-kind="backend"><rect x="'+(50+i*140)+'" y="80" width="80" height="40"/><text x="'+(50+i*140)+'" y="100">'+id+'</text></g>').join('');
  `);
  const browser = desktopBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  const checkPointer = await desktopPointerCheck(browser, session);
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  await send('Emulation.setFocusEmulationEnabled', { enabled: true });
  async function run(expression) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.storyErrors=[];window.storyEnds=[];addEventListener('animationend',e=>{if(e.target.matches('.story-trail-flow'))storyEnds.push({name:e.animationName,trusted:e.isTrusted});},true);addEventListener('error',e=>storyErrors.push(e.message));
    addEventListener('unhandledrejection',e=>storyErrors.push(String(e.reason)));
    try {localStorage.removeItem('archify-motion');} catch (_) {}
    window.storyWait=predicate=>new Promise((resolve,reject)=>{
      const start=performance.now();function sample(){if(predicate())return resolve();
      if(performance.now()-start>12000)return reject(new Error('Story observation timed out'));requestAnimationFrame(sample);}requestAnimationFrame(sample);
    });
  ` });
  async function load(mode = 'architecture', { theme = 'dark', reduced = true, suffix = '' } = {}) {
    await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 0, y: 0 });
    await send('Emulation.setDeviceMetricsOverride', { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
    await send('Emulation.setEmulatedMedia', { media: '', features: [{ name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' }] });
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    await send('Page.navigate', { url: pathToFileURL(files[mode]).href + `?theme=${theme}` + suffix });
    await loaded;
    await checkPointer();
    await run('document.fonts.ready'); await run('Archify.viewerChromeLayout.whenStable()');
  }
  async function point(selector) {
    return run(`(()=>{const r=document.querySelector(${JSON.stringify(selector)}).getBoundingClientRect();return {x:r.x+r.width/2,y:r.y+r.height/2};})()`);
  }
  async function move(selector) { await send('Input.dispatchMouseEvent', { type: 'mouseMoved', ...(selector ? await point(selector) : { x: 0, y: 0 }) }); }
  async function click(selector) {
    const p = await point(selector);
    await send('Input.dispatchMouseEvent', { type: 'mousePressed', ...p, button: 'left', clickCount: 1 });
    await send('Input.dispatchMouseEvent', { type: 'mouseReleased', ...p, button: 'left', clickCount: 1 });
  }
  async function key(key, code, windowsVirtualKeyCode) {
    await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode, text: key === 'Enter' ? '\r' : key === ' ' ? ' ' : undefined });
    await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode });
  }
  const chapterButton = id => `[data-guided-view-id="${id}"]`;
  const stop = id => `#guided-view-trail [data-story-node="${id}"]`;
  async function activate(id) {
    assert.equal(await run(`Archify.guidedViews.activate(${JSON.stringify(id)})`), true);
    await settled();
  }
  async function settled() {
    await run(`storyWait(()=>!Archify.guidedViews.handoff?.()&&!document.querySelector('.diagram-container').hasAttribute('data-camera-transaction'))`);
  }
  async function hash(value) {
    await run(`new Promise(resolve=>{addEventListener('hashchange',()=>requestAnimationFrame(resolve),{once:true});location.hash=${JSON.stringify(value)};})`);
    await settled();
  }
  async function snapshot(scenario) {
    const state = await run(`(()=>{
      const g=Archify.guidedViews,svg=document.querySelector('.diagram-container > svg'),panel=document.getElementById('guided-views');
      const attrs=el=>Object.fromEntries([...el.attributes].filter(a=>/^(data-(story|chapter|active-view|playing|autoplay|share)|aria-(current|pressed|live))/.test(a.name)).map(a=>[a.name,a.value]));
      return {count:g.count,active:g.active(),beat:g.beat?.()||null,preview:g.preview?.()||null,playing:g.isPlaying?.()||false,focus:g.focus?.()||[],hidden:panel.hidden,
        panel:attrs(panel),svg:attrs(svg),html:attrs(document.documentElement),hash:location.hash,
        chapters:[...document.querySelectorAll('[data-guided-view-id]')].map(n=>({id:n.dataset.guidedViewId,tab:n.tabIndex,preview:n.dataset.previewActive||null,attrs:attrs(n)})),
        stops:[...document.querySelectorAll('#guided-view-trail [data-story-node]')].map(n=>({id:n.dataset.storyNode,relation:n.dataset.storyRelation,disabled:n.disabled,current:n.getAttribute('aria-current')})),
        nodes:[...svg.querySelectorAll('[data-node-id]')].filter(n=>n.hasAttribute('data-story-step')||n.hasAttribute('data-chapter-preview-role')).map(n=>({id:n.dataset.nodeId,attrs:attrs(n)})),
        edges:[...svg.querySelectorAll('[data-edge-from][data-story-beat-step]')].map(n=>({key:n.dataset.edgeKey,from:n.dataset.edgeFrom,to:n.dataset.edgeTo,attrs:attrs(n)})),
        overlays:svg.querySelectorAll('[data-story-overlay]').length,carriers:svg.querySelectorAll('[data-story-carrier-overlay]').length,
        note:document.getElementById('guided-view-note').textContent,caption:document.getElementById('guided-story-caption').textContent.trim(),
        cue:{hidden:document.getElementById('share-chapter-cue').hidden,state:document.getElementById('share-chapter-cue').dataset.state||null},
        errors:storyErrors,external:performance.getEntriesByType('resource').map(e=>e.name).filter(n=>/^https?:/.test(n))};
    })()`);
    assert.deepEqual(state.errors, [], scenario); assert.deepEqual(state.external, [], scenario);
    records.push({ scenario, ...state }); return state;
  }
  // Controlled clocks expose cancelled callbacks; real timing is tested separately.
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.storyClock=body=>{
      const schedule=setTimeout,cancel=clearTimeout,clock=Date.now;let now=1000,serial=0;const jobs=[];
      window.setTimeout=(fn,delay)=>{const job={id:++serial,fn,delay,cancelled:false};jobs.push(job);return job.id;};
      window.clearTimeout=id=>{const job=jobs.find(j=>j.id===id);if(job)job.cancelled=true;};Date.now=()=>now;
      try{return body({jobs,advance:ms=>now+=ms,last:delay=>jobs.filter(j=>j.delay===delay).at(-1),fire:job=>job.fn()});}
      finally{window.setTimeout=schedule;window.clearTimeout=cancel;Date.now=clock;}
    };
  ` });

  await t.test('five modes, empty payloads and filtered chapter interfaces initialize faithfully', async () => {
    const members=['count','activate','showAll','play','playCurrent','pause','beatLink','copyBeatLink','cancelHandoff','settleHandoff','clearPreview','isPlaying','handoff','active','preview','delta','beat','focus'];
    for (const mode of Object.keys(cases)) {
      await load(mode); const s=await snapshot(mode+'-initial'); assert.equal(s.count,3); assert.equal(s.active,null); assert.equal(s.hidden,false);
      assert.deepEqual(await run('Object.keys(Archify.guidedViews)'),members);
    }
    for (const mode of ['empty','invalid']) {
      await load(mode); assert.deepEqual(await run('Object.keys(Archify.guidedViews)'),['count','active']);
      const s=await snapshot(mode); assert.equal(s.hidden,true); assert.equal(s.count,0);
    }
    await load('filtered'); await activate('filtered'); assert.deepEqual((await snapshot('filtered')).focus,['users','cdn']);
    await activate('empty'); assert.deepEqual((await snapshot('empty-focus')).stops,[]);
    await activate('solo'); assert.equal((await snapshot('solo')).stops.length,1);
    assert.equal(await run(`Archify.guidedViews.activate('unknown')`),false);
  });

  await t.test('native chapter and beat input preserves preview, focus and Escape ordering', async () => {
    await load(); await click(chapterButton('request-path')); await settled();
    assert.equal((await snapshot('chapter-click')).active,'request-path');
    await run(`document.querySelector(${JSON.stringify(chapterButton('request-path'))}).focus()`);
    await key('ArrowRight','ArrowRight',39); assert.equal(await run('Archify.guidedViews.preview()'),'identity-and-cache');
    await key('End','End',35); await key('ArrowRight','ArrowRight',39);
    assert.equal(await run('document.activeElement.dataset.guidedViewId'),'async-work');
    await key('Home','Home',36); await key('ArrowLeft','ArrowLeft',37);
    assert.equal(await run('document.activeElement.dataset.guidedViewId'),'request-path');
    await key('ArrowRight','ArrowRight',39); await key('Enter','Enter',13); await settled();
    assert.equal((await snapshot('chapter-key')).active,'identity-and-cache');
    await click(stop('cache')); await settled(); assert.equal((await snapshot('beat-click')).beat.nodeId,'cache');
    await run(`document.querySelector(${JSON.stringify(stop('api'))}).focus()`); await key(' ','Space',32); await settled();
    assert.equal((await snapshot('beat-key')).beat.nodeId,'api');
    await run(`document.querySelector(${JSON.stringify(chapterButton('async-work'))}).focus()`);
    assert.equal(await run('Archify.guidedViews.preview()'),'async-work');
    await key('Escape','Escape',27); assert.equal(await run('Archify.guidedViews.preview()'),null); assert.equal(await run('Archify.guidedViews.active()'),'identity-and-cache');
    await key('Escape','Escape',27); assert.equal((await snapshot('escape-overview')).active,null);
    await run('document.activeElement.blur()'); await key(']','BracketRight',221); await settled();
    assert.equal(await run('Archify.guidedViews.active()'),'request-path');
    await key('[','BracketLeft',219); assert.equal(await run('Archify.guidedViews.active()'),null);
    await activate('request-path'); await click('.diagram-container > svg [data-node-id="api"]'); await settled();
    const takeover=await snapshot('node-takeover'); assert.equal(takeover.active,null); assert.equal(await run('Archify.focus.active()'),'api');
    await run(`Archify.guide.open()`); await key(']','BracketRight',221); assert.equal(await run('Archify.guidedViews.active()'),null);
    await run(`Archify.guide.close();Archify.finder.open();document.getElementById('node-finder-input').focus()`);
    await key(']','BracketRight',221); assert.equal(await run('Archify.guidedViews.active()'),null);
  });

  await t.test('chapter preview retains independent pointer and focus intent with owner blocking', async () => {
    await load(); await activate('request-path');
    await run(`document.querySelector(${JSON.stringify(chapterButton('identity-and-cache'))}).focus()`);
    await move(chapterButton('async-work')); await run(`storyWait(()=>Archify.guidedViews.preview()==='async-work')`);
    await snapshot('pointer-wins'); await move(null); await run(`storyWait(()=>Archify.guidedViews.preview()==='identity-and-cache')`);
    await snapshot('focus-fallback'); await run('document.activeElement.blur()'); assert.equal(await run('Archify.guidedViews.preview()'),null);
    const touch=await run(`(()=>{const b=document.querySelector(${JSON.stringify(chapterButton('async-work'))});b.dispatchEvent(new PointerEvent('pointerover',{bubbles:true,pointerType:'touch'}));return Archify.guidedViews.preview();})()`);
    assert.equal(touch,null);
    for (const [name,action] of [['route',`Archify.routeProbe.begin({source:'users'})`],['lens',`Archify.semanticLens.select('backend')`],['focus',`Archify.guidedViews.showAll();Archify.focus.set('api')`]]) {
      await load(); await activate('request-path'); await run(action);
      await run(`document.querySelector(${JSON.stringify(chapterButton('async-work'))}).focus()`);
      assert.equal((await snapshot('preview-blocked-'+name)).preview,null);
    }
    await load('trace',{reduced:false}); await activate('request-path'); await run('Archify.guidedViews.play()');
    await run(`document.querySelector(${JSON.stringify(chapterButton('async-work'))}).focus()`);
    assert.equal(await run('Archify.guidedViews.isPlaying()'),false); assert.equal(await run('Archify.guidedViews.preview()'),'async-work');
    await snapshot('preview-pauses-play');
  });

  await t.test('Story geometry distinguishes direction, grouping and duplicate fragments without BFS', async () => {
    await load('relations'); await activate('relations');
    let s=await snapshot('relations'); assert.deepEqual(s.stops.map(n=>n.relation),['start','forward','reverse','multiple','group']);
    assert.deepEqual(s.edges.map(e=>e.key),['a','b','c','d']);
    const geometry=await run(`(()=>{const svg=document.querySelector('.diagram-container > svg'),g=Archify.guidedViews;
      const f=g.focus();f.pop();const d=g.delta('other');d.enter.push('bad');
      return {focus:g.focus(),delta:g.delta('other'),shapes:[...svg.querySelectorAll('.story-trail-flow')].map(n=>({tag:n.tagName,d:n.getAttribute('d'),points:n.getAttribute('points'),transform:n.parentElement.getAttribute('transform'),key:n.getAttribute('data-edge-key')}))};})()`);
    assert.deepEqual(geometry.focus,['users','cdn','lb','api','db']); assert.deepEqual(geometry.delta,{stay:['api'],enter:['cache'],leave:['users','cdn','lb','db']});
    assert.equal(geometry.shapes.length,4); assert.equal(geometry.shapes[0].transform,'translate(3 4)'); assert.equal(geometry.shapes[0].d,'M 90 100 L 230 100'); assert.ok(geometry.shapes.every(n=>n.key===null));
    records.push({scenario:'geometry-copies',...geometry});
    for (const id of ['users','cdn','lb','api','db']) {
      await click(stop(id)); await settled(); s=await snapshot('relation-beat-'+id); assert.equal(s.beat.nodeId,id);
      assert.equal(await run(`(()=>{const b=Archify.guidedViews.beat();b.edgeKeys.push('bad');return Archify.guidedViews.beat().edgeKeys.includes('bad');})()`),false);
    }
    await run(`document.querySelectorAll('.diagram-container > svg [data-edge-from]').forEach(e=>e.remove());Archify.guidedViews.activate('relations')`); await settled();
    assert.equal((await snapshot('no-geometry')).overlays,0);
  });

  await t.test('handoff holds an authored anchor and preserves cancellation versus settling', async () => {
    await load('trace',{reduced:false}); await activate('request-path');
    const holding=await run(`(()=>{Archify.guidedViews.activate('identity-and-cache');return {handoff:Archify.guidedViews.handoff(),disabled:[...document.querySelectorAll('#guided-view-trail button')].every(n=>n.disabled),anchor:document.querySelector('.diagram-container > svg').dataset.chapterAnchor};})()`);
    assert.equal(holding.handoff.mode,'holding'); assert.equal(holding.anchor,'api'); assert.equal(holding.disabled,true);
    await settled(); assert.equal((await snapshot('handoff-complete')).active,'identity-and-cache');
    const states=await run(`storyClock(c=>{
      const reveal=Archify.view.reveal;const calls=[];const receipts=[];
      Archify.view.reveal=(ids,opts)=>{let resolve;const finished=new Promise(r=>resolve=r);const receipt={finished,cancel:(reason,commit)=>{calls.push({reason,commit});resolve({state:reason});}};receipts.push({ids:[...ids],opts,resolve});return receipt;};
      const g=Archify.guidedViews;
      try {
        g.activate('request-path');const hold=c.last(110);const first=g.handoff();g.cancelHandoff('manual-test');c.fire(hold);const cancelled=g.handoff();
        g.activate('identity-and-cache');c.fire(c.last(110));g.settleHandoff('settled-test');
        g.activate('request-path');c.fire(c.last(110));g.cancelHandoff('manual-camera');
        return {firstMode:first.mode,cancelled,calls,receipts:receipts.map(r=>({ids:r.ids,duration:r.opts.duration})),remaining:g.handoff()};
      } finally {g.cancelHandoff('fixture-cleanup');Archify.view.reveal=reveal;}
    })`);
    assert.equal(states.firstMode,'holding'); assert.equal(states.cancelled,null); assert.equal(states.remaining,null);
    assert.deepEqual(states.calls,[{reason:'settled-test',commit:true},{reason:'manual-camera',commit:false}]);
    assert.ok(states.receipts.every(r=>r.duration===420)); records.push({scenario:'handoff-clock-camera-fixture',...states});
    await load('disjoint',{reduced:false});await activate('one');
    const noAnchor=await run(`(()=>{Archify.guidedViews.activate('two');return document.querySelector('.diagram-container > svg').dataset.chapterHandoff;})()`);assert.equal(noAnchor,'no-anchor');await settled();await snapshot('no-anchor-handoff');
    await load('relations',{reduced:false}); await activate('relations');
    await run(`Archify.guidedViews.activate('other')`); await settled();
    await run(`Archify.guidedViews.activate('relations')`); await run(`storyWait(()=>Archify.guidedViews.handoff()?.mode==='settling')`);
    await run('Archify.view.zoomIn()'); assert.equal(await run('Archify.guidedViews.handoff()'),null); await snapshot('manual-camera-handoff');
    await load('trace',{reduced:false}); await activate('request-path');
    const replacement=await run(`storyClock(c=>{const g=Archify.guidedViews;g.activate('identity-and-cache');const old=c.last(110);g.activate('async-work');const before=g.handoff();c.fire(old);const same=g.handoff().id===before.id;g.showAll();c.fire(old);return {same,active:g.active(),handoff:g.handoff()};})`);
    assert.deepEqual(replacement,{same:true,active:null,handoff:null}); records.push({scenario:'replaced-hold',...replacement});
  });

  await t.test('real Story and chapter playback complete while precise clock fixtures preserve dwell', async () => {
    await load('short',{reduced:false}); assert.equal(await run('Archify.guidedViews.play()'),true);
    await run(`storyWait(()=>Archify.guidedViews.active()==='two'&&Archify.guidedViews.isPlaying())`);
    await run(`storyWait(()=>!Archify.guidedViews.isPlaying())`); let s=await snapshot('whole-story-complete');
    assert.equal(s.active,'two'); assert.equal(s.beat.nodeId,'lb'); assert.match(await run(`document.getElementById('guided-view-play-label').textContent`),/Replay/);
    assert.equal(await run('Archify.guidedViews.play()'),true); await run('Archify.guidedViews.pause()'); assert.equal(await run('Archify.guidedViews.active()'),'one');
    await load('short',{reduced:false}); assert.equal(await run('Archify.guidedViews.playCurrent()'),true);
    await run(`storyWait(()=>!Archify.guidedViews.isPlaying())`); s=await snapshot('single-chapter-complete'); assert.equal(s.active,'one'); assert.equal(s.panel['data-autoplay'],'complete');
    await load('short',{reduced:false}); await activate('one');
    const timing=await run(`storyClock(c=>{
      const g=Archify.guidedViews;g.play();const initial=c.last(1600);c.advance(400);g.pause();g.play();const resumed=c.last(1200);c.fire(resumed);const next=c.last(1600);const beat=g.beat().nodeId;g.pause();
      g.showAll();c.fire(initial);c.fire(next);return {initial:initial.delay,resumed:resumed.delay,next:next.delay,beat,after:g.active(),playing:g.isPlaying()};
    })`);
    assert.deepEqual(timing,{initial:1600,resumed:1200,next:1600,beat:'cdn',after:null,playing:false}); records.push({scenario:'dwell-clock',...timing});
    await load('trace',{reduced:false}); await activate('request-path');
    const stale=await run(`storyClock(c=>{const g=Archify.guidedViews;g.play();const old=c.last(1100);document.querySelector('[data-story-node="lb"]').click();const before=g.beat().nodeId;c.fire(old);const after=g.beat().nodeId;g.showAll();return {before,after,playing:g.isPlaying()};})`);
    assert.deepEqual(stale,{before:'lb',after:'lb',playing:false}); records.push({scenario:'manual-step-stale-timer',...stale});
  });

  await t.test('Motion lifecycle and real capability handoffs preserve Story cleanup', async () => {
    for (const [name,action] of [
      ['camera',`Archify.view.zoomIn()`],['guide',`Archify.guide.open()`],['still',`Archify.motionGovernor.pause()`],
      ['print-fixture',`dispatchEvent(new Event('beforeprint'))`],
      ['hidden-fixture',`Object.defineProperty(document,'hidden',{configurable:true,value:true});document.dispatchEvent(new Event('visibilitychange'))`],
    ]) {
      await load('trace',{reduced:false}); await activate('request-path'); await run('Archify.guidedViews.play()'); await run(action);
      let s=await snapshot(name+'-pause'); assert.equal(s.playing,false); assert.equal(s.active,'request-path'); assert.equal(s.carriers,0);
      if(name==='hidden-fixture') await run(`delete document.hidden;document.dispatchEvent(new Event('visibilitychange'))`);
      if(name==='still') await run('Archify.motionGovernor.resume()');
      assert.equal(await run('Archify.guidedViews.isPlaying()'),false);
    }
    for (const [name,action] of [['finder',`Archify.finder.open()`],['route',`Archify.routeProbe.begin({source:'users'})`],['lens',`Archify.semanticLens.select('backend')`]]) {
      await load('trace',{reduced:false}); await activate('request-path'); await run('Archify.guidedViews.play()'); await run(action);
      if(name==='finder'){
        const opened=await snapshot('finder-open-preserves-story');assert.equal(opened.active,'request-path');assert.equal(opened.playing,true);
        await run(`storyWait(()=>document.activeElement.id==='node-finder-input')`);
        await run(`document.getElementById('node-finder-input').value='PostgreSQL';document.getElementById('node-finder-input').dispatchEvent(new Event('input',{bubbles:true}))`);await key('Enter','Enter',13);
      }
      const s=await snapshot(name+'-takeover'); assert.equal(s.active,null); assert.equal(s.playing,false); assert.equal(s.overlays,0);
    }
    await load('trace',{reduced:false});await activate('request-path');await run('Archify.guidedViews.play()');
    await send('Emulation.setEmulatedMedia',{features:[{name:'prefers-reduced-motion',value:'reduce'}]});
    await run(`storyWait(()=>!Archify.guidedViews.isPlaying())`);await snapshot('live-system-reduced-motion');
    await load('architecture',{reduced:false}); assert.equal(await run('Archify.guidedViews.play()'),true); await run('Archify.guidedViews.pause()'); await snapshot('nontrace-playback');
    await load('trace'); assert.equal(await run('Archify.guidedViews.play()'),false);
    await load('trace',{reduced:false}); await activate('request-path'); await click(stop('cdn')); await settled();
    await run(`storyWait(()=>storyEnds.some(e=>e.trusted)&&!document.querySelector('[data-story-carrier-overlay]'))`);
    assert.equal(await run(`document.querySelectorAll('[data-story-pulse]').length`),0); await snapshot('animationend-cleared');
    const preempt=await run(`(()=>{document.querySelector('[data-story-node="lb"]').click();const before=document.querySelectorAll('[data-story-carrier-overlay]').length;const token=Archify.motionGovernor.claim('handoff',()=>{});const after=document.querySelectorAll('[data-story-carrier-overlay]').length;Archify.motionGovernor.release(token);return {before,after};})()`);
    assert.deepEqual(preempt,{before:1,after:0}); records.push({scenario:'pulse-owner-preempt',...preempt});
    // Explicit hidden-page fixture leaves play=1 pending until visibility resumes.
    variant('pending',[chapter('one',['users','cdn'])],`Object.defineProperty(document,'hidden',{configurable:true,value:true});`);
    await load('pending',{reduced:false,suffix:'&embed=1&play=1#view=one'});
    assert.equal(await run(`document.getElementById('guided-views').dataset.autoplay`),'pending');
    await run(`delete document.hidden;document.dispatchEvent(new Event('visibilitychange'))`);
    assert.equal(await run('Archify.guidedViews.isPlaying()'),true); await run('Archify.guidedViews.pause()'); await snapshot('pending-consumed-on-visible');
  });

  await t.test('hash and moment links retain restoration, query and clipboard feedback contracts', async () => {
    await load('trace',{suffix:'&keep=yes#view=request-path&beat=lb'}); await settled();
    await run(`storyWait(()=>Archify.guidedViews.beat()?.nodeId==='lb')`); assert.equal((await snapshot('initial-linked-beat')).beat.nodeId,'lb');
    const link=await run(`(()=>{const u=new URL(Archify.guidedViews.beatLink());return {hash:u.hash,query:u.search};})()`);
    assert.deepEqual(link,{hash:'#view=request-path&beat=lb',query:'?theme=dark&keep=yes'});
    await hash('#view=identity-and-cache&beat=cache'); assert.equal((await snapshot('hash-beat')).beat.nodeId,'cache');
    await hash('#view=identity-and-cache&beat=unknown'); assert.equal((await snapshot('unknown-beat')).beat,null);
    await hash('#view=unknown'); assert.equal((await snapshot('unknown-view')).active,null);
    await hash('#focus=api'); assert.equal((await snapshot('focus-hash')).active,null);
    await hash('#route=users~db'); assert.equal((await snapshot('route-hash')).active,null);
    await hash(''); assert.equal((await snapshot('empty-hash')).active,null);
    assert.equal(await run('Archify.guidedViews.copyBeatLink()'),false);
    await load('trace',{suffix:'&play=1&keep=yes#view=request-path&beat=cdn'}); await settled();
    await run(`storyWait(()=>Archify.guidedViews.beat()?.nodeId==='cdn')`);
    assert.equal(await run(`new URL(Archify.guidedViews.beatLink()).searchParams.has('play')`),false);
    for (const mode of ['success','reject','absent','failure','throw']) {
      const copied=await run(`(async()=>{
        const descriptor=Object.getOwnPropertyDescriptor(navigator,'clipboard'),exec=document.execCommand;let value='',calls=0;
        const expected=Archify.guidedViews.beatLink();
        Object.defineProperty(navigator,'clipboard',{configurable:true,value:${mode==='success'?"{writeText:v=>{value=v;return Promise.resolve();}}":mode==='reject'?"{writeText:()=>Promise.reject(new Error('fixture'))}":'undefined'}});
        document.execCommand=()=>{calls++;value=document.activeElement.value;if(${JSON.stringify(mode)}==='throw')throw new Error('fixture');return ${JSON.stringify(mode)}!=='failure';};
        try {const ok=await Archify.guidedViews.copyBeatLink();return {ok,calls,correct:value===expected,fields:document.querySelectorAll('textarea[readonly]').length,state:document.getElementById('guided-view-beat-link').dataset.copyState};}
        finally{document.execCommand=exec;if(descriptor)Object.defineProperty(navigator,'clipboard',descriptor);else delete navigator.clipboard;}
      })()`);
      assert.equal(copied.ok,!['failure','throw'].includes(mode)); assert.equal(copied.calls,mode==='success'?0:1); assert.equal(copied.correct,true); assert.equal(copied.fields,0); assert.equal(copied.state,copied.ok?'copied':'failed');
      await run(`storyWait(()=>!document.getElementById('guided-view-beat-link').hasAttribute('data-copy-state'))`); records.push({scenario:'copy-'+mode,...copied});
    }
    const late=await run(`(async()=>{const descriptor=Object.getOwnPropertyDescriptor(navigator,'clipboard');let resolve;Object.defineProperty(navigator,'clipboard',{configurable:true,value:{writeText:()=>new Promise(r=>resolve=r)}});
      try{const pending=Archify.guidedViews.copyBeatLink();Archify.guidedViews.showAll();resolve();await pending;const button=document.getElementById('guided-view-beat-link');return {active:Archify.guidedViews.active(),state:button.dataset.copyState,disabled:button.disabled};}
      finally{if(descriptor)Object.defineProperty(navigator,'clipboard',descriptor);else delete navigator.clipboard;}})()`);
    assert.deepEqual(late,{active:null,state:'copied',disabled:true}); records.push({scenario:'copy-completes-after-overview',...late});
    await run(`storyWait(()=>!document.getElementById('guided-view-beat-link').hasAttribute('data-copy-state'))`);
    await load('trace',{reduced:false}); await activate('request-path');
    await run(`location.hash='view=identity-and-cache&beat=cache';new Promise(resolve=>requestAnimationFrame(()=>{location.hash='view=async-work&beat=worker';requestAnimationFrame(resolve);}))`);
    await settled(); await run(`storyWait(()=>Archify.guidedViews.beat()?.nodeId==='worker')`); await snapshot('latest-hash-wins');
  });


  await t.test('active preview, handoff and carrier serialize without leaking transient state', async () => {
    const actions={
      'focus-preview':`document.querySelector('[data-guided-view-id="identity-and-cache"]').focus()`,
      'pointer-preview':`void 0`,
      handoff:`Archify.guidedViews.activate('identity-and-cache')`,
      carrier:`document.querySelector('[data-story-node="cdn"]').click()`,
    };
    for(const [name,action] of Object.entries(actions)){
      await load('trace',{reduced:false});await activate('request-path');
      if(name==='pointer-preview')await move(chapterButton('identity-and-cache'));
      const result=await run(`(async()=>{
        ${action};const svg=document.querySelector('.diagram-container > svg');
        const present=${JSON.stringify(name)}.endsWith('preview')?!!Archify.guidedViews.preview():${JSON.stringify(name)}==='handoff'?!!Archify.guidedViews.handoff():!!svg.querySelector('[data-story-carrier-overlay]');
        const geometry=root=>[...root.querySelectorAll('[data-edge-from]')].map(n=>({tag:n.tagName,key:n.getAttribute('data-edge-key'),d:n.getAttribute('d'),points:n.getAttribute('points'),transform:n.getAttribute('transform')}));
        const sourceGeometry=geometry(svg),before=svg.outerHTML,create=URL.createObjectURL;let blob,after;
        URL.createObjectURL=function(value){if(value.type.startsWith('image/svg+xml'))blob=value;return create.call(URL,value);};
        try{const pending=Archify.exportMenu.run('svg');after=svg.outerHTML;await pending;}finally{URL.createObjectURL=create;}
        const root=new DOMParser().parseFromString(await blob.text(),'image/svg+xml').documentElement;
        return {present,liveSame:before===after,geometrySame:JSON.stringify(sourceGeometry)===JSON.stringify(geometry(root)),viewBox:root.getAttribute('viewBox')===svg.getAttribute('viewBox'),clean:![...root.querySelectorAll('*'),root].some(n=>[...n.attributes].some(a=>/^data-(story|chapter)/.test(a.name)))};
      })()`);
      // Export focuses its trigger before serialization: focus-backed preview clears.
      // Pointer-backed preview remains and must be removed only from the clone.
      assert.deepEqual(result,{present:true,liveSame:name!=='focus-preview',geometrySame:true,viewBox:true,clean:true});records.push({scenario:'export-'+name,...result});
    }
  });

  await t.test('theme, narrow layout, embed moments and real SVG exports keep canonical geometry', async () => {
    for(const theme of ['dark','light']) {
      for(const position of ['overview','beat']) {
        await load('trace',{theme}); await activate('request-path');
        if(position==='beat'){await click(stop('lb'));await settled();}
        const style=await run(`({flow:getComputedStyle(document.querySelector('.story-trail-flow')).animationName,carriers:document.querySelectorAll('[data-story-carrier-overlay]').length})`);
        assert.deepEqual(style,{flow:'none',carriers:0}); await snapshot(theme+'-'+position);
        if(evidence){
          await run(`Promise.all(document.getAnimations().filter(a=>Number.isFinite(a.effect.getTiming().iterations)).map(a=>a.finished.catch(()=>{})))`);
          const shot=await send('Page.captureScreenshot',{format:'png'});fs.writeFileSync(path.join(evidence,theme+'-'+position+'.png'),Buffer.from(shot.data,'base64'));
        }
        const exported=await run(`(async()=>{const svg=document.querySelector('.diagram-container > svg'),before=svg.outerHTML,create=URL.createObjectURL;let blob,after;
          URL.createObjectURL=function(value){if(value.type.startsWith('image/svg+xml'))blob=value;return create.call(URL,value);};
          try{const pending=Archify.exportMenu.run('svg');after=svg.outerHTML;await pending;}finally{URL.createObjectURL=create;}
          const root=new DOMParser().parseFromString(await blob.text(),'image/svg+xml').documentElement;
          return {liveSame:before===after,viewBox:root.getAttribute('viewBox')===svg.getAttribute('viewBox'),clean:![...root.querySelectorAll('*'),root].some(n=>[...n.attributes].some(a=>/^data-(story|chapter)/.test(a.name)))};
        })()`); assert.deepEqual(exported,{liveSame:true,viewBox:true,clean:true});
      }
    }
    for(const width of [720,390,1440]) {
      await load(); await send('Emulation.setDeviceMetricsOverride',{width,height:900,deviceScaleFactor:1,mobile:false});await run('Archify.viewerChromeLayout.whenStable()');
      await run(`document.querySelector(${JSON.stringify(chapterButton('request-path'))}).focus()`);await key('End','End',35);await key('Enter','Enter',13);await settled();
      await run(`document.querySelector(${JSON.stringify(stop('worker'))}).focus()`);await key('Enter','Enter',13);await settled();
      assert.equal(await run('Archify.guidedViews.active()'), 'async-work');
      assert.equal(await run('Archify.guidedViews.beat().nodeId'), 'worker');
    }
    for(const width of [390,720,1440]) {
      await load('scroll');
      await send('Emulation.setDeviceMetricsOverride',{width,height:900,deviceScaleFactor:1,mobile:false});
      await run('Archify.viewerChromeLayout.whenStable()');
      // Keyboard activation must reveal and center off-screen items itself.
      // preventScroll keeps native focus scrolling from concealing a regression.
      for(const index of [8,4,0]) {
        const chapterId='chapter-'+index;
        await run(`document.querySelector(${JSON.stringify(chapterButton(chapterId))}).focus({preventScroll:true})`);
        await key('Enter','Enter',13);await settled();await run('Archify.viewerChromeLayout.whenStable()');
        assert.equal(await run('Archify.guidedViews.active()'),chapterId);
        for(const nodeId of [null,'worker','api','users']) {
          if(nodeId) {
            await run(`document.querySelector(${JSON.stringify(stop(nodeId))}).focus({preventScroll:true})`);
            await key('Enter','Enter',13);await settled();
            assert.equal(await run('Archify.guidedViews.beat().nodeId'),nodeId);
          }
          const scroll=await run(`(()=>{
            function measure(container,selector) {
              const c=document.getElementById(container),item=c.querySelector(selector),r=item.getBoundingClientRect(),box=c.getBoundingClientRect();
              const left=box.left+c.clientLeft,right=left+c.clientWidth;
              return {itemWidth:r.width,leftGap:r.left-left,rightGap:right-r.right,
                centerDelta:(r.left+r.right-left-right)/2,scroll:c.scrollLeft,maxScroll:c.scrollWidth-c.clientWidth};
            }
            return ${nodeId ? `{trail:measure('guided-view-trail','[data-story-node="${nodeId}"]')}` : `{chapter:measure('guided-view-chapters','[data-guided-view-id="${chapterId}"]')}`};
          })()`);
          for(const [name,position] of Object.entries(scroll)) {
            const context=JSON.stringify({width,chapterId,nodeId,name,...position});
            assert.ok(position.itemWidth>0 && position.leftGap>=-1 && position.rightGap>=-1,'selected item is fully visible: '+context);
            assert.ok(Math.abs(position.centerDelta)<=1 ||
              (position.scroll<=1 && position.centerDelta<0) ||
              (position.scroll>=position.maxScroll-1 && position.centerDelta>0),'centered or clamped at the corresponding edge: '+context);
            if(width===390) assert.ok(position.maxScroll>20,'fixture genuinely overflows: '+context);
          }
          records.push({scenario:'scroll-'+width+'-'+chapterId+'-'+nodeId,...scroll});
          if(width===390 && index===4 && nodeId==='api') {
            await run(`document.getElementById('guided-view-trail').scrollLeft=0;new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))`);
            assert.equal(await run(`document.getElementById('guided-view-trail').scrollLeft`),0,'manual scrolling alone must not recenter the active beat');
          }
        }
      }
    }
    for(const suffix of ['&embed=1#view=request-path&beat=lb','&embed=1&play=1#view=request-path&beat=lb']) {
      await load('trace',{suffix});await settled();await run(`storyWait(()=>Archify.guidedViews.beat()?.nodeId==='lb')`);
      const s=await snapshot(suffix.includes('play=1')?'embed-static-share':'embed-pinned');assert.equal(s.playing,false);assert.equal(s.cue.hidden,false);assert.equal(s.cue.state,suffix.includes('play=1')?'reduced-motion':'pinned');
    }
  });
});
```

## test/guided-views.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-guided-views-'));

const CASES = {
  architecture: { example: 'web-app.architecture.json', collection: 'components' },
  workflow: { example: 'agent-tool-call.workflow.json', collection: 'nodes' },
  sequence: { example: 'cache-miss-request.sequence.json', collection: 'participants' },
  dataflow: { example: 'product-analytics.dataflow.json', collection: 'nodes' },
  lifecycle: { example: 'agent-run.lifecycle.json', collection: 'states' },
};

function run(mode, doc, suffix) {
  const input = path.join(tmp, `${mode}-${suffix}.json`);
  const output = path.join(tmp, `${mode}-${suffix}.html`);
  fs.writeFileSync(input, JSON.stringify(doc));
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, output,
  ], { encoding: 'utf8' });
  return { result, output, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

function fixture(mode) {
  return JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', CASES[mode].example), 'utf8'));
}

function svg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

for (const [mode, config] of Object.entries(CASES)) {
  test(`${mode}: guided views preserve base SVG geometry`, () => {
    const withViews = fixture(mode);
    const ids = withViews[config.collection].slice(0, 2).map((item) => item.id);
    withViews.meta.views = [{
      id: 'reader-path',
      label: 'Reader path',
      focus: ids,
      note: 'A safe note with </script><script> text.',
    }];
    const withoutViews = structuredClone(withViews);
    delete withoutViews.meta.views;

    const guided = run(mode, withViews, 'guided');
    const plain = run(mode, withoutViews, 'plain');
    assert.equal(guided.result.status, 0, guided.result.stderr);
    assert.equal(plain.result.status, 0, plain.result.stderr);
    assert.equal(svg(guided.html), svg(plain.html));
    assert.match(guided.html, /id="guided-views" hidden/);
    assert.match(guided.html, /Archify\.guidedViews = \(function \(\)/);
    assert.match(guided.html, /#view=/);
    assert.match(guided.html, /addEventListener\('hashchange', syncViewFromHash\)/);
    assert.match(guided.html, /id="guided-view-play"/);
    assert.match(guided.html, /VIEW_INTERVAL_MS = 3200/);
    assert.match(guided.html, /visibilitychange/);
    assert.match(guided.html, /play: startPlayback/);
    assert.match(guided.html, /playCurrent: startCurrentViewPlayback/);
    assert.match(guided.html, /URLSearchParams\(location\.search\)\.get\('play'\) === '1'/);
    assert.match(guided.html, /data-autoplay/);
    assert.match(guided.html, /prefers-reduced-motion: reduce/);
    assert.match(guided.html, /pausePlayback\(\{ complete: true \}\)/);
    assert.match(guided.html, /html\[data-embed="true"\] svg\[data-animation="trace"\] \[data-animate\],[\s\S]*?html\[data-share-playback="true"\][\s\S]*?animation: none !important;[\s\S]*?stroke-dashoffset: 0/);
    assert.match(guided.html, /document\.documentElement\.setAttribute\('data-share-playback', 'true'\)/);
    assert.match(guided.html, /document\.documentElement\.removeAttribute\('data-share-playback'\)/);
    const oneShot = guided.html.match(/function startCurrentViewPlayback\(\) \{([\s\S]*?)\n      function maybeStartSharePlayback/);
    assert.ok(oneShot, 'one-shot share playback implementation missing');
    assert.match(oneShot[1], /storyPlaybackScope = 'chapter'/);
    assert.match(oneShot[1], /scheduleStoryPlayback\(\)/);
    assert.doesNotMatch(oneShot[1], /scheduleNextView/);
    assert.match(guided.html, /id="share-chapter-cue" hidden role="status" aria-live="polite"/);
    assert.match(guided.html, /data-share-playback="true"\] \.share-chapter-cue:not\(\[hidden\]\)/);
    assert.match(guided.html, /data-share-playback="true"\] \.diagram-container \{\s*padding-top: 4\.25rem/);
    assert.match(guided.html, /function renderShareCue\(\)/);
    assert.match(guided.html, /function shareCueBeatCopy\(state, view, stops\)/);
    assert.match(guided.html, /viewerText\('viewer\.guided\.share\.step'/);
    assert.match(guided.html, /shareCue\.setAttribute\('aria-live', state === 'playing' \? 'off' : 'polite'\)/);
    assert.match(guided.html, /function scheduleStoryPlayback\(\)/);
    assert.match(guided.html, /storyBeatTimer = setTimeout/);
    assert.match(guided.html, /generation !== storyPlaybackGeneration/);
    assert.match(guided.html, /settleStoryBeats\(\);/);
    assert.match(guided.html, /kind === 'multiple' \? '\\u21c4' : '\\u00b7'/);
    assert.match(guided.html, /currentStoryProgress\(\)/);
    assert.match(guided.html, /setShareCueProgress\(progress\)/);
    assert.match(guided.html, /startShareCueProgress\(progress, remainingChapter\)/);
    assert.match(guided.html, /data-wide-diagram/);
    assert.match(guided.html, /min-width: 720px/);
    assert.match(guided.html, /reveal: reveal/);
    assert.match(guided.html, /container\.addEventListener\('scroll', onScroll, \{ passive: true \}\)/);
    assert.match(guided.html, /--archify-scroll-x/);
    assert.match(guided.html, /focus: function \(\) \{ return activeIndex < 0 \? \[\] : views\[activeIndex\]\.focus\.slice\(\); \}/);
    assert.doesNotMatch(guided.html, /<p class="footer">/);
    assert.doesNotMatch(guided.html, /<kbd>P<\/kbd> play story/);
    assert.doesNotMatch(plain.html, /<kbd>P<\/kbd> play story/);
    assert.match(guided.html, /\\u003c\/script\\u003e\\u003cscript\\u003e/);
    assert.doesNotMatch(guided.html, /A safe note with <\/script><script>/);
  });
}

test('guided views reject duplicate view ids', () => {
  const doc = fixture('workflow');
  doc.meta.views = [
    { id: 'same', label: 'First', focus: ['user'] },
    { id: 'same', label: 'Second', focus: ['chat'] },
  ];
  const { result } = run('workflow', doc, 'duplicate-view-id');
  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /duplicates view id "same"/);
});

test('guided views reject dangling semantic ids', () => {
  const doc = fixture('sequence');
  doc.meta.views = [{ id: 'broken', label: 'Broken', focus: ['ghost'] }];
  const { result } = run('sequence', doc, 'dangling-id');
  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /references unknown semantic id "ghost"/);
});

test('guided views schema enforces collection and focus bounds', () => {
  const tooMany = fixture('architecture');
  tooMany.meta.views = Array.from({ length: 6 }, (_, index) => ({
    id: `view-${index}`,
    label: `View ${index}`,
    focus: [tooMany.components[0].id],
  }));
  const overLimit = run('architecture', tooMany, 'too-many');
  assert.notEqual(overLimit.result.status, 0);
  assert.match(overLimit.result.stderr, /must NOT have more than 5 items/);

  const duplicateFocus = fixture('dataflow');
  duplicateFocus.meta.views = [{ id: 'duplicate', label: 'Duplicate', focus: ['web', 'web'] }];
  const duplicate = run('dataflow', duplicateFocus, 'duplicate-focus');
  assert.notEqual(duplicate.result.status, 0);
  assert.match(duplicate.result.stderr, /duplicates semantic id "web"/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/helpers

```

```

## test/helpers/desktop-browser.mjs

```js
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { ChromeVisualBrowser } from '../../bin/visual-check.mjs';

// Test-only Blink settings: headless hosts may have no physical mouse.
// Disabling touch emulation would restore those host defaults and undo this.
const pointerSettings = '--blink-settings=availableHoverTypes=2,primaryHoverType=2,availablePointerTypes=4,primaryPointerType=4';

export function desktopBrowser(chrome) {
  return new ChromeVisualBrowser(chrome, {
    spawnImpl: (command, args, options) => spawn(command, [pointerSettings, ...args], options),
  });
}

export async function desktopPointerCheck(browser, session) {
  const version = await browser.cdp.send('Browser.getVersion');
  await browser.cdp.send('Page.addScriptToEvaluateOnNewDocument', { source: `
    (() => {
      // Capture the native query before controlled coarse-pointer fixtures run.
      const query = window.matchMedia.bind(window);
      const sample = () => ({
        hover: query('(hover: hover)').matches,
        fine: query('(pointer: fine)').matches,
        combined: query('(hover: hover) and (pointer: fine)').matches,
        anyHover: query('(any-hover: hover)').matches,
        anyFine: query('(any-pointer: fine)').matches,
        maxTouchPoints: navigator.maxTouchPoints,
      });
      window.__archifyTestPointer = { initial: sample(), sample };
    })();
  ` }, session);
  return async function checkDesktopPointer() {
    const result = await browser.cdp.send('Runtime.evaluate', {
      expression: '({initial:__archifyTestPointer.initial,current:__archifyTestPointer.sample()})',
      returnByValue: true,
    }, session);
    assert.equal(result.exceptionDetails, undefined, 'Desktop pointer diagnostics did not initialize');
    const samples = result.result.value;
    for (const phase of ['initial', 'current']) {
      for (const capability of ['hover', 'fine', 'combined', 'anyHover', 'anyFine']) {
        assert.equal(samples[phase][capability], true,
          `Desktop pointer capability ${phase}.${capability}: ${JSON.stringify({
            browser: version.product, platform: process.platform, pointerSettings, samples,
          })}`);
      }
    }
  };
}
```

## test/helpers/offline-fonts.mjs

```js
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import { parse } from 'parse5';

const FONT_LICENSE = fs.readFileSync(new URL('../../assets/JetBrainsMono-OFL.txt', import.meta.url), 'utf8').trim();

// Pinned Google Fonts v24 bytes and coverage, independent of CSS formatting.
const EXPECTED_FACES = [
  ['9343de2ca5d9549f792e7962375af8efb0f320c7643bfd36c884b5a30e5c396f', 'U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F'],
  ['4995a9a43ac659ec32fcd8b463755cd6a07b31a6e6b3894a6a153b661cf490e2', 'U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116'],
  ['49c3da6c9a2b279b0f1f860f5cfb1f5dc38d88a5c7be9c9b1837bbc4e3db6111', 'U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF'],
  ['d44eb1936043a56038eb02dd70b243f379bef65783f94ec12f277550720411f1', 'U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB'],
  ['9c38cb2d0d2d93c1ee6e21fa78db76f13ea7e15e15cc64214c7ca89b6aaa35c4', 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF'],
  ['2c32b9b3ee358c119e210f6f5195f9bd34894d78a785ff2e95d60e718e400af4', 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD'],
].sort(([a], [b]) => a.localeCompare(b));

export function assertFontCss(css, subject) {
  assert.ok(css.includes(FONT_LICENSE), `${subject}: missing standalone font license`);
  const clean = css.replace(/\/\*[\s\S]*?\*\//g, '');
  const faces = [...clean.matchAll(/@font-face\s*\{([^}]+)\}/gi)].map(([, block]) => {
    const descriptor = (name) => block.match(new RegExp(`\\b${name}\\s*:\\s*([^;]+)`, 'i'))?.[1].trim();
    assert.equal(descriptor('font-family')?.replace(/["']/g, ''), 'JetBrains Mono', subject);
    assert.equal(descriptor('font-style'), 'normal', subject);
    assert.equal(descriptor('font-weight')?.replace(/\s+/g, ' '), '400 800', subject);
    assert.doesNotMatch(block, /\blocal\s*\(/i, `${subject}: installed fonts must not override embedded bytes`);
    const encoded = block.match(/\bsrc\s*:\s*url\(\s*["']?data:font\/woff2;base64,([A-Za-z0-9+/=]+)["']?\s*\)/i)?.[1];
    assert.ok(encoded, `${subject}: missing embedded WOFF2 source`);
    const bytes = Buffer.from(encoded, 'base64');
    assert.equal(bytes.toString('latin1', 0, 4), 'wOF2', subject);
    return [createHash('sha256').update(bytes).digest('hex'), descriptor('unicode-range')?.replace(/\s+/g, '').toUpperCase()];
  }).sort(([a], [b]) => a.localeCompare(b));
  assert.deepEqual(faces, EXPECTED_FACES, `${subject}: font bytes or character coverage changed`);
}

// Parse HTML instead of scanning script/comment strings. parse5 also decodes
// srcdoc exactly once, so each nested viewer must satisfy the contract itself.
export function inspectDocuments(html, subject = 'artifact') {
  const document = { subject, styles: [], scripts: [], resources: [], children: [] };
  const remote = (value) => /^(?:https?:)?\/\//i.test(value || '');
  function cssResources(css) {
    const clean = css.replace(/\/\*[\s\S]*?\*\//g, '');
    for (const match of clean.matchAll(/(?:url\(\s*|@import\s+)["']?((?:https?:)?\/\/[^"')\s;]+)/gi)) document.resources.push(match[1]);
  }
  function visit(node) {
    const attrs = Object.fromEntries((node.attrs || []).map(({ name, value }) => [name, value]));
    const text = (node.childNodes || []).filter((child) => child.nodeName === '#text').map((child) => child.value).join('');
    if (node.tagName === 'style') { document.styles.push(text); cssResources(text); }
    if (node.tagName === 'script') document.scripts.push(text);
    if (attrs.style) cssResources(attrs.style);
    for (const name of ['src', 'poster', 'data']) if (remote(attrs[name])) document.resources.push(attrs[name]);
    if (attrs.srcset) for (const match of attrs.srcset.matchAll(/(?:^|[\s,])((?:https?:)?\/\/[^\s,]+)/g)) document.resources.push(match[1]);
    if (['image', 'use', 'feImage'].includes(node.tagName) || (node.tagName === 'link' && /\b(stylesheet|preconnect|dns-prefetch|preload|modulepreload|prefetch|icon)\b/.test(attrs.rel || ''))) {
      if (remote(attrs.href)) document.resources.push(attrs.href);
    }
    if (node.tagName === 'iframe' && attrs.srcdoc != null) document.children.push(...inspectDocuments(attrs.srcdoc, `${subject}/srcdoc[${document.children.length}]`));
    for (const child of node.childNodes || []) visit(child);
  }
  visit(parse(html));
  return [document, ...document.children];
}

export function assertOfflineArtifact(html, subject) {
  const documents = inspectDocuments(html, subject);
  let viewers = 0;
  for (const document of documents) {
    assert.deepEqual(document.resources, [], `${document.subject}: external subresource`);
    const viewer = document.scripts.some((script) => /Archify\.readerLayout/.test(script));
    if (viewer) {
      assertFontCss(document.styles.join('\n'), document.subject);
      viewers += 1;
    }
  }
  assert.ok(viewers > 0, `${subject}: expected a viewer document`);
  return viewers;
}
```

## test/helpers/viewer-click.mjs

```js
import assert from 'node:assert/strict';

// Keep native input and assert the actual event target, so a later docking
// update cannot silently turn a missed click into an unrelated state timeout.
function installClickObserver() {
  const describe = el => el ? { tag: el.tagName, id: el.id, node: el.getAttribute('data-node-id') } : null;
  const state = el => {
    const r = el?.getBoundingClientRect();
    const point = r ? { x: r.x + r.width / 2, y: r.y + r.height / 2 } : null;
    const hit = point && document.elementFromPoint(point.x, point.y);
    return {
      point, rect: r ? [r.x, r.y, r.width, r.height] : null,
      visible: !!el?.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }),
      enabled: !!el && !el.matches(':disabled,[aria-disabled="true"]'),
      hit: describe(hit), matches: !!el && (el === hit || el.contains(hit)),
      camera: document.querySelector('.diagram-container')?.getAttribute('data-camera-transaction'),
      dock: document.getElementById('route-probe')?.getAttribute('data-route-dock'),
    };
  };
  let listener, observation;
  function arm(selector) {
    if (listener) removeEventListener('click', listener, true);
    observation = null;
    listener = event => {
      const el = document.querySelector(selector);
      observation = { trusted: event.isTrusted, matches: !!el && (el === event.target || el.contains(event.target)),
        target: describe(event.target), state: state(el), x: event.clientX, y: event.clientY };
    };
    addEventListener('click', listener, { capture: true, once: true });
  }
  window.viewerClick = {
    async ready(selector, timeout) {
      document.querySelector(selector)?.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'instant' });
      await Archify.viewerChromeLayout.whenStable();
      return new Promise((resolve, reject) => {
        const start = performance.now();
        let previous;
        function sample() {
          const current = state(document.querySelector(selector));
          const stable = current.rect && previous?.every((value, i) => Math.abs(value - current.rect[i]) < 0.1);
          if (stable && current.visible && current.enabled && current.matches && !current.camera) {
            arm(selector);
            return resolve(current.point);
          }
          if (performance.now() - start > timeout) {
            return reject(new Error('Viewer click target not ready: ' + selector + ' ' + JSON.stringify(current)));
          }
          previous = current.rect;
          requestAnimationFrame(sample);
        }
        requestAnimationFrame(sample);
      });
    },
    finish() {
      removeEventListener('click', listener, true);
      listener = null;
      return observation;
    },
  };
}

export async function createViewerClick({ send, run, timeout }) {
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `(${installClickObserver.toString()})();` });
  return async selector => {
    const point = await run(`viewerClick.ready(${JSON.stringify(selector)}, ${timeout})`);
    await send('Input.dispatchMouseEvent', { type: 'mousePressed', ...point, button: 'left', clickCount: 1 });
    await send('Input.dispatchMouseEvent', { type: 'mouseReleased', ...point, button: 'left', clickCount: 1 });
    const observation = await run('viewerClick.finish()');
    assert.ok(observation?.trusted && observation.matches,
      `Native click missed ${selector}: ${JSON.stringify({ point, observation })}`);
  };
}
```

## test/helpers/xml.mjs

```js
import { parse, parseFragment } from 'parse5';
import { SaxesParser } from 'saxes';

function visit(node, callback, insideSvg = false) {
  callback(node, insideSvg);
  const childInsideSvg = insideSvg || node.tagName === 'svg';
  for (const child of node.childNodes || []) visit(child, callback, childInsideSvg);
  if (node.content) visit(node.content, callback, childInsideSvg);
}

export function parseXml(source) {
  return new SaxesParser({ xmlns: true }).write(source).close();
}

export function extractSvgs(markup, fragment = false) {
  const document = fragment
    ? parseFragment(markup, { sourceCodeLocationInfo: true })
    : parse(markup, { sourceCodeLocationInfo: true });
  const direct = [];
  const srcdocs = [];

  visit(document, (node, insideSvg) => {
    if (node.tagName === 'svg' && !insideSvg && node.sourceCodeLocation) {
      direct.push(markup.slice(node.sourceCodeLocation.startOffset, node.sourceCodeLocation.endOffset));
    }
    const srcdoc = node.attrs?.find((attribute) => attribute.name === 'srcdoc');
    if (srcdoc) srcdocs.push(srcdoc.value);
  });

  return {
    direct,
    embedded: srcdocs.flatMap((srcdoc) => {
      const nested = extractSvgs(srcdoc, true);
      return [...nested.direct, ...nested.embedded];
    }),
  };
}
```

## test/i18n.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';

import {
  SUPPORTED_LOCALES,
  catalogKeys,
  translateCount,
  translateMessage,
} from '../renderers/shared/i18n.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const cli = path.join(skillRoot, 'bin/archify.mjs');
const templatePath = path.join(skillRoot, 'assets/template.html');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-i18n-'));
const chromePath = process.env.ARCHIFY_CHROME ? findChrome() : null;
let sequence = 0;

const EXAMPLES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function example(type) {
  return JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[type]), 'utf8'));
}

const AUTHORED_TEXT_KEYS = new Set([
  'title',
  'subtitle',
  'label',
  'sublabel',
  'tag',
  'note',
  'context',
  'responsibility',
  'classification',
  'step',
]);

function authoredExample(type, locale) {
  const document = example(type);
  const authored = [];
  let authoredIndex = 0;
  const nextAuthoredText = () => {
    authoredIndex += 1;
    const value = locale === 'zh-CN'
      ? `文案${String(authoredIndex).padStart(2, '0')}`
      : `Copy${String(authoredIndex).padStart(2, '0')}`;
    authored.push(value);
    return value;
  };
  const rewrite = (value, path = []) => {
    if (Array.isArray(value)) {
      value.forEach((item, index) => rewrite(item, [...path, index]));
      return;
    }
    if (!value || typeof value !== 'object') return;
    for (const [key, child] of Object.entries(value)) {
      if (typeof child === 'string' && AUTHORED_TEXT_KEYS.has(key)) {
        value[key] = nextAuthoredText();
      } else if (key === 'items' && path.includes('cards') && Array.isArray(child)) {
        value[key] = child.map((item) => (typeof item === 'string' ? nextAuthoredText() : item));
      } else {
        rewrite(child, [...path, key]);
      }
    }
  };

  rewrite(document);
  document.meta.locale = locale;
  if (!document.meta.subtitle) document.meta.subtitle = nextAuthoredText();
  return { document, authored };
}

function run(type, document, command = 'render') {
  const id = sequence++;
  const input = path.join(tmp, `${id}-${type}.json`);
  const output = path.join(tmp, `${id}-${type}.html`);
  fs.writeFileSync(input, JSON.stringify(document));
  const args = command === 'render'
    ? [cli, 'render', type, input, output]
    : [cli, 'validate', type, input, '--json'];
  const result = spawnSync(process.execPath, args, { cwd: skillRoot, encoding: 'utf8' });
  return {
    ...result,
    output,
    html: result.status === 0 && command === 'render' ? fs.readFileSync(output, 'utf8') : '',
  };
}

async function evaluate(browser, sessionId, expression, awaitPromise = false) {
  const response = await browser.cdp.send('Runtime.evaluate', {
    expression,
    returnByValue: true,
    awaitPromise,
  }, sessionId);
  if (response.exceptionDetails) {
    throw new Error(response.exceptionDetails.exception?.description
      || response.exceptionDetails.text
      || 'browser evaluation failed');
  }
  return response.result?.value;
}

async function loadArtifact(browser, artifactPath) {
  const sessionId = await browser.sessionPromise;
  await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
    width: 1440,
    height: 900,
    deviceScaleFactor: 1,
    mobile: false,
  }, sessionId);
  const loaded = browser.cdp.waitFor('Page.loadEventFired', sessionId);
  const navigation = await browser.cdp.send('Page.navigate', {
    url: pathToFileURL(artifactPath).href,
  }, sessionId);
  if (navigation.errorText) throw new Error(`Chrome navigation failed: ${navigation.errorText}`);
  await loaded;
  await evaluate(browser, sessionId, `new Promise(function (resolve) {
    requestAnimationFrame(function () { requestAnimationFrame(function () { resolve(true); }); });
  })`, true);
  return sessionId;
}

test('zh-CN localizes renderer-owned output across all five modes without translating authored content', () => {
  assert.deepEqual(SUPPORTED_LOCALES, ['en', 'zh-CN']);
  for (const type of Object.keys(EXAMPLES)) {
    const document = example(type);
    const authoredTitle = document.meta.title;
    document.meta.locale = 'zh-CN';
    delete document.meta.subtitle;

    const result = run(type, document);
    assert.equal(result.status, 0, `${type}: ${result.stderr || result.stdout}`);
    assert.match(result.html, /^<!DOCTYPE html>\n<html lang="zh-CN"/);
    assert.match(result.html, /<svg\b[^>]*\blang="zh-CN"/);
    assert.ok(result.html.includes(`<title>${authoredTitle}</title>`), `${type}: authored title changed`);
    assert.ok(result.html.includes(`<h1>${authoredTitle}</h1>`), `${type}: authored heading changed`);
    assert.match(result.html, /<text\b[^>]*>\u56fe\u4f8b<\/text>/);
    assert.match(result.html, /aria-label="\u805a\u7126/);
    assert.match(result.html, new RegExp(`<desc id="archify-diagram-description">\u7531 Archify \u751f\u6210\u7684`));
    assert.match(result.html, /"locale":"zh-CN"/);
    assert.match(result.html, />\u5bfc\u51fa\u56fe\u8868</);
    assert.doesNotMatch(result.html, /\{\{i18n:/);
  }
});

test('explicit en and zh-CN preserve complete authored field inventories across all five modes', () => {
  for (const type of Object.keys(EXAMPLES)) {
    const english = authoredExample(type, 'en');
    const chinese = authoredExample(type, 'zh-CN');
    assert.equal(english.authored.length, chinese.authored.length, `${type}: authored shapes differ`);
    assert.ok(english.authored.length >= 10, `${type}: authored inventory is unexpectedly small`);
    if (type === 'dataflow') {
      assert.ok(
        english.authored.includes(english.document.flows[0].classification),
        'dataflow: classification is missing from the authored inventory',
      );
    }
    if (type === 'lifecycle') {
      assert.ok(
        english.authored.includes(english.document.states[0].step),
        'lifecycle: step is missing from the authored inventory',
      );
    }

    for (const candidate of [english, chinese]) {
      const locale = candidate.document.meta.locale;
      const result = run(type, candidate.document);
      assert.equal(result.status, 0, `${type}/${locale}: ${result.stderr || result.stdout}`);
      assert.match(result.html, new RegExp(`^<!DOCTYPE html>\\n<html lang="${locale}"`));
      assert.match(result.html, new RegExp(`<svg\\b[^>]*\\blang="${locale}"`));
      assert.match(result.html, new RegExp(`"locale":"${locale}"`));
      for (const authoredText of candidate.authored) {
        assert.ok(result.html.includes(authoredText), `${type}/${locale}: lost authored text ${authoredText}`);
      }
      if (locale === 'zh-CN') {
        assert.ok(result.html.includes(`<title>${candidate.document.meta.title}</title>`), type);
        assert.match(result.html, />导出图表</);
      } else {
        assert.ok(result.html.includes(`<title>${candidate.document.meta.title} Diagram</title>`), type);
        assert.match(result.html, />Export diagram</);
      }
    }
  }
});

test('omitted locale preserves non-English authored content and the English Viewer contract in all five modes', () => {
  for (const type of Object.keys(EXAMPLES)) {
    const document = example(type);
    const authoredTitle = `作者内容-${type}`;
    document.meta.title = authoredTitle;
    delete document.meta.locale;
    delete document.meta.subtitle;

    const result = run(type, document);
    assert.equal(result.status, 0, `${type}: ${result.stderr || result.stdout}`);
    assert.match(result.html, /^<!DOCTYPE html>\n<html lang="en"/);
    assert.ok(result.html.includes(`<title>${authoredTitle} Diagram</title>`), `${type}: authored title changed`);
    assert.ok(result.html.includes(`<h1>${authoredTitle}</h1>`), `${type}: authored heading changed`);
    assert.match(result.html, /<svg\b[^>]*\blang="en"/);
    assert.match(result.html, /aria-label="Focus /);
    assert.match(result.html, /"locale":"en"/);
    assert.match(result.html, />Export diagram</);
  }
});

test('unsupported locale values fail schema validation in every mode', () => {
  for (const locale of ['fr', 'zh-HK']) {
    for (const type of Object.keys(EXAMPLES)) {
      const document = example(type);
      document.meta.locale = locale;
      const result = run(type, document, 'validate');
      assert.notEqual(result.status, 0, `${type}: unsupported locale ${locale} unexpectedly passed`);
      const payload = JSON.parse(result.stdout);
      assert.equal(payload.ok, false);
      assert.ok(payload.diagnostics.some((entry) => entry.subject?.path === '/meta/locale'), `${type}: ${locale}`);
    }
  }
});

test('real Chrome keeps zh-CN Finder, Route, Export, and accessibility UI localized in all five modes', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser localization regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    for (const type of Object.keys(EXAMPLES)) {
      const document = example(type);
      document.meta.locale = 'zh-CN';
      document.meta.title = `浏览器本地化-${type}`;
      const result = run(type, document);
      assert.equal(result.status, 0, `${type}: ${result.stderr || result.stdout}`);

      const sessionId = await loadArtifact(browser, result.output);
      const state = await evaluate(browser, sessionId, `(function () {
        var finderButton = document.getElementById('btn-node-finder');
        var routeButton = document.getElementById('btn-route-probe');
        var exportButton = document.getElementById('btn-export');
        finderButton.click();
        var finder = {
          hidden: document.getElementById('node-finder').hidden,
          title: document.getElementById('node-finder-title').textContent.trim(),
          searchLabel: document.getElementById('node-finder-input').getAttribute('aria-label')
        };
        document.getElementById('node-finder-close').click();
        routeButton.click();
        var route = {
          hidden: document.getElementById('route-probe').hidden,
          title: document.getElementById('route-probe-title').textContent.trim(),
          label: routeButton.getAttribute('aria-label')
        };
        routeButton.click();
        exportButton.click();
        var exportMenu = document.getElementById('export-menu');
        function pseudoContent(selector) {
          var content = getComputedStyle(document.querySelector(selector), '::after').content || '';
          return content.replace(/^["']|["']$/g, '');
        }
        var presetBadges = {};
        ['signal-flow', 'blueprint', 'editorial'].forEach(function (preset) {
          document.documentElement.setAttribute('data-preset', preset);
          presetBadges[preset] = {
            header: pseudoContent('.header-row'),
            plate: pseudoContent('.diagram-container')
          };
        });
        return {
          htmlLang: document.documentElement.lang,
          svgLang: document.querySelector('.diagram-container svg').getAttribute('lang'),
          toolbarLabel: document.querySelector('.diagram-nav').getAttribute('aria-label'),
          finder: finder,
          route: route,
          exportMenuOpen: exportMenu.classList.contains('open'),
          exportLabel: exportButton.getAttribute('aria-label'),
          exportMenuLabel: exportMenu.getAttribute('aria-label'),
          exportMenuText: exportMenu.textContent,
          presetBadges: presetBadges
        };
      })()`);

      assert.equal(state.htmlLang, 'zh-CN', type);
      assert.equal(state.svgLang, 'zh-CN', type);
      assert.equal(state.toolbarLabel, '图表视图控制', type);
      assert.deepEqual(state.finder, {
        hidden: false,
        title: '查找节点',
        searchLabel: '搜索图表节点',
      }, type);
      assert.deepEqual(state.route, {
        hidden: false,
        title: '选择起点节点',
        label: '清除已追踪路径',
      }, type);
      assert.equal(state.exportMenuOpen, true, type);
      assert.equal(state.exportLabel, '导出图表', type);
      assert.equal(state.exportMenuLabel, '导出', type);
      assert.match(state.exportMenuText, /分享卡片/, type);
      assert.deepEqual(state.presetBadges, {
        'signal-flow': { header: '信号流', plate: 'none' },
        blueprint: { header: '蓝图 / 修订 01', plate: '' },
        editorial: { header: '编辑风格 / 现场笔记', plate: 'ARCHIFY / 图版 04' },
      }, type);

      const shareCardFailure = await evaluate(browser, sessionId, `(async function () {
        var originalGetContext = HTMLCanvasElement.prototype.getContext;
        HTMLCanvasElement.prototype.getContext = function () { return null; };
        try {
          await Archify.exportMenu.shareCard();
          return { rejected: false, message: '' };
        } catch (error) {
          return { rejected: true, message: String(error && error.message || error) };
        } finally {
          HTMLCanvasElement.prototype.getContext = originalGetContext;
        }
      })()`, true);
      assert.deepEqual(shareCardFailure, {
        rejected: true,
        message: '无法为分享卡片创建二维画布上下文',
      }, type);

      const visual = spawnSync(process.execPath, [cli, 'visual-check', result.output, '--json'], {
        cwd: skillRoot,
        encoding: 'utf8',
        env: { ...process.env, ARCHIFY_CHROME: chromePath },
      });
      assert.ok([0, 1].includes(visual.status), `${type}: ${visual.stderr || visual.stdout}`);
      const receipt = JSON.parse(visual.stdout);
      assert.equal(receipt.visualReview, 'pending', type);
      assert.equal(receipt.chrome.status, 'available', type);
      assert.equal(receipt.readability.status, 'pass', type);
      assert.equal(receipt.viewerChrome.status, 'pass', type);
      assert.equal(receipt.captures.status, 'pass', type);
      assert.equal(
        receipt.containment.viewports.every((viewport) => viewport.overflowX === false),
        true,
        `${type}: localized Viewer introduced horizontal overflow`,
      );
    }
  } finally {
    await browser.close();
  }
});

test('every Viewer message reference resolves through the shared catalog', () => {
  const template = fs.readFileSync(templatePath, 'utf8');
  const keys = new Set(catalogKeys());
  const references = new Set([
    ...[...template.matchAll(/\{\{i18n:([a-zA-Z0-9_.-]+)\}\}/g)].map((match) => match[1]),
    ...[...template.matchAll(/['"](viewer\.[a-zA-Z0-9_.-]+)['"]/g)].map((match) => match[1]),
  ]);
  const unresolved = [...references].filter((key) => (
    !key.endsWith('.') && !keys.has(key) && !(keys.has(`${key}.one`) && keys.has(`${key}.other`))
  ));
  assert.deepEqual(unresolved, []);
});

test('every supported catalog is complete and preserves interpolation variables', () => {
  const variables = (value) => [...value.matchAll(/\{([a-zA-Z0-9_]+)\}/g)]
    .map((match) => match[1])
    .sort();
  for (const key of catalogKeys()) {
    const expected = variables(translateMessage('en', key));
    for (const locale of SUPPORTED_LOCALES) {
      const message = translateMessage(locale, key);
      assert.ok(message && message !== 'undefined', `${locale}: ${key}`);
      assert.deepEqual(variables(message), expected, `${locale}: ${key}`);
    }
  }
});

test('runtime labels stay localized after composition', () => {
  assert.equal(translateMessage('zh-CN', 'viewer.kind.backend'), '后端');
  assert.equal(translateMessage('zh-CN', 'viewer.kind.decision'), '决策');
  assert.equal(translateMessage('zh-CN', 'viewer.passport.relationship.connectsFrom'), '连接自');
  assert.equal(translateMessage('zh-CN', 'viewer.nav.level.auto'), '自动');

  const zhHops = translateCount('zh-CN', 'viewer.route.hop', 2);
  assert.equal(
    translateMessage('zh-CN', 'viewer.finder.result.routeTarget', { label: '终点', links: zhHops }),
    '选择终点作为路径终点，2 跳',
  );
  const enHop = translateCount('en', 'viewer.route.overview.hop', 1);
  const enNode = translateCount('en', 'viewer.route.overview.node', 2);
  assert.equal(
    translateMessage('en', 'viewer.route.overview.status', { nodes: enNode, hops: enHop }),
    '2 nodes · 1 directed hop · shortest authored route',
  );

});

test('Share Card and export failures use catalog messages instead of fixed English', () => {
  assert.equal(
    translateCount('zh-CN', 'viewer.export.card.routeSummary', 2, { source: '来源', target: '目标' }),
    '路径：来源 → 目标 · 2 个有向跳转',
  );
  assert.equal(
    translateMessage('zh-CN', 'viewer.export.error.toBlobNull', { label: '分享卡片' }),
    '分享卡片的 canvas.toBlob 未返回数据',
  );

  const template = fs.readFileSync(templatePath, 'utf8');
  for (const hardcoded of [
    "'Route: '",
    "'Share Card variants cannot be combined'",
    "canvas2dOrThrow(canvas, 'Share Card')",
    "'Share Card export could not remove temporary viewer state'",
    "'WebM motion export requires a trace animation and browser MediaRecorder support'",
  ]) {
    assert.ok(!template.includes(hardcoded), hardcoded);
  }
});
```

## test/intent-trace-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { findChrome } from '../bin/visual-check.mjs';
import { desktopBrowser, desktopPointerCheck } from './helpers/desktop-browser.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;

test('Intent Trace preserves input handoffs, transient geometry and cleanup', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser Intent Trace checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-intent-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_INTENT_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const records = [];
  t.after(() => {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n');
  });
  const cases = {
    architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    files[mode] = path.join(scratch, mode + '.html');
    execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      path.join(skillRoot, 'examples', example), files[mode]]);
  }
  const trace = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', cases.architecture), 'utf8'));
  trace.meta.animation = 'trace';
  const traceInput = path.join(scratch, 'trace.json');
  fs.writeFileSync(traceInput, JSON.stringify(trace)); files.trace = path.join(scratch, 'trace.html');
  execFileSync(process.execPath, [path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'), traceInput, files.trace]);

  const browser = desktopBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  const checkPointer = await desktopPointerCheck(browser, session);
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  await send('Emulation.setFocusEmulationEnabled', { enabled: true });
  async function run(expression) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.intentErrors=[]; window.intentEnds=[];
    addEventListener('error',e=>intentErrors.push(e.message));
    addEventListener('unhandledrejection',e=>intentErrors.push(String(e.reason)));
    addEventListener('animationend',e=>{if(e.target.matches('.intent-trace-flow'))intentEnds.push({name:e.animationName,trusted:e.isTrusted});},true);
    try {localStorage.removeItem('archify-motion');} catch (_) {}
    window.intentWait=predicate=>new Promise((resolve,reject)=>{
      const start=performance.now();
      function sample(){if(predicate())return resolve();if(performance.now()-start>12000)return reject(new Error('Intent observation timed out'));requestAnimationFrame(sample);}
      requestAnimationFrame(sample);
    });
    // This synchronous fixture isolates timer ordering; ordinary pointer tests
    // below still use the browser's real timers and CDP input.
    window.intentTimerFixture=body=>{
      const schedule=window.setTimeout,cancel=window.clearTimeout,queue=new Map(),delays=[];let serial=0;
      window.setTimeout=(fn,delay)=>{delays.push(delay);queue.set(++serial,fn);return serial;};
      window.clearTimeout=id=>queue.delete(id);
      const node=id=>document.querySelector('.diagram-container svg [data-node-id="'+id+'"]');
      const over=(id,related=null,pointerType='mouse')=>node(id).dispatchEvent(new PointerEvent('pointerover',{bubbles:true,pointerType,relatedTarget:related}));
      const out=id=>node(id).dispatchEvent(new PointerEvent('pointerout',{bubbles:true,pointerType:'mouse'}));
      try {return body({node,over,out,queue,delays,fire:()=>{const [id,fn]=queue.entries().next().value;queue.delete(id);fn();}});}
      finally {window.setTimeout=schedule;window.clearTimeout=cancel;}
    };
  ` });
  async function media(reduced) {
    await send('Emulation.setEmulatedMedia', { media: '', features: [
      { name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' },
    ] });
  }
  async function load(mode = 'architecture', { theme = 'dark', reduced = false } = {}) {
    await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 0, y: 0 });
    await send('Emulation.setDeviceMetricsOverride', { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
    await media(reduced);
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    await send('Page.navigate', { url: pathToFileURL(files[mode]).href + `?theme=${theme}` });
    await loaded;
    await checkPointer();
    await run('document.fonts.ready'); await run('Archify.viewerChromeLayout.whenStable()');
  }
  async function point(selector) {
    return run(`(()=>{const r=document.querySelector(${JSON.stringify(selector)}).getBoundingClientRect();return {x:r.x+r.width/2,y:r.y+r.height/2};})()`);
  }
  async function move(id) {
    const p = id ? await point(`.diagram-container svg [data-node-id="${id}"]`) : { x: 0, y: 0 };
    await send('Input.dispatchMouseEvent', { type: 'mouseMoved', ...p });
  }
  async function click(selector) {
    const p = await point(selector);
    await send('Input.dispatchMouseEvent', { type: 'mousePressed', ...p, button: 'left', clickCount: 1 });
    await send('Input.dispatchMouseEvent', { type: 'mouseReleased', ...p, button: 'left', clickCount: 1 });
  }
  async function key(key, code, windowsVirtualKeyCode) {
    await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode });
    await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode });
  }
  async function snapshot(scenario) {
    const state = await run(`(()=>{
      const svg=document.querySelector('.diagram-container > svg');
      return {active:Archify.intentTrace.active(),attribute:svg.getAttribute('data-intent-trace-active'),
        selected:[...svg.querySelectorAll('[data-intent-trace-selected]')].map(n=>n.getAttribute('data-node-id')),
        matched:[...svg.querySelectorAll('[data-node-id][data-intent-trace-match]')].map(n=>n.getAttribute('data-node-id')),
        edges:svg.querySelectorAll('[data-edge-from][data-intent-trace-match]').length,
        overlays:svg.querySelectorAll('[data-intent-trace-overlay]').length,
        directions:[...svg.querySelectorAll('.intent-trace-flow')].map(n=>n.getAttribute('data-direction')),
        status:document.getElementById('intent-trace-status').textContent,
        focus:Archify.focus.active(),route:Archify.routeProbe.active(),owner:Archify.motionGovernor.owner(),
        errors:intentErrors,external:performance.getEntriesByType('resource').map(e=>e.name).filter(n=>/^https?:/.test(n))};
    })()`);
    assert.deepEqual(state.errors, [], scenario); assert.deepEqual(state.external, [], scenario);
    records.push({ scenario, ...state }); return state;
  }
  async function afterTimer() { await run('new Promise(resolve=>setTimeout(resolve,150))'); }

  await t.test('five modes initialize and public return/broadcast contracts remain intact', async () => {
    for (const mode of Object.keys(cases)) {
      await load(mode); const initial = await snapshot(mode + '-initial');
      assert.equal(initial.active, null); assert.equal(initial.overlays, 0); assert.equal(initial.status, '');
      assert.deepEqual(await run('Object.keys(Archify.intentTrace).sort()'), ['active', 'clear', 'show']);
    }
    await load();
    const api = await run(`(()=>{
      const p=Archify.intentTrace;const shown=p.show('api',{announce:true}),text=document.getElementById('intent-trace-status').textContent;
      const overlay=document.querySelector('[data-intent-trace-overlay]');
      const repeated=p.show('api',{announce:true}),same=overlay===document.querySelector('[data-intent-trace-overlay]');
      const missing=p.show('missing'),retained=document.getElementById('intent-trace-status').textContent===text;
      const empty=p.show(''),cleared=p.clear(),twice=p.clear();
      return {shown,repeated,same,missing,retained,empty,cleared:cleared===undefined,twice:twice===undefined,text};
    })()`);
    assert.deepEqual({ ...api, text: '' }, { shown: true, repeated: true, same: true, missing: false, retained: true, empty: false, cleared: true, twice: true, text: '' });
    assert.match(api.text, /API Server/); assert.equal((await snapshot('public-cleared')).status, '');
  });

  await t.test('real pointer delay, fast exit, keyboard focus and Escape share cleanup', async () => {
    await load(); await move('api');
    await run(`intentWait(()=>Archify.intentTrace.active()==='api')`);
    let state = await snapshot('hover-api'); assert.equal(state.status, ''); assert.equal(state.overlays, 1);
    await move(); await afterTimer(); assert.equal((await snapshot('hover-left')).active, null);
    await move('db'); await move(); await afterTimer(); assert.equal((await snapshot('fast-exit')).active, null);
    await move('api'); await move('db');
    await run(`intentWait(()=>Archify.intentTrace.active()==='db')`); await afterTimer();
    assert.equal((await snapshot('hover-replaced')).active, 'db');
    await run(`document.querySelector('.diagram-container svg [data-node-id="api"]').focus()`);
    state = await snapshot('keyboard-api'); assert.equal(state.active, 'api'); assert.match(state.status, /API Server/);
    const internalFocus = await run(`(()=>{
      const overlay=document.querySelector('[data-intent-trace-overlay]'),child=document.querySelector('.diagram-container svg [data-node-id="api"] text');
      child.setAttribute('tabindex','0');child.focus();
      const same=overlay===document.querySelector('[data-intent-trace-overlay]')&&Archify.intentTrace.active()==='api';
      child.blur();child.removeAttribute('tabindex');return same;
    })()`);
    assert.equal(internalFocus, true);
    assert.equal((await snapshot('focusout-falls-back')).active, 'db');
    await move();
    await run(`document.querySelector('.diagram-container svg [data-node-id="api"]').focus()`);
    await key('Escape', 'Escape', 27);
    state = await snapshot('escape'); assert.equal(state.active, null); assert.equal(state.status, '');
    await move('db'); await run(`intentWait(()=>Archify.intentTrace.active()==='db')`);
    // Pointerout sync can reuse the retained keyboard reference after clear.
    await move(); assert.equal((await snapshot('retained-focus')).active, 'api');
    await click('.diagram-nav'); await afterTimer();
    assert.equal((await snapshot('outside-pointerdown')).active, null);
  });

  await t.test('controlled timers preserve repeated-show, retained-input and input-filter semantics', async () => {
    await load();
    const result = await run(`intentTimerFixture(({node,over,out,queue,delays,fire})=>{
      const p=Archify.intentTrace;
      node('api').focus();const text=document.getElementById('intent-trace-status').textContent;
      over('db');p.show('api',{announce:true});const pending=queue.size;fire();const afterHover=p.active();
      out('db');const afterSync=p.active();
      over('db');p.clear({announce:false});const cancelled=queue.size,retained=document.getElementById('intent-trace-status').textContent===text;
      out('db');const reactivated=p.active();
      over('api',node('api').querySelector('text'));const internal=queue.size;
      node('api').blur();p.clear();over('db',null,'touch');const touch=queue.size;
      const mm=window.matchMedia;window.matchMedia=()=>({matches:false});over('db');const coarse=queue.size;
      window.matchMedia=undefined;over('db');const fallback=queue.size;fire();window.matchMedia=mm;
      const fallbackActive=p.active();p.clear();
      return {pending,afterHover,afterSync,cancelled,retained,reactivated,internal,touch,coarse,fallback,fallbackActive,delays};
    })`);
    assert.deepEqual(result, { pending: 1, afterHover: 'db', afterSync: 'api', cancelled: 0, retained: true, reactivated: 'api', internal: 0, touch: 0, coarse: 0, fallback: 1, fallbackActive: 'db', delays: [90, 90, 90] });
    await snapshot('timer-fixture');
    await load(); await move('api'); await run('Archify.intentTrace.clear()'); await afterTimer();
    assert.equal((await snapshot('pending-clear')).active, null);
    await run(`Archify.intentTrace.show('api',{announce:true}); window.dispatchEvent(new Event('blur'))`); await afterTimer();
    const blurred = await snapshot('blur-fixture'); assert.equal(blurred.active, null); assert.match(blurred.status, /API Server/);
  });

  await t.test('a minimal SVG fixture preserves counting, cloned geometry and isolated nodes', async () => {
    await load();
    const result = await run(`(()=>{
      const svg=document.querySelector('.diagram-container > svg');
      svg.innerHTML='<g data-edge-from="a" data-edge-to="b" data-edge-key="ab" transform="translate(3 4)"><path id="original-path" d="M0 0 L10 10" class="original" style="opacity:.7" marker-end="url(#arrow)"/><line x1="1" y1="2" x2="3" y2="4"/></g><path data-edge-from="a" data-edge-to="b" data-edge-key="ab" d="M2 3 L4 5"/><polyline data-edge-from="c" data-edge-to="a" data-edge-label="incoming" points="1,2 3,4"/><path data-edge-from="a" data-edge-to="a" d="M0 0 C1 2 3 4 0 0"/><g data-node-id="a" data-node-label="Alpha"><rect width="10" height="10"/></g><g data-node-id="b"/><g data-node-id="c"/><g data-node-id="solo"/>';
      const original=svg.querySelector('#original-path').outerHTML;Archify.intentTrace.show('a',{announce:true});
      const overlay=svg.querySelector('[data-intent-trace-overlay]'),shapes=[...overlay.querySelectorAll('.intent-trace-flow')];
      const data={directions:shapes.map(n=>n.getAttribute('data-direction')),status:document.getElementById('intent-trace-status').textContent,
        transform:overlay.firstElementChild.getAttribute('transform'),d:shapes[0].getAttribute('d'),points:shapes[3].getAttribute('points'),
        stripped:!overlay.querySelector('[id],[style],[marker-end],[data-edge-from]'),pathLength:shapes.every(n=>n.getAttribute('pathLength')==='1'),
        original:svg.querySelector('#original-path').outerHTML===original,beforeEdges:overlay.nextElementSibling.hasAttribute('data-edge-from')};
      Archify.intentTrace.show('solo',{announce:true});data.solo={active:Archify.intentTrace.active(),overlays:svg.querySelectorAll('[data-intent-trace-overlay]').length,status:document.getElementById('intent-trace-status').textContent};
      return data;
    })()`);
    assert.deepEqual(result.directions, ['out', 'out', 'out', 'in', 'loop']);
    assert.match(result.status, /Alpha/); assert.match(result.status, /1 outgoing/); assert.match(result.status, /1 incoming/);
    assert.match(result.status, /1 self loop/); assert.match(result.status, /3 connections/);
    assert.equal(result.transform, 'translate(3 4)'); assert.equal(result.d, 'M0 0 L10 10'); assert.equal(result.points, '1,2 3,4');
    for (const flag of ['stripped', 'pathLength', 'original', 'beforeEdges']) assert.equal(result[flag], true, flag);
    assert.equal(result.solo.active, 'solo'); assert.equal(result.solo.overlays, 0);
    assert.match(result.solo.status, /0 connections/);
    await snapshot('geometry-fixture');
  });

  await t.test('blockers gate requests while actual Focus, Route and Lens retain handoff behavior', async () => {
    await load();
    const blockers = await run(`(()=>{
      const html=document.documentElement,svg=document.querySelector('.diagram-container > svg'),container=svg.parentElement,p=Archify.intentTrace;
      const cases=[[html,'data-embed','true'],[html,'data-guide-open','true'],[svg,'data-lens-active',''],[svg,'data-story-active',''],[svg,'data-relationship-preview-active','']];
      const results=cases.map(([el,name,value])=>{p.show('api');el.setAttribute(name,value);const before=p.active(),rejected=p.show('api'),after=p.active();el.removeAttribute(name);return {before,rejected,after};});
      container.classList.add('is-panning');results.push({rejected:p.show('api'),after:p.active()});container.classList.remove('is-panning');return results;
    })()`);
    for (const row of blockers) { assert.equal(row.rejected, false); assert.equal(row.after, null); }
    assert.ok(blockers.slice(0, 5).every(row => row.before === 'api'));
    for (const [name, action, release] of [
      ['focus', `Archify.focus.set('api',{toggle:false})`, `Archify.focus.clear({updateUrl:false})`],
      ['route', `Archify.routeProbe.begin({source:'users'})`, `Archify.routeProbe.clear({updateUrl:false})`],
      ['lens', `Archify.semanticLens.select('backend')`, `Archify.semanticLens.clear({updateUrl:false})`],
    ]) {
      await load(); await move('db'); await run(`intentWait(()=>Archify.intentTrace.active()==='db')`);
      await run(action); await snapshot(name + '-handoff');
      assert.equal(await run(`Archify.intentTrace.show('db')`), false);
      assert.equal((await snapshot(name + '-blocked')).overlays, 0);
      await run(release); await move(); await move('db');
      await run(`intentWait(()=>Archify.intentTrace.active()==='db')`);
      assert.equal((await snapshot(name + '-released')).active, 'db');
    }
  });

  await t.test('real CSS completion, Motion ownership, reduced motion and themes preserve previews', async () => {
    await load('trace'); await run(`intentWait(()=>document.documentElement.getAttribute('data-ambient-motion')==='settled')`);
    await move('api'); await run(`intentWait(()=>Archify.motionGovernor.owner()==='intent')`);
    await run(`intentWait(()=>intentEnds.some(e=>e.trusted&&e.name==='archify-intent-trace-flow'))`);
    assert.equal((await snapshot('animation-complete')).active, 'api');
    const direction = await run(`getComputedStyle(document.querySelector('.intent-trace-flow[data-direction="in"]')).animationDirection`);
    assert.equal(direction, 'normal');
    await move(); await run(`intentWait(()=>Archify.motionGovernor.owner()==='')`);
    await snapshot('owner-cleared');
    for (const theme of ['dark', 'light']) {
      await load('trace', { theme, reduced: true });
      const scheduled = await run(`intentTimerFixture(({over,queue,delays,fire})=>{over('api');const before=Archify.intentTrace.active();fire();return {before,after:Archify.intentTrace.active(),delays};})`);
      assert.deepEqual(scheduled, { before: null, after: 'api', delays: [0] });
      await run(`intentWait(()=>Archify.motionGovernor.owner()==='intent')`);
      const style = await run(`(()=>{const c=getComputedStyle(document.querySelector('.intent-trace-flow'));return {animation:c.animationName,pointer:getComputedStyle(document.querySelector('.intent-trace-overlay')).pointerEvents,dash:c.strokeDasharray};})()`);
      assert.equal(style.animation, 'none'); assert.equal(style.pointer, 'none'); assert.equal(style.dash, 'none');
      await snapshot('reduced-' + theme);
      if (evidence) {
        await run(`Promise.all([...document.querySelectorAll('[data-intent-trace-match]')].flatMap(n=>n.getAnimations()).filter(a=>Number.isFinite(a.effect.getTiming().iterations)).map(a=>a.finished.catch(()=>{})))`);
        await run('new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))');
        const shot = await send('Page.captureScreenshot', { format: 'png' });
        fs.writeFileSync(path.join(evidence, theme + '.png'), Buffer.from(shot.data, 'base64'));
      }
      const exported = await run(`(async()=>{
        const original=URL.createObjectURL;let blob;
        URL.createObjectURL=function(v){if(v.type.startsWith('image/svg+xml'))blob=v;return original.call(URL,v);};
        try {await Archify.exportMenu.run('svg');}finally{URL.createObjectURL=original;}
        const root=new DOMParser().parseFromString(await blob.text(),'image/svg+xml').documentElement;
        return {clean:!root.hasAttribute('data-intent-trace-active')&&!root.querySelector('[data-intent-trace-overlay],[data-intent-trace-match],[data-intent-trace-selected]'),viewBox:root.getAttribute('viewBox')===document.querySelector('.diagram-container > svg').getAttribute('viewBox')};
      })()`);
      assert.deepEqual(exported, { clean: true, viewBox: true });
      await click('.diagram-container svg [data-node-id="api"]');
      assert.equal(await run('Archify.focus.active()'), 'api');
    }
  });
});
```

## test/intent-trace.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-intent-trace-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers inherit one geometry-neutral Intent Trace', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /Archify\.intentTrace = \(function \(\)/, mode);
    assert.match(html, /id="intent-trace-status" role="status" aria-live="polite" aria-atomic="true"/, mode);
    assert.match(html, /svg\.setAttribute\('data-intent-trace-active', id\)/, mode);
    assert.match(html, /data-intent-trace-overlay/, mode);
    assert.equal((html.match(/<svg\b/g) || []).length, 1, `${mode} keeps one static canonical SVG`);
    assert.doesNotMatch(canonicalSvg(html), /data-intent-trace|intent-trace-flow/, mode);
  }
});

test('Intent Trace derives exact one-hop direction from stable renderer relationships', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /function show\(id, options\)/);
  assert.match(html, /if \(from !== id && to !== id\) return/);
  assert.match(html, /direction = from === id && to === id \? 'loop' : \(from === id \? 'out' : 'in'\)/);
  assert.match(html, /related\[from\] = true/);
  assert.match(html, /related\[to\] = true/);
  assert.match(html, /edge\.setAttribute\('data-intent-trace-match', ''\)/);
  assert.match(html, /node\.setAttribute\('data-intent-trace-selected', ''\)/);
  assert.match(html, /clone\.setAttribute\('data-direction', direction\)/);
  assert.match(html, /counts\[direction\] \+= 1/);
});

test('Intent Trace keeps incoming and outgoing motion on authored source-to-target geometry', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    const incomingRule = html.match(/\.intent-trace-flow\[data-direction="in"\]\s*\{[\s\S]*?\}/)?.[0] || '';
    assert.ok(incomingRule, `${mode}: expected the incoming Intent Trace style`);
    assert.doesNotMatch(
      incomingRule,
      /animation-direction\s*:\s*reverse/,
      `${mode}: incoming authored geometry must not be replayed from target to source`,
    );
    assert.match(incomingRule, /animation-direction\s*:\s*normal/, mode);
    assert.match(html, /function traceGeometry\(shape, direction\)[\s\S]+shape\.cloneNode\(false\)/, mode);
    assert.match(html, /@keyframes archify-intent-trace-flow[\s\S]+stroke-dashoffset: -1/, mode);
  }
});

test('Intent Trace separates hover, keyboard, touch, and committed focus', () => {
  const html = render('sequence', CASES.sequence);
  assert.match(html, /window\.matchMedia\('\(hover: hover\) and \(pointer: fine\)'\)/);
  assert.match(html, /event\.pointerType === 'touch'/);
  assert.match(html, /addEventListener\('pointerover'/);
  assert.match(html, /addEventListener\('pointerout'/);
  assert.match(html, /addEventListener\('focusin'/);
  assert.match(html, /addEventListener\('focusout'/);
  assert.match(html, /show\(node\.getAttribute\('data-node-id'\), \{ announce: true \}\)/);
  assert.match(html, /Press Enter for details/);
  assert.match(html, /html\.getAttribute\('data-embed'\) === 'true'/);
  assert.match(html, /container\.classList\.contains\('is-panning'\)/);
  assert.match(html, /svg\.hasAttribute\('data-story-active'\)/);
  assert.match(html, /svg\.hasAttribute\('data-relationship-preview-active'\)/);
  assert.match(html, /Archify\.focus\.active\(\)/);
  assert.match(html, /Archify\.intentTrace\.clear\(\{ announce: false \}\)/);
  assert.match(html, /e\.key === 'Escape' && Archify\.intentTrace\.active\(\)/);
});

test('Intent Trace normalizes motion, respects reduced motion, and exports cleanly', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /clone\.setAttribute\('pathLength', '1'\)/);
  assert.match(html, /\.intent-trace-flow\[data-direction="out"\]/);
  assert.match(html, /\.intent-trace-flow\[data-direction="in"\]/);
  assert.match(html, /\.intent-trace-flow\[data-direction="loop"\]/);
  assert.match(html, /@keyframes archify-intent-trace-flow/);
  assert.match(html, /animation: archify-intent-trace-flow 1\.15s linear 1 both/);
  assert.match(html, /@media \(prefers-reduced-motion: reduce\)[\s\S]+\.intent-trace-flow \{[\s\S]+animation: none !important/);
  assert.match(html, /clone\.removeAttribute\('data-intent-trace-active'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-intent-trace-overlay\]'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-intent-trace-match\], \[data-intent-trace-selected\]'\)/);
  assert.match(html, /!clone\.hasAttribute\('data-intent-trace-active'\)/);
  assert.doesNotMatch(canonicalSvg(html), /data-intent-trace|intent-trace-flow/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/landing.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const docsRoot = path.join(repoRoot, 'docs');
const landing = fs.readFileSync(path.join(docsRoot, 'index.html'), 'utf8');
const manifest = JSON.parse(fs.readFileSync(path.join(docsRoot, 'gallery', 'manifest.json'), 'utf8'));

const proofs = [
  {
    key: 'signal', id: 'agent-tool-call', artifact: 'gallery/artifacts/agent-tool-call.workflow.html',
    preset: 'signal-flow', nodes: 12, edges: 11, view: 'happy-path',
  },
  {
    key: 'blueprint', id: 'deployment-ownership', artifact: 'gallery/artifacts/production-deployment.architecture.html',
    preset: 'blueprint', nodes: 12, edges: 12, view: 'request-boundary',
  },
  {
    key: 'classic', id: 'cache-miss', artifact: 'gallery/artifacts/cache-miss.sequence.html',
    preset: 'classic', nodes: 7, edges: 12, view: 'cache-fallback',
  },
];

test('landing metadata describes the full technical-diagram product and trusted hero promise', () => {
  assert.match(landing, /<title>Archify — Technical Diagrams from Plain English<\/title>/);
  assert.match(landing, /<meta property="og:title" content="Archify — Technical Diagrams from Plain English">/);
  assert.match(landing, /An agent skill for Cursor, Claude Code, Codex CLI, and OpenCode/);
  assert.equal((landing.match(/npx -y skills add tt-a1i\/archify --skill archify --agent cursor --global --copy --yes/g) || []).length, 2);
  assert.match(landing, /From plain English<br>to architecture <em>you can trust\.<\/em>/);
});

test('landing hero leads with three real generated proof artifacts', () => {
  assert.match(landing, /id="hero-proof-stage"/);
  assert.match(landing, /id="hero-proof-panel" role="tabpanel"/);
  assert.equal((landing.match(/class="spec-card"/g) || []).length, 3);
  assert.equal((landing.match(/role="tab"/g) || []).length, 3);
  assert.doesNotMatch(landing, /class="hero-screenshot/);

  for (const proof of proofs) {
    const entry = manifest.entries.find(item => item.id === proof.id);
    assert.ok(entry, `${proof.id}: proof manifest entry missing`);
    assert.equal(entry.artifact, proof.artifact);
    assert.equal(entry.visualPreset, proof.preset);
    assert.equal(entry.animation, 'trace');
    assert.equal(entry.nodeCount, proof.nodes);
    assert.equal(entry.edgeCount, proof.edges);
    assert.ok(entry.viewIds.includes(proof.view));
    assert.ok(entry.checks.every(check => check.ok), `${proof.id}: validation receipt is not green`);
    assert.ok(fs.existsSync(path.join(docsRoot, proof.artifact)), `${proof.id}: live artifact missing`);
    assert.match(landing, new RegExp(`data-proof="${proof.key}"`));
    assert.ok(landing.includes(`artifact: '${proof.artifact}'`));
    assert.ok(landing.includes(`view: '${proof.view}'`));
  }
});

test('landing proof switcher is bilingual and keyboard navigable', () => {
  assert.match(landing, /proof-live':'Live proof'/);
  assert.match(landing, /proof-live':'实时成品'/);
  assert.match(landing, /event\.key === 'ArrowRight'/);
  assert.match(landing, /event\.key === 'ArrowLeft'/);
  assert.match(landing, /event\.key === 'Home'/);
  assert.match(landing, /event\.key === 'End'/);
  assert.match(landing, /proofFrame\.dataset\.proof !== key/);
  assert.match(landing, /\?embed=1&amp;play=1&amp;theme=dark#view=happy-path/);
  assert.match(landing, /sandbox="allow-scripts"/);
  assert.match(landing, /const playback = play \? '&play=1' : ''/);
  assert.match(landing, /proofEmbedUrl\(proof, \{ play: deliberate \}\)/);
  assert.doesNotMatch(landing, /proofFrame\.contentWindow|proofFrame\.contentDocument/);
  assert.match(landing, /\?present=1&play=1#view=/);
  assert.match(landing, /#view=\$\{encodeURIComponent\(proof\.view\)\}/);
  assert.match(landing, /Pin one exact Story Moment, copy its stable link, and let someone else open the same authored node/);
});

test('landing makes Route Journey and core exploration shortcuts discoverable in both languages', () => {
  assert.match(landing, /Route Journey keeps the complete authored path visible/);
  assert.match(landing, /Route Journey 始终保留完整作者路径/);
  assert.match(landing, /one finite, reader-controlled pass over each exact incoming relationship/);
  assert.match(landing, /沿每条精确入向关系播放一次由读者控制的有限旅程/);
  assert.match(landing, /data-i18n="f7-tag">INSPECT · PLAY · PAUSE/);
  assert.match(landing, /'f7-tag':'INSPECT · PLAY · PAUSE'/);
  assert.match(landing, /'f7-tag':'检查 · 播放 · 暂停'/);
  assert.match(landing, /data-i18n="kbd-zoom">Reading depth \/ reset/);
  assert.match(landing, /'kbd-zoom':'阅读层级 \/ 复位'/);
  assert.match(landing, /data-i18n="kbd-guide">Diagram guide<\/span><kbd>\?<\/kbd>/);
  assert.match(landing, /'kbd-guide':'图表指南'/);
  assert.match(landing, /data-i18n="kbd-find">Find node \/ route endpoint<\/span><kbd>\/<\/kbd>/);
  assert.match(landing, /'kbd-find':'查找节点 \/ 路径端点'/);
  assert.match(landing, /data-i18n="kbd-route">Trace, inspect, and play a route<\/span><kbd>R<\/kbd>/);
  assert.match(landing, /'kbd-route':'探查、检查并播放路径'/);
  assert.match(landing, /data-i18n="kbd-lens">Compare semantic kinds<\/span><kbd>L<\/kbd>/);
  assert.match(landing, /'kbd-lens':'对比语义类型'/);
});
```

## test/layout-rules.test.mjs

```js
// Per-rule coverage for the renderers' layout validators. The golden suite's
// negative cases mostly trip ajv SCHEMA rules; this file targets the hand-
// written LAYOUT rules (the `problems.push(...)` checks) — the layer that has
// regressed before — by mutating a valid example into exactly one violation
// and asserting the renderer exits non-zero with the expected message.
//
// It also locks the error-message CONTRACT: representative messages must carry
// both the numeric threshold and a remediation hint, since the consumer is an
// LLM that fixes the JSON from the message alone.
//
//   node --test test/*.test.mjs

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-rules-'));

const EXAMPLES = {
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
  architecture: 'web-app.architecture.json',
};

function load(mode) {
  return JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8'));
}

// Returns { code, stderr }. Never throws on non-zero exit.
function render(mode, doc) {
  const input = path.join(tmp, `${mode}-${Math.abs(hash(JSON.stringify(doc)))}.json`);
  const outPath = path.join(tmp, `${mode}-${Math.abs(hash(JSON.stringify(doc)))}.html`);
  fs.writeFileSync(input, JSON.stringify(doc));
  try {
    execFileSync('node', [
      path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      input,
      outPath,
    ], { stdio: ['ignore', 'ignore', 'pipe'] });
    return { code: 0, stderr: '', outPath };
  } catch (err) {
    return { code: err.status ?? 1, stderr: String(err.stderr || ''), outPath };
  }
}

function validateCli(mode, doc, quality = 'showcase') {
  const input = path.join(tmp, `${mode}-cli-${Math.abs(hash(JSON.stringify(doc)))}.json`);
  fs.writeFileSync(input, JSON.stringify(doc));
  try {
    const stdout = execFileSync('node', [
      path.join(skillRoot, 'bin', 'archify.mjs'),
      'validate',
      mode,
      input,
      '--quality',
      quality,
      '--json',
    ], { encoding: 'utf8' });
    return { code: 0, result: JSON.parse(stdout) };
  } catch (err) {
    return {
      code: err.status ?? 1,
      result: JSON.parse(String(err.stdout || '{}')),
    };
  }
}

function hash(s) {
  let h = 0;
  for (let i = 0; i < s.length; i += 1) h = (h * 31 + s.charCodeAt(i)) | 0;
  return h;
}

function workflowEdgePoints(html, id) {
  const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const pointsAttribute = html.match(
    new RegExp(`data-edge-id="${escapedId}" data-composition-points="([^"]+)"`),
  )?.[1];
  assert.ok(pointsAttribute, `expected rendered workflow edge points for ${id}`);
  return pointsAttribute.split(';').map((point) => point.split(',').map(Number));
}

function workflowNodeRect(html, id) {
  const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const match = html.match(new RegExp(
    `<g id="node-${escapedId}"[^>]*>[\\s\\S]*?<rect x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"`,
  ));
  assert.ok(match, `expected rendered workflow node rect for ${id}`);
  const [, x, y, width, height] = match.map(Number);
  return { x, y, width, height };
}

function boundaryFrameRect(html, index) {
  const match = html.match(new RegExp(
    `<rect data-graph-role="structural-frame"[^>]*data-composition-frame-id="${index}"[^>]*x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"`,
  ));
  assert.ok(match, `expected boundary frame ${index}`);
  const [, x, y, width, height] = match.map(Number);
  return { x, y, width, height };
}

function boundaryTitleMasks(html) {
  return [...html.matchAll(
    /<g data-graph-role="structural-frame-label"[^>]*data-composition-frame-id="(\d+)"[^>]*>[\s\S]*?<rect data-graph-role="structural-frame-label-mask" x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"/g,
  )].map((match) => ({
    index: Number(match[1]),
    x: Number(match[2]),
    y: Number(match[3]),
    width: Number(match[4]),
    height: Number(match[5]),
  }));
}

function rectContainsRect(outer, inner) {
  return outer.x <= inner.x
    && outer.y <= inner.y
    && outer.x + outer.width >= inner.x + inner.width
    && outer.y + outer.height >= inner.y + inner.height;
}

function rectanglesOverlap(left, right) {
  return left.x < right.x + right.width
    && left.x + left.width > right.x
    && left.y < right.y + right.height
    && left.y + left.height > right.y;
}

function workflowEdgeLabelPoint(html, id) {
  const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const match = html.match(new RegExp(
    `<g data-detail="context"[^>]*data-edge-id="${escapedId}"[^>]*>[\\s\\S]*?<text x="([^"]+)" y="([^"]+)"`,
  ));
  assert.ok(match, `expected rendered workflow edge label for ${id}`);
  return match.slice(1).map(Number);
}

function axisOverlapLength(a1, a2, b1, b2) {
  return Math.max(0, Math.min(Math.max(a1, a2), Math.max(b1, b2))
    - Math.max(Math.min(a1, a2), Math.min(b1, b2)));
}

function workflowNodeBorderOverlap(points, rect) {
  const hits = [];
  for (let index = 0; index < points.length - 1; index += 1) {
    const [start, end] = [points[index], points[index + 1]];
    if (start[0] === end[0] && (start[0] === rect.x || start[0] === rect.x + rect.width)) {
      const length = axisOverlapLength(start[1], end[1], rect.y, rect.y + rect.height);
      if (length > 0) hits.push({ segment: index, length, start, end });
    }
    if (start[1] === end[1] && (start[1] === rect.y || start[1] === rect.y + rect.height)) {
      const length = axisOverlapLength(start[0], end[0], rect.x, rect.x + rect.width);
      if (length > 0) hits.push({ segment: index, length, start, end });
    }
  }
  return hits;
}

function assertRelationshipsAvoidAllNodeBorders(html, relationships, diagramNodes) {
  for (const [edgeIndex, edge] of relationships.entries()) {
    const edgeId = edge.id || `edge-${edgeIndex}`;
    const points = workflowEdgePoints(html, edgeId);
    for (const node of diagramNodes) {
      const hits = workflowNodeBorderOverlap(points, workflowNodeRect(html, node.id));
      assert.deepEqual(
        hits,
        [],
        `workflow edge ${edgeId} runs along node ${node.id} border: ${JSON.stringify(hits)}`,
      );
    }
  }
}

function assertWorkflowEdgesAvoidAllNodeBorders(html, doc) {
  assertRelationshipsAvoidAllNodeBorders(html, doc.edges, doc.nodes);
}

// [name, mode, mutate(doc), expectedSubstrings[]] — every mutation introduces
// exactly one layout violation. Each expected substring must appear in stderr.
const CASES = [
  // ---- workflow layout rules ----
  ['workflow: unknown lane', 'workflow', (d) => { d.nodes[0].lane = 'ghost'; }, ['unknown lane "ghost"']],
  ['workflow: node label wider than box', 'workflow',
    (d) => { d.nodes[0].label = 'An Extremely Long Node Label That Overflows'; }, ['wider than node', 'shorten the label']],
  ['workflow: node sublabel wider than its legible minimum', 'workflow',
    (d) => { d.nodes[0].sublabel = 'This supporting sentence is much too long for one workflow node'; }, ['Sublabel', 'legible', 'increase node.width']],
  ['workflow: node tag wider than its legible minimum', 'workflow',
    (d) => { d.nodes[0].tag = 'This tag is far too long to sit inside one workflow node box'; },
    ['Tag', 'legible', 'increase node.width']],
  ['workflow: viewBox width below schema min', 'workflow',
    (d) => { d.meta.viewBox = [699, 900]; }, ['700']],
  ['workflow: nodes too close in a lane', 'workflow',
    (d) => { d.nodes.push({ ...d.nodes[0], id: 'dupe', col: d.nodes[0].col }); }, ['less than 8px apart']],
  ['workflow: empty group', 'workflow',
    (d) => { d.groups = [{ id: 'empty', label: 'Empty group', lane: 'ui', fromCol: 3, toCol: 4 }]; }, ['does not contain any nodes']],
  ['workflow: mainPath missing edge', 'workflow',
    (d) => { d.mainPath = ['user', 'planner']; }, ['mainPath step "user" -> "planner" has no matching edge']],
  ['workflow: mainPath moves backward', 'workflow',
    (d) => { d.mainPath = ['external', 'trace']; }, ['moves backward from col']],
  ['workflow: phase ranges overlap', 'workflow',
    (d) => { d.phases[2].fromCol = d.phases[1].toCol; }, ['overlaps phase', 'start at col 4 or later']],

  // ---- sequence layout rules ----
  ['sequence: message references unknown participant', 'sequence',
    (d) => { d.messages[0].from = 'ghost'; }, ['unknown source "ghost"']],
  ['sequence: message y outside timeline', 'sequence',
    (d) => { d.messages[0].y = 9000; }, ['outside the readable timeline', 'keep y between']],
  ['sequence: segment to <= from', 'sequence',
    (d) => { d.segments = [{ from: 400, to: 300, label: 'bad' }]; }, ['invalid y range', 'greater than']],
  ['sequence: segment label exceeds segment frame available width', 'sequence',
    (d) => { d.segments[0].label = 'Long Segment Label '.repeat(10); },
    ['exceeds the segment frame\'s available width', 'increase meta.viewBox[0]']],
  ['sequence: participant sublabel wider than its legible minimum', 'sequence',
    (d) => { d.participants[0].sublabel = 'This supporting sentence is far too long for one sequence participant'; },
    ['Sublabel', 'legible', 'shorten the sublabel']],

  // ---- dataflow layout rules ----
  ['dataflow: flow missing label', 'dataflow',
    (d) => { delete d.flows[0].label; }, ['label']],
  ['dataflow: flow references unknown node', 'dataflow',
    (d) => { d.flows[0].to = 'ghost'; }, ['unknown target "ghost"']],
  ['dataflow: explicit via keeps every route segment orthogonal', 'dataflow',
    (d) => {
      d.flows[0].via = [[195, 140], [195, 260]];
    }, ['diagonal segment', 'align via[0]']],
  ['dataflow: node sublabel wider than its legible minimum', 'dataflow',
    (d) => { d.nodes[0].sublabel = 'This supporting sentence is far too long for one data-flow node box'; },
    ['Sublabel', 'legible', 'increase node.width']],
  ['dataflow: node tag wider than its legible minimum', 'dataflow',
    (d) => { d.nodes[0].tag = 'This tag is far too long to sit inside one data-flow node box'; },
    ['Tag', 'legible', 'increase node.width']],

  // ---- lifecycle layout rules ----
  ['lifecycle: missing reserved main lane', 'lifecycle',
    (d) => {
      d.lanes = d.lanes.map((l) => (l.id === 'main' ? { ...l, id: 'primary' } : l));
      d.states = d.states.map((s) => (s.lane === 'main' ? { ...s, lane: 'primary' } : s));
    }, ['"main"', 'reserved']],
  ['lifecycle: cross-lane state overlap', 'lifecycle',
    (d) => {
      const approval = d.states.find((s) => s.id === 'approval');
      const failed = d.states.find((s) => s.id === 'failed');
      delete failed.yOffset;
      failed.col = approval.col;
    }, ['less than 10px apart']],
  ['lifecycle: viewBox height below schema min', 'lifecycle',
    (d) => { d.meta.viewBox = [980, 565]; }, ['566']],
  ['lifecycle: state sublabel wider than its legible minimum', 'lifecycle',
    (d) => { d.states[0].sublabel = 'This supporting sentence is far too long for one lifecycle state box'; },
    ['Sublabel', 'legible', 'increase state.width']],
  ['lifecycle: state tag wider than its legible minimum', 'lifecycle',
    (d) => { d.states[0].tag = 'This tag is far too long to sit inside one lifecycle state box'; },
    ['Tag', 'legible', 'increase state.width']],

  // ---- architecture layout rules ----
  ['architecture: components overlap', 'architecture',
    (d) => { d.components[1].pos = [...d.components[0].pos]; }, ['less than 8px apart']],
  ['architecture: connection references unknown component', 'architecture',
    (d) => { d.connections[0].to = 'ghost'; }, ['unknown target "ghost"']],
  ['architecture: boundary wraps unknown component', 'architecture',
    (d) => { d.boundaries[0].wraps.push('ghost'); }, ['wraps unknown component "ghost"']],
  ['architecture: boundary title must fit inside its own frame', 'architecture',
    (d) => {
      d.meta.quality_profile = 'standard';
      d.meta.viewBox = [500, 400];
      d.components = [{
        id: 'narrow',
        type: 'backend',
        label: 'Narrow',
        pos: [330, 100],
        size: [128, 60],
      }];
      delete d.meta.views;
      d.connections = [];
      d.cards = [];
      d.boundaries = [{
        kind: 'region',
        label: 'A boundary title that cannot fit its narrow authored frame',
        wraps: ['narrow'],
        pad: 0,
      }];
    }, ['Boundary label', 'fit', 'shorten']],
  ['architecture: boundary title must stay inside the authored viewBox', 'architecture',
    (d) => {
      d.meta.quality_profile = 'standard';
      d.meta.viewBox = [500, 400];
      d.components = [{
        id: 'near-top',
        type: 'backend',
        label: 'Near top',
        pos: [120, 8],
        size: [128, 60],
      }];
      delete d.meta.views;
      d.connections = [];
      d.cards = [];
      d.boundaries = [{
        kind: 'region',
        label: 'Viewport title',
        wraps: ['near-top'],
        pad: 0,
      }];
    }, ['Boundary label', 'outside the viewBox', 'move wrapped components']],
  ['architecture: label wider than component', 'architecture',
    (d) => { d.components[0].label = 'An Extremely Long Component Label Overflow'; }, ['wider than component', 'shorten the label']],
  ['architecture: component sublabel wider than its legible minimum', 'architecture',
    (d) => { d.components[0].sublabel = 'This supporting sentence is far too long for one architecture component'; },
    ['Sublabel', 'legible', 'widen size']],
  ['architecture: component tag wider than its legible minimum', 'architecture',
    (d) => { d.components[0].tag = 'This tag is far too long to sit inside one architecture component box'; },
    ['Tag', 'legible', 'widen size']],
  ['architecture: component overlap suggests fix', 'architecture',
    (d) => { d.components[1].pos = [...d.components[0].pos]; }, ['Suggested fix', 'move "']],

];

for (const [name, mode, mutate, expected] of CASES) {
  test(name, () => {
    const doc = load(mode);
    mutate(doc);
    const { code, stderr } = render(mode, doc);
    assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
    assert.doesNotMatch(stderr, /TypeError|is not a function|Cannot read/, `crashed instead of reporting:\n${stderr}`);
    for (const sub of expected) {
      assert.ok(stderr.includes(sub), `expected "${sub}" in stderr:\n${stderr}`);
    }
  });
}

test('architecture: ordinary boundaries may express orthogonal overlapping memberships', () => {
  const d = load('architecture');
  d.boundaries[1].wraps.push('auth');
  const { code, stderr, outPath } = render('architecture', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  assert.equal(rectanglesOverlap(boundaryFrameRect(html, 0), boundaryFrameRect(html, 1)), true);
  const masks = boundaryTitleMasks(html);
  assert.equal(masks.length, 2);
  assert.equal(rectanglesOverlap(masks[0], masks[1]), false);
});

test('architecture: profile-less v1 keeps rendering when a boundary title cannot meet strict composition', () => {
  const d = load('architecture');
  delete d.meta.quality_profile;
  d.meta.viewBox = [500, 400];
  d.components = [{
    id: 'narrow',
    type: 'backend',
    label: 'Narrow',
    pos: [330, 100],
    size: [128, 60],
  }];
  delete d.meta.views;
  d.connections = [];
  d.cards = [];
  d.boundaries = [{
    kind: 'region',
    label: 'A boundary title that cannot fit its narrow authored frame',
    wraps: ['narrow'],
    pad: 0,
  }];

  const { code, stderr } = render('architecture', d);
  assert.equal(code, 0, stderr);
});

test('architecture: profile-less v1 keeps legacy boundary geometry at the top edge', () => {
  const d = load('architecture');
  delete d.meta.quality_profile;
  d.meta.viewBox = [500, 400];
  d.components = [{
    id: 'near-top',
    type: 'backend',
    label: 'Near top',
    pos: [120, 22],
    size: [128, 60],
  }];
  delete d.meta.views;
  d.connections = [];
  d.cards = [];
  d.boundaries = [{
    kind: 'region',
    label: 'Legacy top edge',
    wraps: ['near-top'],
    pad: 0,
  }];

  const { code, stderr } = render('architecture', d);
  assert.equal(code, 0, stderr);
});

test('architecture: deployment ownership requires nested membership geometry to agree', () => {
  const d = JSON.parse(fs.readFileSync(
    path.join(skillRoot, 'examples/production-deployment.architecture.json'),
    'utf8',
  ));
  d.boundaries.find((boundary) => boundary.label === 'private application network').pad = 260;
  const { code, stderr } = render('architecture', d);
  assert.notEqual(code, 0);
  assert.match(stderr, /final frames partially overlap/);
  assert.match(stderr, /adjust wraps, pad, or component positions/);
});

test('architecture: boundary labels reserve readable space above wrapped components', () => {
  const d = load('architecture');
  d.boundaries = [{
    kind: 'security-group',
    label: 'Tool effects and permissions',
    wraps: ['lb', 'api'],
    pad: 14,
  }];

  const { code, stderr, outPath } = render('architecture', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  const label = html.match(
    /<text data-boundary-label="" x="[^"]+" y="([^"]+)" class="t-security" font-size="[^"]+" font-weight="600">Tool effects and permissions<\/text>/,
  );
  assert.ok(label, 'expected the security boundary label');
  const labelBaseline = Number(label[1]);
  const firstWrappedNodeY = Math.min(
    workflowNodeRect(html, 'lb').y,
    workflowNodeRect(html, 'api').y,
  );
  assert.ok(
    labelBaseline <= firstWrappedNodeY - 4,
    `expected the boundary label baseline (${labelBaseline}) to clear the first wrapped node (${firstWrappedNodeY})`,
  );
});

test('architecture: auto viewBox keeps expanded boundary titles readable at desktop scale', () => {
  const d = load('architecture');
  delete d.meta.viewBox;
  d.meta.quality_profile = 'showcase';
  delete d.meta.views;
  d.components = [{
    id: 'node',
    type: 'backend',
    label: 'Current node',
    pos: [800, 100],
    size: [120, 60],
  }];
  d.connections = [];
  d.cards = [];
  d.boundaries = [{
    kind: 'region',
    label: 'Disaster recovery boundary Disaster recovery boundary Disaster recovery boundary',
    wraps: ['node'],
    pad: 30,
  }];

  const { code, stderr, outPath } = render('architecture', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  const viewBoxWidth = Number(html.match(/\bviewBox="0 0 ([\d.]+) [\d.]+"/)?.[1]);
  const sourceFontPx = Number(html.match(/<text data-boundary-label[^>]*font-size="([\d.]+)"/)?.[1]);
  const projectedFontPx = sourceFontPx * Math.min(1, 930 / viewBoxWidth);
  assert.ok(
    projectedFontPx >= 6,
    `expected the final ${viewBoxWidth}px viewBox to project its ${sourceFontPx}px boundary title at 6px or larger, got ${projectedFontPx}px`,
  );
});

test('architecture: boundary labels and their masks paint above relationship routes', () => {
  const d = load('architecture');
  const { code, stderr, outPath } = render('architecture', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  const lastRoute = html.lastIndexOf('data-composition-points=');
  const firstBoundaryLabel = html.indexOf('data-graph-role="structural-frame-label"');
  assert.ok(lastRoute >= 0, 'expected at least one rendered relationship route');
  assert.ok(firstBoundaryLabel >= 0, 'expected a foreground boundary label group');
  assert.ok(
    firstBoundaryLabel > lastRoute,
    'boundary labels must paint after relationship routes so routes cannot cross the title text',
  );
  assert.match(
    html.slice(firstBoundaryLabel, firstBoundaryLabel + 500),
    /data-graph-role="structural-frame-label-mask"/,
    'expected an opaque mask behind the foreground boundary title',
  );
});

test('architecture: nested boundary title rails stay inside frames and avoid labels and nodes', () => {
  const d = JSON.parse(fs.readFileSync(
    path.join(skillRoot, 'examples/production-deployment.architecture.json'),
    'utf8',
  ));
  const { code, stderr, outPath } = render('architecture', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  const masks = boundaryTitleMasks(html);
  assert.equal(masks.length, d.boundaries.length);

  for (const mask of masks) {
    assert.ok(
      rectContainsRect(boundaryFrameRect(html, mask.index), mask),
      `boundary title mask ${mask.index} must stay inside its frame: ${JSON.stringify(mask)}`,
    );
    for (const component of d.components) {
      assert.equal(
        rectanglesOverlap(mask, workflowNodeRect(html, component.id)),
        false,
        `boundary title mask ${mask.index} overlaps component ${component.id}`,
      );
    }
  }

  for (let left = 0; left < masks.length; left += 1) {
    for (let right = left + 1; right < masks.length; right += 1) {
      assert.equal(
        rectanglesOverlap(masks[left], masks[right]),
        false,
        `boundary title masks ${left} and ${right} overlap`,
      );
    }
  }
});

test('architecture: boundary title masks cannot obscure connection labels', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Boundary and connection labels', quality_profile: 'standard', viewBox: [700, 500] },
    components: [
      { id: 'scoped', type: 'backend', label: 'Scoped', pos: [250, 100], size: [120, 60] },
      { id: 'source', type: 'external', label: 'Source', pos: [50, 300], size: [100, 50] },
      { id: 'target', type: 'external', label: 'Target', pos: [500, 300], size: [100, 50] },
    ],
    boundaries: [{ kind: 'region', label: 'Runtime scope', wraps: ['scoped'] }],
    connections: [{
      from: 'source',
      to: 'target',
      label: 'Route label',
      route: 'straight',
      labelAt: [270, 88],
    }],
  };

  const { code, stderr } = render('architecture', d);
  assert.notEqual(code, 0);
  assert.match(stderr, /Boundary label "Runtime scope" overlaps connection label "Route label"/);
  assert.match(stderr, /labelAt\/labelDx\/labelDy\/labelSegment/);
});

// ---- sublabel/tag shrink-to-fit: the render half of the same rule ----
// Validation only rejects text that cannot fit even at its legible minimum.
// Everything between "fits at the preferred size" and that floor must shrink,
// not overflow — otherwise the common case still paints over its neighbours.
const SHRINK_CASES = [
  // [mode, mutate(doc), preferredFontSize, selector for the sublabel <text>]
  ['architecture', (d) => { d.components[0].sublabel = 'Browser and mobile apps'; }, 9],
  ['sequence', (d) => {
    d.meta.column_fit = 'fixed';
    d.participants[0].sublabel = 'long browser session';
  }, 7],
  ['dataflow', (d) => { d.nodes[0].sublabel = 'browser SDK and mobile SDK'; }, 7],
  ['lifecycle', (d) => { d.states[0].sublabel = 'request accepted and queued'; }, 7],
];

for (const [mode, mutate, preferred] of SHRINK_CASES) {
  test(`${mode}: an over-long sublabel shrinks to fit instead of overflowing`, () => {
    const d = load(mode);
    mutate(d);
    const { code, stderr, outPath } = render(mode, d);
    assert.equal(code, 0, stderr);
    const html = fs.readFileSync(outPath, 'utf8');
    const sub = html.match(/<text data-detail="context"[^>]*font-size="([\d.]+)"[^>]*>/);
    assert.ok(sub, 'expected a sublabel <text> in the rendered SVG');
    const fontSize = Number(sub[1]);
    assert.ok(
      fontSize < preferred,
      `expected the sublabel to shrink below the ${preferred}px preferred size, got ${fontSize}`,
    );
    assert.ok(fontSize >= 6, `expected the sublabel to stay legible, got ${fontSize}`);
  });
}

const TAG_SHRINK_CASES = [
  // [mode, collection, tag, preferredFontSize]
  ['architecture', 'components', 'owner: platform operations team', 7],
  ['dataflow', 'nodes', 'owner: analytics platform', 7],
  ['lifecycle', 'states', 'owner: platform operations pod', 7],
  ['workflow', 'nodes', 'owner: runtime execution squad A', 7],
];

for (const [mode, collection, tag, preferred] of TAG_SHRINK_CASES) {
  test(`${mode}: an over-long tag shrinks to fit instead of overflowing`, () => {
    const d = load(mode);
    d[collection][0].tag = tag;
    const { code, stderr, outPath } = render(mode, d);
    assert.equal(code, 0, stderr);
    const html = fs.readFileSync(outPath, 'utf8');
    const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const match = html.match(new RegExp(
      `<text data-detail="fine"[^>]*font-size="([\\d.]+)"[^>]*>${escapedTag}</text>`,
    ));
    assert.ok(match, `expected a tag <text> for "${tag}" in the rendered SVG`);
    const fontSize = Number(match[1]);
    assert.ok(
      fontSize < preferred,
      `expected the tag to shrink below the ${preferred}px preferred size, got ${fontSize}`,
    );
    assert.ok(fontSize >= 6, `expected the tag to stay legible, got ${fontSize}`);
  });
}

test('contract: a too-wide label is never redirected into sublabel', () => {
  // Every renderer used to advise "move detail to sublabel" for an over-long
  // label. Sublabels are measured now, so that advice would move the problem
  // rather than fix it.
  const LABELS = {
    workflow: 'An Extremely Long Node Label That Overflows',
    sequence: 'An Extremely Long Participant Label That Overflows',
    dataflow: 'An Extremely Long Node Label That Overflows',
    lifecycle: 'An Extremely Long State Label That Overflows',
    architecture: 'An Extremely Long Component Label Overflow',
  };
  const FIELD = {
    workflow: 'nodes', sequence: 'participants', dataflow: 'nodes',
    lifecycle: 'states', architecture: 'components',
  };
  for (const [mode, label] of Object.entries(LABELS)) {
    const d = load(mode);
    d[FIELD[mode]][0].label = label;
    const { code, stderr } = render(mode, d);
    assert.notEqual(code, 0, `${mode}: expected non-zero exit; stderr:\n${stderr}`);
    assert.ok(stderr.includes('wider than'), `${mode}: expected a width message:\n${stderr}`);
    assert.ok(
      !/move detail to sublabel/.test(stderr),
      `${mode}: label advice still points at the measured sublabel field:\n${stderr}`,
    );
  }
});

// ---- error-message contract: threshold + remediation, not just a path ----
test('contract: short-edge message carries both the px minimum and a fix verb', () => {
  const d = load('workflow');
  // Force a too-short labeled edge between adjacent same-lane columns.
  d.nodes.push({ id: 'a1', lane: d.nodes[0].lane, col: 0, type: 'backend', label: 'A' });
  d.nodes.push({ id: 'a2', lane: d.nodes[0].lane, col: 0, type: 'backend', label: 'B', yOffset: 30 });
  d.edges.push({ from: 'a1', to: 'a2', label: 'x', route: 'straight' });
  const { stderr } = render('workflow', d);
  // Whatever rule fires, the messages must remain actionable (threshold + verb).
  assert.match(stderr, /\d+px|at least \d+|0\.\.\d+|less than/);
});

test('contract: ajv path errors are annotated with the element id', () => {
  const d = load('workflow');
  d.nodes[3].colour = 'red'; // unknown property → ajv additionalProperties
  const { stderr } = render('workflow', d);
  // Only meaningful when ajv is installed; skip the assertion in degraded mode.
  if (!/schema validation failed/.test(stderr)) return;
  assert.match(stderr, /id\/label:/);
});

test('workflow: same-lane offset auto edge stays orthogonal', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Same-lane offset route' },
    lanes: [{ id: 'main', label: 'Main lane' }],
    nodes: [
      { id: 'left', lane: 'main', col: 1, type: 'backend', label: 'A', width: 32, height: 38, yOffset: -14 },
      { id: 'right', lane: 'main', col: 2, type: 'backend', label: 'B', width: 32, height: 38, yOffset: 14 },
    ],
    edges: [{ from: 'left', to: 'right' }],
  };
  const { code, stderr, outPath } = render('workflow', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  assert.doesNotMatch(html, /M 236 105 L 284 133/);
  assert.match(html, /M 236 105 L 260 105 L 260 133 L 284 133/);
});

test('workflow: automatic routing uses one bend and avoids every node border', () => {
  const d = JSON.parse(fs.readFileSync(
    path.join(skillRoot, 'test/fixtures/automatic-routing-node-border-clearance.workflow.json'),
    'utf8',
  ));
  const { code, stderr, outPath } = render('workflow', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  const points = workflowEdgePoints(html, 'stdin');
  const target = workflowNodeRect(html, 'team_send');
  assert.equal(points.length, 3, `expected one bend, received ${JSON.stringify(points)}`);
  assert.equal(points[0][0], points[1][0], 'edge must leave the source vertically');
  assert.ok(points[1][1] < points[0][1], 'edge must leave the source through its top side');
  assert.equal(points[1][1], points[2][1], 'edge must enter the target horizontally');
  assert.ok(points[2][0] > points[1][0], 'edge must enter the target through its left side');
  assert.equal(points[2][0], target.x, 'edge must stop at the target border');
  assert.ok(
    points[2][1] > target.y && points[2][1] < target.y + target.height,
    'edge must meet the target inside its left-side anchor range',
  );
  assertWorkflowEdgesAvoidAllNodeBorders(html, d);

  const [labelX, labelY] = workflowEdgeLabelPoint(html, 'stdin');
  assert.equal(labelX, points[0][0], 'the label should use the longer vertical segment');
  assert.ok(labelY > points[1][1] && labelY < points[0][1], 'the label should stay inside that segment');
});

test('workflow: shared automatic endpoints avoid every node border', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Shared endpoint border invariant' },
    lanes: [
      { id: 'target-lane', label: 'Target' },
      { id: 'source-lane', label: 'Sources' },
    ],
    nodes: [
      { id: 'target', lane: 'target-lane', col: 2, type: 'backend', label: 'Target' },
      { id: 'source-left', lane: 'source-lane', col: 1, type: 'backend', label: 'Left' },
      { id: 'source-right', lane: 'source-lane', col: 3, type: 'backend', label: 'Right' },
    ],
    edges: [
      { id: 'left-to-target', from: 'source-left', to: 'target' },
      { id: 'right-to-target', from: 'source-right', to: 'target' },
    ],
  };
  const { code, stderr, outPath } = render('workflow', d);
  assert.equal(code, 0, stderr);
  assertWorkflowEdgesAvoidAllNodeBorders(fs.readFileSync(outPath, 'utf8'), d);
});

test('workflow: blocked first one-bend candidate selects the clear one-bend orientation', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Blocked one-bend candidate', quality_profile: 'showcase' },
    lanes: [
      { id: 'target-lane', label: 'Target' },
      { id: 'obstacle-lane', label: 'Obstacle' },
      { id: 'source-lane', label: 'Source' },
    ],
    nodes: [
      { id: 'target', lane: 'target-lane', col: 3, type: 'backend', label: 'Target' },
      { id: 'obstacle', lane: 'obstacle-lane', col: 1, type: 'security', label: 'Obstacle' },
      { id: 'source', lane: 'source-lane', col: 1, type: 'backend', label: 'Source' },
    ],
    edges: [{ id: 'clear-corner', from: 'source', to: 'target' }],
  };
  const { code, stderr, outPath } = render('workflow', d);
  assert.equal(code, 0, stderr);
  const points = workflowEdgePoints(fs.readFileSync(outPath, 'utf8'), 'clear-corner');
  assert.equal(points.length, 3, `expected the alternate one-bend route, received ${JSON.stringify(points)}`);
  assert.equal(points[0][1], points[1][1], 'blocked vertical-first candidate must switch to horizontal-first');
  assert.ok(points[1][0] > points[0][0], 'edge must leave the source through its right side');
  assert.equal(points[1][0], points[2][0], 'edge must enter the target vertically');
  assert.ok(points[2][1] < points[1][1], 'edge must enter the target through its bottom side');
});

test('workflow: explicit drop routing remains authoritative over the one-bend preference', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Explicit route compatibility', quality_profile: 'showcase' },
    lanes: [
      { id: 'target-lane', label: 'Target' },
      { id: 'source-lane', label: 'Source' },
    ],
    nodes: [
      { id: 'target', lane: 'target-lane', col: 3, type: 'backend', label: 'Target' },
      { id: 'source', lane: 'source-lane', col: 1, type: 'backend', label: 'Source' },
    ],
    edges: [{
      id: 'authored-drop',
      from: 'source',
      to: 'target',
      route: 'drop',
      fromSide: 'top',
      toSide: 'bottom',
    }],
  };
  const { code, stderr, outPath } = render('workflow', d);
  assert.equal(code, 0, stderr);
  const points = workflowEdgePoints(fs.readFileSync(outPath, 'utf8'), 'authored-drop');
  assert.equal(points.length, 4, `explicit drop route was replaced: ${JSON.stringify(points)}`);
  assert.equal(points[0][0], points[1][0]);
  assert.equal(points[1][1], points[2][1]);
  assert.equal(points[2][0], points[3][0]);
});

test('workflow: authored endpoint sides that follow a border are rejected generically', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Authored endpoint border run' },
    lanes: [
      { id: 'target-lane', label: 'Target' },
      { id: 'source-lane', label: 'Source' },
    ],
    nodes: [
      { id: 'target', lane: 'target-lane', col: 3, type: 'backend', label: 'Target' },
      { id: 'source', lane: 'source-lane', col: 1, type: 'backend', label: 'Source' },
    ],
    edges: [{
      id: 'invalid-drop',
      from: 'source',
      to: 'target',
      route: 'drop',
      fromSide: 'right',
      toSide: 'left',
    }],
  };
  const { code, stderr } = render('workflow', d);
  assert.notEqual(code, 0);
  assert.match(stderr, /\[clean-flow\/endpoint-side-direction\] workflow edges\[0\] id "invalid-drop"/);
  assert.match(stderr, /cross node borders perpendicularly/);
});

test('dataflow: authored endpoint sides that follow a border are rejected generically', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'dataflow',
    meta: { title: 'Data-flow endpoint border run' },
    stages: [{ label: 'Source' }, { label: 'Target' }],
    nodes: [
      { id: 'source', stage: 0, row: 0, type: 'backend', label: 'Source' },
      { id: 'target', stage: 1, row: 2, type: 'database', label: 'Target' },
    ],
    flows: [{
      id: 'invalid-bottom',
      from: 'source',
      to: 'target',
      label: 'payload',
      route: 'bottom-channel',
      fromSide: 'right',
      toSide: 'left',
    }],
  };
  const { code, stderr } = render('dataflow', d);
  assert.notEqual(code, 0);
  assert.match(stderr, /\[clean-flow\/endpoint-side-direction\] dataflow flows\[0\] id "invalid-bottom"/);
  assert.match(stderr, /cross node borders perpendicularly/);
});

test('lifecycle: automatic cross-lane routes avoid every state border', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'lifecycle',
    meta: { title: 'Lifecycle endpoint border invariant' },
    lanes: [
      { id: 'main', label: 'Main' },
      { id: 'terminal', label: 'Terminal' },
    ],
    states: [
      { id: 'target', lane: 'main', col: 3, type: 'success', label: 'Target' },
      { id: 'source', lane: 'terminal', col: 1, type: 'active', label: 'Source' },
    ],
    transitions: [{ id: 'automatic-transition', from: 'source', to: 'target' }],
  };
  const { code, stderr, outPath } = render('lifecycle', d);
  assert.equal(code, 0, stderr);
  assertRelationshipsAvoidAllNodeBorders(
    fs.readFileSync(outPath, 'utf8'),
    d.transitions,
    d.states,
  );
});

function tangentViaLifecycle() {
  return {
    schema_version: 1,
    diagram_type: 'lifecycle',
    meta: { title: 'Legacy tangent via compatibility' },
    lanes: [
      { id: 'main', label: 'Main' },
      { id: 'terminal', label: 'Terminal' },
    ],
    states: [
      { id: 'source', lane: 'main', col: 1, type: 'active', label: 'Source' },
      { id: 'target', lane: 'terminal', col: 0, type: 'success', label: 'Target' },
    ],
    transitions: [{
      id: 'legacy-via',
      from: 'source',
      to: 'target',
      fromSide: 'bottom',
      toSide: 'top',
      via: [[320, 188], [320, 430], [402, 430]],
    }],
  };
}

test('lifecycle: legacy tangent via is rendered exactly instead of silently rewritten', () => {
  const d = tangentViaLifecycle();
  const { code, stderr, outPath } = render('lifecycle', d);
  assert.equal(code, 0, stderr);
  assert.deepEqual(workflowEdgePoints(fs.readFileSync(outPath, 'utf8'), 'legacy-via'), [
    [248, 188], [320, 188], [320, 430], [402, 430], [402, 450],
  ]);
});

for (const qualityProfile of ['standard', 'showcase']) {
  test(`lifecycle: ${qualityProfile} keeps an authored tangent via authoritative`, () => {
    const d = tangentViaLifecycle();
    d.meta.quality_profile = qualityProfile;
    const { code, stderr, outPath } = render('lifecycle', d);
    assert.equal(code, 0, stderr);
    assert.deepEqual(workflowEdgePoints(fs.readFileSync(outPath, 'utf8'), 'legacy-via'), [
      [248, 188], [320, 188], [320, 430], [402, 430], [402, 450],
    ]);
  });

  test(`lifecycle: public validate accepts an authoritative authored tangent via in ${qualityProfile}`, () => {
    const d = tangentViaLifecycle();
    d.meta.quality_profile = qualityProfile;
    const { code, result } = validateCli('lifecycle', d);
    assert.equal(code, 0, JSON.stringify(result, null, 2));
    assert.equal(result.ok, true);
  });
}

test('workflow: explicit labelAt remains authoritative on an automatic one-bend edge', () => {
  const d = JSON.parse(fs.readFileSync(
    path.join(skillRoot, 'test/fixtures/automatic-routing-node-border-clearance.workflow.json'),
    'utf8',
  ));
  d.edges[0].labelAt = [350, 166];
  const { code, stderr, outPath } = render('workflow', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  assert.deepEqual(workflowEdgeLabelPoint(html, 'stdin'), [350, 166]);
});

test('workflow: bounded font fitting keeps an ordinary long sublabel inside its node', () => {
  const d = load('workflow');
  d.nodes[0].width = 92;
  d.nodes[0].sublabel = 'shell / browser / MCP';
  const { code, stderr, outPath } = render('workflow', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  assert.match(html, /font-size="6\.6"[^>]*>shell \/ browser \/ MCP<\/text>/);
});

test('workflow: edge crossing a non-endpoint node is rejected', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Crossing edge route', quality_profile: 'standard' },
    lanes: [{ id: 'main', label: 'Main lane' }],
    nodes: [
      { id: 'left', lane: 'main', col: 0, type: 'backend', label: 'Left', width: 60 },
      { id: 'middle', lane: 'main', col: 2, type: 'database', label: 'Middle', width: 70 },
      { id: 'right', lane: 'main', col: 4, type: 'backend', label: 'Right', width: 60 },
    ],
    edges: [{ from: 'left', to: 'right', route: 'straight' }],
  };
  const { code, stderr } = render('workflow', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /crosses node "middle"/);
  assert.match(stderr, /fromSide\/toSide|channel|lane\/column/);
});

test('architecture: Clean Flow Gate rejects a connection through a component', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Opaque component crossing', quality_profile: 'standard' },
    components: [
      { id: 'left', type: 'frontend', label: 'Left', pos: [60, 120], size: [100, 54] },
      { id: 'middle', type: 'security', label: 'Middle', pos: [270, 120], size: [100, 54] },
      { id: 'right', type: 'backend', label: 'Right', pos: [480, 120], size: [100, 54] },
    ],
    connections: [{ id: 'direct', from: 'left', to: 'right', route: 'straight' }],
  };
  const { code, stderr } = render('architecture', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[clean-flow\/edge-through-node\] architecture connections\[0\] id "direct"/);
  assert.match(stderr, /crosses component "middle"/);
  assert.match(stderr, /segment 0 .*2px clearance/);
});

test('dataflow: showcase rejects a relationship label that hides another route', () => {
  const d = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', 'event-stream.dataflow.json'), 'utf8'));
  const approvedReplay = d.flows.find((flow) => flow.label === 'approved replay');
  delete approvedReplay.labelAt;
  delete approvedReplay.labelDx;
  delete approvedReplay.labelDy;
  delete approvedReplay.labelSegment;
  const { code, stderr } = render('dataflow', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[composition\/label-route-clearance\] showcase dataflow/);
  assert.match(stderr, /approved replay.*failure sample/);
  assert.match(stderr, /labelAt.*labelDx.*labelDy.*labelSegment/);
});

test('dataflow: validator and SVG share the 27px CJK/emoji classification mask', () => {
  const d = load('dataflow');
  const flow = d.flows[0];
  flow.label = '写入🚀';
  flow.classification = '机密🔐';
  const { code, stderr, outPath } = render('dataflow', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  const group = html.match(/<g data-detail="context"[^>]*data-edge-id="web-clickstream"[^>]*>[\s\S]*?<\/g>/)?.[0] || '';
  assert.match(group, /data-edge-label="写入🚀"/);
  assert.match(group, /<rect x="[^\"]+" y="[^\"]+" width="41\.4" height="27" rx="4" class="c-mask"\/>/);
  assert.match(group, />机密🔐<\/text>/);
});

test('architecture: showcase rejects a connection label that hides another route', () => {
  const d = load('architecture');
  d.connections[0].labelAt = [620, 330];
  const { code, stderr } = render('architecture', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[composition\/label-route-clearance\] showcase architecture/);
  assert.match(stderr, /HTTPS.*lb-to-api/);
});

test('workflow: showcase rejects an edge label that hides another route', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: {
      title: 'Workflow label-route clearance',
      quality_profile: 'showcase',
      viewBox: [720, 400],
      legend: { mode: 'hidden' },
    },
    lanes: [
      { id: 'label', label: 'Label owner' },
      { id: 'route', label: 'Other route' },
    ],
    nodes: [
      { id: 'a', lane: 'label', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'label', col: 2, type: 'backend', label: 'B' },
      { id: 'c', lane: 'route', col: 0, type: 'backend', label: 'C' },
      { id: 'd', lane: 'route', col: 2, type: 'backend', label: 'D' },
    ],
    edges: [
      { id: 'labeled-edge', from: 'a', to: 'b', label: 'plan', labelAt: [200, 243] },
      { id: 'other-route', from: 'c', to: 'd' },
    ],
  };
  const { code, stderr } = render('workflow', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[composition\/label-route-clearance\] showcase workflow/);
  assert.match(stderr, /plan.*other-route/);
});

test('lifecycle: showcase rejects a transition label that hides another route', () => {
  const d = load('lifecycle');
  d.transitions[0].label = 'approval gate';
  d.transitions[0].labelAt = [556, 250];
  const { code, stderr } = render('lifecycle', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[composition\/label-route-clearance\] showcase lifecycle/);
  assert.match(stderr, /approval gate.*review-blocked/);
});

test('sequence: showcase rejects a message label that hides an adjacent route', () => {
  const d = load('sequence');
  d.messages = d.messages.slice(0, 2);
  d.messages[0].label = 'customer authorization context';
  d.messages[0].y = 250;
  d.messages[1].label = 'ok';
  d.messages[1].y = 245;
  d.segments = [];
  d.activations = [];
  const { code, stderr } = render('sequence', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[composition\/label-route-clearance\] showcase sequence/);
  assert.match(stderr, /customer authorization context.*dashboard-request/);
});

function autoRoutePassThroughDocument(connection) {
  return {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Auto-route pass-through regression' },
    components: [
      { id: 'api', type: 'backend', label: 'API', pos: [400, 280], size: [160, 76] },
      { id: 'cache', type: 'database', label: 'Cache', pos: [645, 130], size: [130, 60] },
      { id: 'queue', type: 'cloud', label: 'Queue', pos: [880, 130] },
    ],
    connections: [connection],
  };
}

test('architecture: default auto route selects a safe orthogonal candidate around an unrelated component', () => {
  const d = autoRoutePassThroughDocument({ from: 'api', to: 'queue', variant: 'dashed' });
  const { code, stderr, outPath } = render('architecture', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  assert.match(html, /data-composition-points="560,318;856,318;856,160;880,160"/);
});

test('architecture: auto route enters explicit top and bottom ports perpendicularly', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Endpoint direction regression' },
    components: [
      { id: 'cli-agents', type: 'external', label: 'CLI Agents', pos: [300, 100], size: [100, 60] },
      { id: 'tasks-watch', type: 'backend', label: 'Tasks Watcher', pos: [100, 240], size: [100, 60] },
    ],
    connections: [
      { id: 'tasks-file', from: 'cli-agents', to: 'tasks-watch', variant: 'dashed', fromSide: 'bottom', toSide: 'top' },
    ],
  };
  const { code, stderr, outPath } = render('architecture', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  assert.match(html, /data-composition-points="350,160;350,200;150,200;150,240"/);
});

test('architecture: auto route preserves inferred side normals when the primary dogleg is blocked', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Inferred endpoint direction regression' },
    components: [
      { id: 'workspace', type: 'frontend', label: 'Workspace UI', pos: [40, 300], size: [120, 60] },
      { id: 'runtime-server', type: 'backend', label: 'Runtime Server', pos: [220, 300], size: [120, 60] },
      { id: 'runtime-store', type: 'backend', label: 'Runtime Store', pos: [400, 300], size: [120, 60] },
      { id: 'stream-hub', type: 'messagebus', label: 'Terminal Stream Hub', pos: [700, 100], size: [120, 60] },
    ],
    connections: [
      { id: 'terminal-return', from: 'stream-hub', to: 'workspace' },
    ],
  };
  const { code, stderr, outPath } = render('architecture', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  assert.match(html, /data-composition-points="700,130;184,130;184,330;160,330"/);
  assert.doesNotMatch(html, /data-composition-points="700,130;700,230;160,230;160,330"/);
});

test('architecture: explicit via cannot run tangentially into an authored top port', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Tangent endpoint regression' },
    components: [
      { id: 'cli-agents', type: 'external', label: 'CLI Agents', pos: [300, 100], size: [100, 60] },
      { id: 'tasks-watch', type: 'backend', label: 'Tasks Watcher', pos: [100, 240], size: [100, 60] },
    ],
    connections: [
      {
        id: 'tasks-file',
        from: 'cli-agents',
        to: 'tasks-watch',
        variant: 'dashed',
        fromSide: 'bottom',
        toSide: 'top',
        via: [[350, 200], [100, 200], [100, 240]],
      },
    ],
  };
  const { code, stderr } = render('architecture', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[clean-flow\/endpoint-side-direction\] architecture connections\[0\] id "tasks-file"/);
  assert.match(stderr, /final segment 3 \[100, 240\] -> \[150, 240\]/);
  assert.match(stderr, /toSide "top".*vertical downward from above/);
});

test('architecture: explicit orthogonal route remains authoritative when it crosses a component', () => {
  const d = autoRoutePassThroughDocument({
    from: 'api',
    to: 'queue',
    variant: 'dashed',
    route: 'orthogonal-h',
  });
  const { code, stderr } = render('architecture', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /connections\[0\] "api" -> "queue" crosses component "cache"/);
});

test('architecture: auto route still fails closed when doglegs and side-aware bridges are blocked', () => {
  const d = autoRoutePassThroughDocument({ from: 'api', to: 'queue', variant: 'dashed' });
  d.components.push({ id: 'guard', type: 'security', label: 'Guard', pos: [825, 215], size: [70, 50] });
  const { code, stderr } = render('architecture', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /connections\[0\] "api" -> "queue" crosses component "cache"/);
});

test('architecture: explicit waypoints around an obstacle remain valid by default', () => {
  const d = autoRoutePassThroughDocument({
    from: 'api',
    to: 'queue',
    variant: 'dashed',
    fromSide: 'right',
    toSide: 'top',
    via: [[620, 318], [620, 100], [940, 100]],
  });
  const { code, stderr } = render('architecture', d);
  assert.equal(code, 0, stderr);
});

test('architecture: showcase rejects an unrelated proper edge crossing', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Showcase crossing', quality_profile: 'showcase' },
    components: [
      { id: 'a', type: 'frontend', label: 'A', pos: [60, 80], size: [60, 40] },
      { id: 'b', type: 'backend', label: 'B', pos: [360, 260], size: [60, 40] },
      { id: 'c', type: 'database', label: 'C', pos: [60, 260], size: [60, 40] },
      { id: 'd', type: 'external', label: 'D', pos: [360, 80], size: [60, 40] },
    ],
    connections: [
      { id: 'down-right', from: 'a', to: 'b', route: 'orthogonal-h' },
      { id: 'up-right', from: 'c', to: 'd', route: 'orthogonal-v', fromSide: 'top', toSide: 'bottom' },
    ],
  };
  const { code, stderr } = render('architecture', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[composition\/proper-crossing\] showcase architecture/);
  assert.match(stderr, /connections\[0\] id "down-right"/);
  assert.match(stderr, /connections\[1\] id "up-right"/);
  assert.match(stderr, /at \[240, 190\]/);
  assert.match(stderr, /segments 1 and 1/);
  assert.match(stderr, /route\/via|fromSide\/toSide/);
});

test('architecture: showcase preserves a straight-through explicit waypoint as an authored touch', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: {
      title: 'Forward-collinear waypoint compatibility',
      quality_profile: 'showcase',
      viewBox: [600, 320],
      legend: { mode: 'hidden' },
    },
    components: [
      { id: 'a', type: 'backend', label: 'A', pos: [50, 100], size: [80, 60] },
      { id: 'b', type: 'backend', label: 'B', pos: [450, 100], size: [80, 60] },
      { id: 'c', type: 'backend', label: 'C', pos: [260, 0], size: [80, 60] },
      { id: 'd', type: 'backend', label: 'D', pos: [260, 200], size: [80, 60] },
    ],
    connections: [
      { id: 'horizontal', from: 'a', to: 'b', via: [[300, 130]] },
      { id: 'vertical', from: 'c', to: 'd' },
    ],
  };

  const { code, stderr } = render('architecture', d);
  assert.equal(code, 0, stderr);
});

test('architecture: standard keeps the same proper crossing renderable', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Standard crossing', quality_profile: 'standard' },
    components: [
      { id: 'a', type: 'frontend', label: 'A', pos: [60, 80], size: [60, 40] },
      { id: 'b', type: 'backend', label: 'B', pos: [360, 260], size: [60, 40] },
      { id: 'c', type: 'database', label: 'C', pos: [60, 260], size: [60, 40] },
      { id: 'd', type: 'external', label: 'D', pos: [360, 80], size: [60, 40] },
    ],
    connections: [
      { from: 'a', to: 'b', route: 'orthogonal-h' },
      { from: 'c', to: 'd', route: 'orthogonal-v', fromSide: 'top', toSide: 'bottom' },
    ],
  };
  const { code, stderr } = render('architecture', d);
  assert.equal(code, 0, stderr);
});

function ambiguousCorridorDocument(profile) {
  return {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Ambiguous corridor', quality_profile: profile },
    components: [
      { id: 'a', type: 'frontend', label: 'A', pos: [40, 60], size: [60, 40] },
      { id: 'b', type: 'backend', label: 'B', pos: [400, 60], size: [60, 40] },
      { id: 'c', type: 'database', label: 'C', pos: [120, 220], size: [60, 40] },
      { id: 'd', type: 'external', label: 'D', pos: [480, 220], size: [60, 40] },
    ],
    connections: [
      { id: 'first', from: 'a', to: 'b', fromSide: 'right', toSide: 'left', route: 'straight' },
      { id: 'second', from: 'c', to: 'd', fromSide: 'top', toSide: 'top', via: [[150, 80], [390, 80], [390, 180], [510, 180]] },
    ],
  };
}

test('architecture: showcase rejects an unrelated shared route corridor', () => {
  const { code, stderr } = render('architecture', ambiguousCorridorDocument('showcase'));
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[composition\/ambiguous-corridor\] showcase architecture/);
  assert.match(stderr, /connections\[0\] id "first" "a" -> "b" shares a 240px corridor with connections\[1\] id "second" "c" -> "d"/);
  assert.match(stderr, /\[150, 80\] -> \[390, 80\]/);
  assert.match(stderr, /do not visually merge/);
});

test('architecture: standard keeps an ambiguous corridor renderable for repair', () => {
  const { code, stderr } = render('architecture', ambiguousCorridorDocument('standard'));
  assert.equal(code, 0, stderr);
});

test('architecture: route rhythm warns in standard and blocks a showcase micro segment', () => {
  const base = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Readable turn rhythm' },
    components: [
      { id: 'a', type: 'frontend', label: 'A', pos: [60, 80], size: [60, 40] },
      { id: 'b', type: 'backend', label: 'B', pos: [360, 80], size: [60, 40] },
    ],
    connections: [
      { id: 'tight', from: 'a', to: 'b', fromSide: 'right', toSide: 'bottom', via: [[125, 100], [125, 160], [390, 160]] },
    ],
  };
  const standard = structuredClone(base);
  standard.meta.quality_profile = 'standard';
  assert.equal(render('architecture', standard).code, 0);

  const showcase = structuredClone(base);
  showcase.meta.quality_profile = 'showcase';
  const { code, stderr } = render('architecture', showcase);
  assert.notEqual(code, 0);
  assert.match(stderr, /\[composition\/micro-segment\] showcase architecture connections\[0\] id "tight"/);
  assert.match(stderr, /5px source-stub segment 0/);
  assert.match(stderr, /wider corridor|move the component/);
});

test('architecture: container border run is blocking in standard and showcase', () => {
  for (const profile of ['standard', 'showcase']) {
    const d = load('architecture');
    d.meta.quality_profile = profile;
    d.connections.find((connection) => connection.id === 'jwt-verification').via = [[620, 142], [620, 270], [735, 270]];
    const { code, stderr } = render('architecture', d);
    assert.notEqual(code, 0, `expected ${profile} to reject a border run`);
    assert.match(stderr, /\[composition\/container-border-run\] architecture connections\[1\] id "jwt-verification"/);
    assert.match(stderr, /security-group "sg-api :443\/:8000" top border/);
  }
});

test('dataflow: stage border run is blocking and the inter-stage gutter passes', () => {
  const bad = load('dataflow');
  bad.flows.find((flow) => flow.id === 'web-clickstream').via = [[184, 157], [184, 271]];
  const failed = render('dataflow', bad);
  assert.notEqual(failed.code, 0);
  assert.match(failed.stderr, /\[composition\/container-border-run\] dataflow flows\[0\] id "web-clickstream"/);
  assert.match(failed.stderr, /stage "Sources" right border for 114px/);

  const clean = load('dataflow');
  const passed = render('dataflow', clean);
  assert.equal(passed.code, 0, passed.stderr);
});

test('sequence: a message cannot masquerade as a time-segment border', () => {
  const d = load('sequence');
  d.messages.find((message) => message.id === 'cache-read').y = d.segments[1].from;
  const { code, stderr } = render('sequence', d);
  assert.notEqual(code, 0);
  assert.match(stderr, /\[composition\/container-border-run\] sequence messages\[4\] id "cache-read"/);
  assert.match(stderr, /segment "Fallback" top border/);
});

test('dataflow: Clean Flow Gate rejects a flow through an unrelated node', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'dataflow',
    meta: { title: 'Opaque data node crossing', quality_profile: 'standard' },
    stages: [{ label: 'Source' }, { label: 'Middle' }, { label: 'Sink' }],
    nodes: [
      { id: 'left', type: 'frontend', label: 'Left', stage: 0, row: 1 },
      { id: 'middle', type: 'security', label: 'Middle', stage: 1, row: 1 },
      { id: 'right', type: 'database', label: 'Right', stage: 2, row: 1 },
    ],
    flows: [{ id: 'direct', from: 'left', to: 'right', label: 'payload', route: 'straight', labelAt: [315, 190] }],
  };
  const { code, stderr } = render('dataflow', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[clean-flow\/edge-through-node\] dataflow flows\[0\] id "direct"/);
  assert.match(stderr, /crosses node "middle"/);
  assert.match(stderr, /stage\/row/);
});

test('lifecycle: Clean Flow Gate rejects a transition through an unrelated state', () => {
  const d = {
    schema_version: 1,
    diagram_type: 'lifecycle',
    meta: { title: 'Opaque state crossing', quality_profile: 'standard' },
    lanes: [{ id: 'main', label: 'Main' }],
    states: [
      { id: 'left', type: 'start', label: 'Left', lane: 'main', col: 0 },
      { id: 'middle', type: 'waiting', label: 'Middle', lane: 'main', col: 2 },
      { id: 'right', type: 'success', label: 'Right', lane: 'main', col: 4 },
    ],
    transitions: [{ id: 'direct', from: 'left', to: 'right', route: 'straight' }],
  };
  const { code, stderr } = render('lifecycle', d);
  assert.notEqual(code, 0, `expected non-zero exit; stderr:\n${stderr}`);
  assert.match(stderr, /\[clean-flow\/edge-through-node\] lifecycle transitions\[0\] id "direct"/);
  assert.match(stderr, /crosses state "middle"/);
  assert.match(stderr, /col\/yOffset/);
});

test('sequence: lifelines and activation bars remain intentional pass-through geometry', () => {
  const d = load('sequence');
  const { code, stderr } = render('sequence', d);
  assert.equal(code, 0, stderr);
  assert.doesNotMatch(stderr, /Clean Flow Gate/);
});

test('sequence: segment titles render as foreground badges above their borders', () => {
  const d = load('sequence');
  const { code, stderr, outPath } = render('sequence', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  const firstSegment = d.segments[0];
  const segmentLabelsAt = html.indexOf('<!-- Segment Labels -->');
  const activationsAt = html.indexOf('<!-- Activations -->');
  const messagesAt = html.indexOf('<!-- Messages -->');

  assert.ok(segmentLabelsAt > activationsAt, 'segment labels should stay above lifelines, messages, and activations');
  assert.ok(messagesAt > activationsAt, 'message arrows and labels should stay above activation bars');
  assert.match(html, new RegExp(`data-graph-role="segment-label"[^>]*data-segment-id="0"`));
  assert.match(html, new RegExp(`<text x="62" y="${firstSegment.from - 9}"[^>]*>${firstSegment.label}</text>`));
});

test('sequence: segment title badge clears a nearby first message label', () => {
  const d = load('sequence');
  const firstSegment = d.segments[0];
  const firstMessage = d.messages[0];
  firstSegment.from = 180;
  firstMessage.y = firstSegment.from + 5;

  const { code, stderr, outPath } = render('sequence', d);
  assert.equal(code, 0, stderr);
  const html = fs.readFileSync(outPath, 'utf8');
  const segment = html.match(
    /<g data-graph-role="segment-label" data-segment-id="0">\s*<rect x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"/,
  );
  const message = html.match(new RegExp(
    `<g [^>]*data-edge-id="${firstMessage.id}"[\\s\\S]*?<rect x="([^"]+)" y="([^"]+)" width="([^"]+)" height="([^"]+)"`,
  ));
  assert.ok(segment, 'expected the first segment badge rectangle');
  assert.ok(message, 'expected the first message label rectangle');

  const [segmentX, segmentY, segmentW, segmentH] = segment.slice(1).map(Number);
  const [messageX, messageY, messageW, messageH] = message.slice(1).map(Number);
  const overlaps = segmentX < messageX + messageW
    && segmentX + segmentW > messageX
    && segmentY < messageY + messageH
    && segmentY + segmentH > messageY;
  assert.equal(overlaps, false, 'segment title badge must not cover the first message label');
});

test('sequence: segment label exceeding segment frame available width fails layout validation with remediation', () => {
  const d = load('sequence');
  d.meta.viewBox[0] = 820;
  d.meta.column_fit = 'fixed';
  d.segments[0].label = 'Phase 1: Dual-Certificate mTLS Handshake & Distributed Token Verification Protocol Negotiation'.repeat(2);

  const { code, stderr } = render('sequence', d);
  assert.equal(code, 1);
  assert.match(stderr, /Segment "Phase 1: Dual-Certificate mTLS Handshake & Distributed Token Verification Protocol NegotiationPhase 1: Dual-Certificate mTLS Handshake & Distributed Token Verification Protocol Negotiation" label \(~992px\) exceeds the segment frame's available width \(716px\) — shorten the label or increase meta\.viewBox\[0\] to at least 1096\./);

  // Remediating by increasing viewBox[0] to the suggested minimum allows it to pass cleanly
  d.meta.viewBox[0] = 1096;
  const fixed = render('sequence', d);
  assert.equal(fixed.code, 0, fixed.stderr);
});

test('sequence: segment label boundary containment within canvas vs segment frame and exact-fit', () => {
  // Available width in this fixed-column boundary scenario (viewBox[0] = 820):
  // frame right edge = 820 - 48 = 772
  // labelBox.x = 56
  // availableWidth = 772 - 56 = 716px
  // 135 ASCII units -> labelW = 135 * 5.2 + 14 = 716px -> label right edge = 56 + 716 = 772px (exact fit)
  // 136 ASCII units -> labelW = 136 * 5.2 + 14 = 721.2px -> label right edge = 56 + 721.2 = 777.2px
  // (777.2px <= 820 canvas width, but > 772 segment frame edge)

  // 1. Fits within canvas (777.2 <= 820) but exceeds segment frame (777.2 > 772)
  const dExceed = load('sequence');
  dExceed.meta.viewBox[0] = 820;
  dExceed.meta.column_fit = 'fixed';
  dExceed.segments[0].label = 'A'.repeat(136);
  const resExceed = render('sequence', dExceed);
  assert.equal(resExceed.code, 1, 'label exceeding segment frame must fail even if within canvas');
  assert.match(resExceed.stderr, /label \(~721px\) exceeds the segment frame's available width \(716px\) — shorten the label or increase meta\.viewBox\[0\] to at least 826\./);

  // 2. Exact-fit case at the frame's right edge (56 + 716 = 772 === 820 - 48)
  const dExact = load('sequence');
  dExact.meta.viewBox[0] = 820;
  dExact.meta.column_fit = 'fixed';
  dExact.segments[0].label = 'A'.repeat(135);
  const resExact = render('sequence', dExact);
  assert.equal(resExact.code, 0, `exact-fit label at segment frame boundary must pass cleanly: ${resExact.stderr}`);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/legend-contract.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const cli = path.join(skillRoot, 'bin/archify.mjs');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-legend-contract-'));
let sequence = 0;

const FIXTURES = {
  architecture: {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Legend architecture', viewBox: [720, 420] },
    components: [
      { id: 'ui', type: 'frontend', label: 'UI', pos: [60, 90] },
      { id: 'store', type: 'database', label: 'Store', pos: [300, 90] },
    ],
    connections: [],
  },
  workflow: {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Legend workflow', viewBox: [720, 360] },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'ui', lane: 'main', col: 0, type: 'frontend', label: 'UI' },
      { id: 'agent', lane: 'main', col: 2, type: 'backend', label: 'Agent' },
    ],
    edges: [],
  },
  sequence: {
    schema_version: 1,
    diagram_type: 'sequence',
    meta: { title: 'Legend sequence', viewBox: [720, 560] },
    participants: [
      { id: 'client', type: 'frontend', label: 'Client' },
      { id: 'api', type: 'backend', label: 'API' },
    ],
    messages: [
      { from: 'client', to: 'api', y: 220, label: 'request', variant: 'emphasis' },
      { from: 'api', to: 'client', y: 280, label: 'response', variant: 'return' },
    ],
  },
  dataflow: {
    schema_version: 1,
    diagram_type: 'dataflow',
    meta: { title: 'Default Flow Only' },
    stages: [{ label: 'Input' }, { label: 'Output' }],
    nodes: [
      { id: 'input', type: 'backend', label: 'Input', stage: 0, row: 0 },
      { id: 'output', type: 'backend', label: 'Output', stage: 1, row: 0 },
    ],
    flows: [
      { from: 'input', to: 'output', label: 'request', route: 'straight' },
    ],
  },
  lifecycle: {
    schema_version: 1,
    diagram_type: 'lifecycle',
    meta: { title: 'No Waiting or Failure', viewBox: [720, 566] },
    lanes: [{ id: 'main', label: 'Lifecycle' }],
    states: [
      { id: 'started', type: 'start', label: 'Started', lane: 'main', col: 0 },
      { id: 'running', type: 'active', label: 'Running', lane: 'main', col: 1 },
      { id: 'completed', type: 'success', label: 'Completed', lane: 'main', col: 2 },
    ],
    transitions: [
      { from: 'started', to: 'running' },
      { from: 'running', to: 'completed' },
    ],
  },
};

const CATALOGS = {
  architecture: ['frontend', 'backend', 'database', 'cloud', 'security', 'messagebus', 'external'],
  workflow: ['frontend', 'backend', 'security', 'messagebus', 'database', 'cloud', 'external'],
  sequence: ['emphasis', 'return', 'security', 'dashed', 'default'],
  dataflow: ['emphasis', 'security', 'dashed', 'database', 'default'],
  lifecycle: ['start', 'active', 'waiting', 'decision', 'success', 'failure', 'neutral', 'external'],
};

const AUTO_KINDS = {
  architecture: ['frontend', 'database'],
  workflow: ['frontend', 'backend'],
  sequence: ['emphasis', 'return'],
  dataflow: ['default'],
  lifecycle: ['start', 'active', 'success'],
};

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

function withLegend(type, legend) {
  const doc = clone(FIXTURES[type]);
  if (legend !== undefined) doc.meta.legend = legend;
  return doc;
}

function run(type, doc, command = 'render') {
  const id = sequence++;
  const input = path.join(tmp, `${id}-${type}.json`);
  const output = path.join(tmp, `${id}-${type}.html`);
  fs.writeFileSync(input, JSON.stringify(doc));
  const args = command === 'render'
    ? [cli, 'render', type, input, output]
    : [cli, 'validate', type, input, '--json'];
  const result = spawnSync(process.execPath, args, { cwd: skillRoot, encoding: 'utf8' });
  return {
    ...result,
    html: result.status === 0 && command === 'render' ? fs.readFileSync(output, 'utf8') : '',
  };
}

function render(type, doc) {
  const result = run(type, doc);
  assert.equal(result.status, 0, result.stderr || result.stdout);
  return result.html;
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

function attrValues(source, attribute) {
  const pattern = new RegExp(`${attribute}="([^"]+)"`, 'g');
  return [...source.matchAll(pattern)].map((match) => match[1]);
}

function legendKinds(html) {
  return attrValues(canonicalSvg(html), 'data-legend-semantic-kind');
}

function validateFailure(type, doc) {
  const result = run(type, doc, 'validate');
  assert.notEqual(result.status, 0, `expected ${type} validation to fail`);
  const payload = JSON.parse(result.stdout);
  assert.equal(payload.ok, false);
  return payload;
}

test('public typed renderers default to auto and expose only authored semantic kinds', () => {
  for (const type of Object.keys(FIXTURES)) {
    const html = render(type, FIXTURES[type]);
    assert.deepEqual(legendKinds(html), AUTO_KINDS[type], type);
  }
});

test('Dataflow database node facts are interactive while flow variants stay visual-only', () => {
  const withDatabase = clone(FIXTURES.dataflow);
  withDatabase.nodes[1].type = 'database';
  const databaseSvg = canonicalSvg(render('dataflow', withDatabase));
  assert.deepEqual(attrValues(databaseSvg, 'data-legend-semantic-kind'), ['database', 'default']);
  assert.deepEqual(attrValues(databaseSvg, 'data-legend-kind'), ['database']);
  assert.equal((databaseSvg.match(/data-legend-bridge=""/g) || []).length, 1);
  assert.ok(attrValues(databaseSvg, 'data-node-kind').includes('database'));

  const forcedWithoutFact = canonicalSvg(render('dataflow', withLegend('dataflow', {
    entries: { database: { visible: true } },
  })));
  assert.deepEqual(attrValues(forcedWithoutFact, 'data-legend-semantic-kind'), ['database', 'default']);
  assert.deepEqual(attrValues(forcedWithoutFact, 'data-legend-kind'), []);
  assert.doesNotMatch(forcedWithoutFact, /data-legend-bridge/);
});

test('Issue #52 dataflow and lifecycle reproductions publish truthful default legends', () => {
  const dataflow = canonicalSvg(render('dataflow', FIXTURES.dataflow));
  assert.deepEqual(attrValues(dataflow, 'data-legend-semantic-kind'), ['default']);
  assert.doesNotMatch(dataflow, /policy \/ PII|async batch|primary data|data store/i);
  assert.doesNotMatch(dataflow, /data-legend-bridge|data-legend-kind=/);

  const lifecycle = canonicalSvg(render('lifecycle', FIXTURES.lifecycle));
  const lifecycleLegend = lifecycle.slice(lifecycle.indexOf('<!-- Legend -->'));
  assert.deepEqual(attrValues(lifecycleLegend, 'data-legend-semantic-kind'), ['start', 'active', 'success']);
  assert.doesNotMatch(lifecycleLegend, /waiting|failure \/ exit/i);
  assert.deepEqual(attrValues(lifecycleLegend, 'data-legend-kind'), ['start', 'active', 'success']);
});

test('all mode follows each renderer-owned stable catalog order', () => {
  for (const type of Object.keys(FIXTURES)) {
    const html = render(type, withLegend(type, { mode: 'all' }));
    assert.deepEqual(legendKinds(html), CATALOGS[type], type);
  }
});

test('hidden mode removes the complete legend and overrides visible true', () => {
  for (const type of Object.keys(FIXTURES)) {
    const forcedKind = CATALOGS[type].at(-1);
    const html = render(type, withLegend(type, {
      mode: 'hidden',
      entries: { [forcedKind]: { label: 'Must stay hidden', visible: true } },
    }));
    const svg = canonicalSvg(html);
    assert.doesNotMatch(svg, />Legend</);
    assert.doesNotMatch(svg, /data-legend(?:-semantic-kind|-kind|-bridge)?=/);
    assert.doesNotMatch(svg, /Must stay hidden/);
  }
});

test('visibility overrides apply after auto/all and empty legends leave no chrome', () => {
  const cases = {
    architecture: { hidden: 'frontend', forced: 'external' },
    workflow: { hidden: 'frontend', forced: 'security' },
    sequence: { hidden: 'emphasis', forced: 'dashed' },
    dataflow: { hidden: 'default', forced: 'database' },
    lifecycle: { hidden: 'active', forced: 'waiting' },
  };
  for (const [type, kinds] of Object.entries(cases)) {
    const html = render(type, withLegend(type, {
      entries: {
        [kinds.hidden]: { visible: false },
        [kinds.forced]: { visible: true },
      },
    }));
    const expected = AUTO_KINDS[type]
      .filter((kind) => kind !== kinds.hidden)
      .concat(kinds.forced)
      .sort((left, right) => CATALOGS[type].indexOf(left) - CATALOGS[type].indexOf(right));
    assert.deepEqual(legendKinds(html), expected, type);
  }

  const allMinusSecurity = render('sequence', withLegend('sequence', {
    mode: 'all',
    entries: { security: { visible: false } },
  }));
  assert.deepEqual(
    legendKinds(allMinusSecurity),
    CATALOGS.sequence.filter((kind) => kind !== 'security'),
  );

  for (const type of Object.keys(FIXTURES)) {
    const entries = Object.fromEntries(AUTO_KINDS[type].map((kind) => [kind, { visible: false }]));
    const empty = canonicalSvg(render(type, withLegend(type, { entries })));
    assert.doesNotMatch(empty, />Legend</, type);
    assert.doesNotMatch(empty, /data-legend/, type);
  }
});

test('label overrides round-trip through all five public renderers', () => {
  for (const type of Object.keys(FIXTURES)) {
    const kind = AUTO_KINDS[type][0];
    const label = `Custom ${type} label`;
    const svg = canonicalSvg(render(type, withLegend(type, {
      entries: { [kind]: { label } },
    })));
    assert.match(svg, new RegExp(`data-legend-semantic-kind="${kind}"`), type);
    assert.match(svg, new RegExp(`>${label}<`), type);
    if (['architecture', 'workflow', 'lifecycle'].includes(type)) {
      assert.match(svg, new RegExp(`data-legend-kind="${kind}"[^>]+data-legend-label="${label}"`), type);
    } else {
      assert.doesNotMatch(svg, /data-legend-kind=/, type);
    }
  }
});

test('label overrides preserve stable kinds and exact Semantic Legend boundaries', () => {
  const architecture = render('architecture', withLegend('architecture', {
    entries: {
      frontend: { label: 'Reader <UI> & "ops"' },
      external: { label: 'Future integration', visible: true },
    },
  }));
  const svg = canonicalSvg(architecture);
  const baselineSvg = canonicalSvg(render('architecture', FIXTURES.architecture));
  assert.deepEqual(attrValues(svg, 'data-node-id'), attrValues(baselineSvg, 'data-node-id'));
  assert.deepEqual(attrValues(svg, 'data-node-kind'), attrValues(baselineSvg, 'data-node-kind'));
  assert.deepEqual(attrValues(svg, 'data-edge-from'), attrValues(baselineSvg, 'data-edge-from'));
  assert.match(svg, />Reader &lt;UI&gt; &amp; &quot;ops&quot;</);
  assert.doesNotMatch(svg, /<UI>/);
  assert.match(svg, />Future integration</);
  assert.deepEqual(attrValues(svg, 'data-legend-semantic-kind'), ['frontend', 'database', 'external']);
  assert.deepEqual(attrValues(svg, 'data-legend-kind'), ['frontend', 'database']);
  assert.match(svg, /data-legend-kind="frontend"[^>]+data-legend-label="Reader &lt;UI&gt; &amp; &quot;ops&quot;"/);
  assert.match(architecture, /entry\.getAttribute\('data-legend-label'\)/);

  for (const type of ['sequence', 'dataflow']) {
    const kind = type === 'sequence' ? 'emphasis' : 'default';
    const html = canonicalSvg(render(type, withLegend(type, {
      entries: { [kind]: { label: 'Visible only' } },
    })));
    assert.match(html, />Visible only</);
    assert.doesNotMatch(html, /data-legend-bridge|data-legend-kind=/);
  }
});

test('strict per-renderer schemas reject malformed legend contracts with path-prefixed errors', () => {
  const known = {
    architecture: 'frontend', workflow: 'frontend', sequence: 'default', dataflow: 'default', lifecycle: 'start',
  };
  const cases = [
    [{ mode: 'sometimes' }, '/meta/legend/mode'],
    [{ entries: { unknown_kind: { visible: true } } }, '/meta/legend/entries'],
    [(type) => ({ entries: { [known[type]]: { label: '' } } }), '/meta/legend/entries/'],
    [(type) => ({ entries: { [known[type]]: { visible: 'yes' } } }), '/meta/legend/entries/'],
    [(type) => ({ entries: { [known[type]]: { color: '#fff' } } }), '/meta/legend/entries/'],
    [{ mode: 'auto', extra: true }, '/meta/legend'],
  ];
  for (const type of Object.keys(FIXTURES)) {
    for (const [legendOrFactory, pathPrefix] of cases) {
      const legend = typeof legendOrFactory === 'function' ? legendOrFactory(type) : legendOrFactory;
      const failure = validateFailure(type, withLegend(type, legend));
      assert.ok(
        failure.diagnostics.some((diagnostic) => diagnostic.subject.path.startsWith(pathPrefix)),
        `${type}: expected a diagnostic under ${pathPrefix}: ${JSON.stringify(failure.diagnostics)}`,
      );
    }
  }
});

test('measured legends fail explicitly instead of wrapping into diagram content', () => {
  const label = '界'.repeat(40);
  const entries = Object.fromEntries(CATALOGS.workflow.map((kind) => [kind, { label }]));
  const failure = validateFailure('workflow', withLegend('workflow', { mode: 'all', entries }));
  const diagnostic = failure.diagnostics.find((entry) => entry.code === 'legend/vertical-overflow');
  assert.ok(diagnostic, JSON.stringify(failure.diagnostics));
  assert.equal(diagnostic.subject.path, '/meta/legend');
  assert.ok(diagnostic.evidence.rowCount > 1);

  const routedEntries = Object.fromEntries(CATALOGS.architecture.map((kind) => [
    kind,
    { label: `Long ${kind} convention` },
  ]));
  const routed = withLegend('architecture', { mode: 'all', entries: routedEntries });
  routed.connections = [{
    from: 'ui',
    to: 'store',
    label: 'bottom route',
    fromSide: 'bottom',
    toSide: 'bottom',
    via: [[120, 382], [360, 382]],
    labelAt: [240, 370],
  }];
  const renderFailure = run('architecture', routed);
  assert.notEqual(renderFailure.status, 0);
  assert.match(renderFailure.stderr, /legend\/content-overlap/);
  assert.equal(renderFailure.html, '');
});

test('explicit Architecture viewBox rejects legend title rectangles that overlap content', () => {
  const doc = clone(FIXTURES.architecture);
  doc.meta.viewBox = [320, 320];
  doc.meta.legend = { mode: 'auto' };
  doc.components = [
    { id: 'ui', type: 'frontend', label: 'UI', pos: [40, 216], size: [120, 60] },
  ];
  const failure = validateFailure('architecture', doc);
  const diagnostic = failure.diagnostics.find((entry) => entry.code === 'legend/vertical-overflow');
  assert.ok(diagnostic, JSON.stringify(failure.diagnostics));
  assert.equal(diagnostic.subject.path, '/meta/legend');
  assert.ok(diagnostic.evidence.requiredTopY < diagnostic.evidence.availableTopY);
});

test('a single unfit label fails with a path-specific width diagnostic', () => {
  const failure = validateFailure('architecture', withLegend('architecture', {
    entries: { frontend: { label: '界'.repeat(80) } },
  }));
  const diagnostic = failure.diagnostics.find((entry) => entry.code === 'legend/label-too-wide');
  assert.ok(diagnostic, JSON.stringify(failure.diagnostics));
  assert.equal(diagnostic.subject.path, '/meta/legend/entries/frontend/label');
  assert.ok(diagnostic.evidence.measuredWidthPx > diagnostic.evidence.availableWidthPx);
});

test('measured legend rows share baselines and stay within the viewBox for localized labels', () => {
  const doc = withLegend('lifecycle', {
    mode: 'all',
    entries: {
      start: { label: '开始 / Start of the complete lifecycle' },
      active: { label: '正在执行 active processing' },
      waiting: { label: '等待人工输入' },
      decision: { label: 'Decision gate with deterministic wrapping' },
      success: { label: '成功完成' },
      failure: { label: 'Failure / 失败' },
      neutral: { label: 'Neutral state' },
      external: { label: 'External system' },
    },
  });
  const svg = canonicalSvg(render('lifecycle', doc));
  const viewBox = attrValues(svg.match(/<svg\b[^>]*>/)?.[0] || '', 'viewBox')[0].split(/\s+/).map(Number);
  const tags = [...svg.matchAll(/<g\b[^>]*data-legend-semantic-kind="[^"]+"[^>]*>/g)].map((match) => match[0]);
  assert.equal(tags.length, CATALOGS.lifecycle.length);
  const boxes = tags.map((tag) => ({
    x: Number(attrValues(tag, 'data-legend-x')[0]),
    y: Number(attrValues(tag, 'data-legend-baseline')[0]),
    width: Number(attrValues(tag, 'data-legend-width')[0]),
  }));
  assert.ok(boxes.every((box) => Number.isFinite(box.x) && Number.isFinite(box.y) && Number.isFinite(box.width)));
  assert.ok(boxes.every((box) => box.x >= 0 && box.x + box.width <= viewBox[2]));
  const rows = new Map();
  for (const box of boxes) rows.set(box.y, [...(rows.get(box.y) || []), box]);
  for (const row of rows.values()) {
    const sorted = [...row].sort((left, right) => left.x - right.x);
    for (let index = 1; index < sorted.length; index += 1) {
      assert.ok(sorted[index - 1].x + sorted[index - 1].width <= sorted[index].x);
    }
  }
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/motion-governor-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;

test('Motion Governor preserves mode, ownership, ambient completion and real callers', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser motion checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-motion-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_MOTION_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const records = [];
  t.after(() => {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n');
  });
  const cases = {
    architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8'));
    doc.meta.animation = 'trace';
    const input = path.join(scratch, mode + '.json');
    fs.writeFileSync(input, JSON.stringify(doc));
    files[mode] = path.join(scratch, mode + '.html');
    execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, files[mode]]);
  }
  files.static = path.join(scratch, 'static.html');
  execFileSync(process.execPath, [path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
    path.join(skillRoot, 'examples', cases.architecture), files.static]);
  const browser = new ChromeVisualBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  async function run(expression) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  let startup;
  let navigationId = 0;
  async function media(reduced) {
    await send('Emulation.setEmulatedMedia', { media: '', features: [
      { name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' },
    ] });
  }
  async function load(mode = 'architecture', { theme = 'dark', reduced = false, fixture = '', preserveStorage = false, query = '' } = {}) {
    const expectedNavigation = ++navigationId;
    if (startup) await send('Page.removeScriptToEvaluateOnNewDocument', { identifier: startup });
    ({ identifier: startup } = await send('Page.addScriptToEvaluateOnNewDocument', { source: `(() => {
      if (new URL(location.href).searchParams.get('testNavigation') !== '${expectedNavigation}') return;
      window.motionNavigation = ${expectedNavigation};
      try { window.motionStartupPreference = localStorage.getItem('archify-motion'); }
      catch (error) { window.motionStartupPreference = String(error); }
      window.motionErrors = []; window.motionEnds = []; window.motionAmbient = [];
      addEventListener('error', e => motionErrors.push(e.message));
      addEventListener('unhandledrejection', e => motionErrors.push(String(e.reason)));
      addEventListener('animationend', e => {
        if (e.target.matches('[data-animate]')) motionEnds.push({ trusted:e.isTrusted, name:e.animationName });
      }, true);
      new MutationObserver(records => {
        for (const r of records) if (r.attributeName === 'data-ambient-motion') {
          motionAmbient.push({ before:r.oldValue, after:r.target.getAttribute(r.attributeName) });
        }
      }).observe(document, { subtree:true, attributes:true, attributeOldValue:true, attributeFilter:['data-ambient-motion'] });
      window.motionWait = predicate => new Promise((resolve, reject) => {
        const start = performance.now();
        function sample() {
          if (predicate()) return resolve();
          if (performance.now() - start > 12000) return reject(new Error('Motion observation timed out'));
          requestAnimationFrame(sample);
        }
        requestAnimationFrame(sample);
      });
      ${preserveStorage ? '' : "try { localStorage.removeItem('archify-motion'); } catch (_) {}"}
      ${fixture}
    })();` }));
    await send('Emulation.setDeviceMetricsOverride', { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
    await media(reduced);
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    const navigation = await send('Page.navigate', { url: pathToFileURL(files[mode]).href + `?theme=${theme}&testNavigation=${expectedNavigation}${query}` });
    assert.ok(navigation.loaderId, 'Motion fixture must load a new document.');
    await loaded;
    await run('document.fonts.ready');
    assert.equal(await run('window.motionNavigation'), expectedNavigation, 'Motion fixture document identity');
  }
  async function snapshot(label) {
    const value = await run(`(() => {
      const m = Archify.motionGovernor, root = document.documentElement, btn = document.getElementById('btn-motion');
      return { capable:m.capable, mode:m.mode(), paused:m.isPaused(), owner:m.owner(),
        rootMode:root.getAttribute('data-motion'), rootOwner:root.getAttribute('data-motion-owner'),
        ambient:root.getAttribute('data-ambient-motion'), reason:root.getAttribute('data-ambient-settle-reason'),
        hidden:btn.hidden, disabled:btn.disabled, pressed:btn.getAttribute('aria-pressed'),
        label:document.getElementById('motion-label').textContent, aria:btn.getAttribute('aria-label'), errors:motionErrors };
    })()`);
    assert.deepEqual(value.errors, [], label);
    records.push({ scenario: label, ...value });
    return value;
  }
  async function screenshot(name) {
    if (!evidence) return;
    await run('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))');
    const shot = await send('Page.captureScreenshot', { format: 'png' });
    fs.writeFileSync(path.join(evidence, name + '.png'), Buffer.from(shot.data, 'base64'));
  }

  await t.test('five trace modes initialize; representative CSS animation completes once; static methods remain inert', async () => {
    for (const mode of Object.keys(cases)) {
      await load(mode);
      const initial = await snapshot(mode + '-initial');
      assert.equal(initial.capable, true); assert.equal(initial.mode, 'live');
      // All modes share the same Governor/CSS. One real completion plus the
      // five-mode initialization contract covers this seam without five waits.
      if (mode === 'architecture') {
        await run(`motionWait(() => document.documentElement.getAttribute('data-ambient-motion') === 'settled')`);
        const state = await snapshot(mode + '-settled');
        assert.equal(state.capable, true); assert.equal(state.mode, 'live'); assert.equal(state.reason, 'complete');
        const ambient = await run(`({ running:motionAmbient.some(r => r.after === 'running' || r.before === 'running'),
          ended:motionEnds.some(e => e.trusted), animations:Array.from(document.querySelectorAll('[data-animate]'), e => getComputedStyle(e).animationName),
          security:Array.from(document.querySelectorAll('.a-security'), e => getComputedStyle(e).strokeDasharray) })`);
        assert.equal(ambient.running, true); assert.equal(ambient.ended, true);
        assert.ok(ambient.animations.every(name => name === 'none'));
        assert.ok(ambient.security.every(dash => dash === '5px, 5px'));
      }
      await run(`Archify.motionGovernor.pause(); Archify.motionGovernor.resume();`);
      assert.equal((await snapshot(mode + '-resumed')).ambient, 'settled');
    }
    await load('static');
    const inert = await run(`(() => { const m=Archify.motionGovernor; return [m.capable,m.pause(),m.resume(),m.toggle(),m.setMode('live'),m.mode(),m.claim('story'),m.release(1),m.suspend('test')(),m.isPaused(),m.owner()]; })()`);
    assert.deepEqual(inert, [false,false,false,false,'still','still',0,false,false,true,'']);
    assert.equal((await snapshot('static')).hidden, true);
  });

  await t.test('stored user intent remains distinct from reduced motion and suspension', async () => {
    await load();
    assert.equal(await run('Archify.motionGovernor.pause()'), true);
    assert.equal(await run(`localStorage.getItem('archify-motion')`), 'still');
    for (let reload = 0; reload < 5; reload++) {
      await load('architecture', { preserveStorage: true });
      const stored = await run(`({initial:motionStartupPreference,current:localStorage.getItem('archify-motion'),navigation:motionNavigation,url:location.href})`);
      assert.equal((await snapshot('stored-still-' + reload)).mode, 'still', JSON.stringify(stored));
    }
    assert.equal(await run(`Archify.motionGovernor.setMode('live', {persist:false})`), 'live');
    assert.equal(await run(`localStorage.getItem('archify-motion')`), 'still');
    assert.equal(await run('Archify.motionGovernor.resume()'), false);
    assert.equal(await run(`localStorage.getItem('archify-motion')`), null);
    await media(true);
    await run('motionWait(() => document.getElementById("btn-motion").disabled)');
    assert.equal(await run('Archify.motionGovernor.resume()'), false);
    assert.equal((await snapshot('reduced-resume')).mode, 'still');
    await run('window.releaseTest = Archify.motionGovernor.suspend("test")');
    await media(false);
    await run('motionWait(() => !document.getElementById("btn-motion").disabled)');
    assert.equal((await snapshot('suspension-after-media')).mode, 'still');
    assert.equal(await run('releaseTest()'), true);
    assert.equal(await run('releaseTest()'), false);
    assert.equal((await snapshot('released')).mode, 'live');
    assert.equal(await run('Archify.motionGovernor.toggle()'), true);
    assert.equal(await run('Archify.motionGovernor.toggle()'), false);
    await load('architecture', { fixture: `Storage.prototype.getItem = Storage.prototype.setItem = Storage.prototype.removeItem = function () { throw new Error('storage fixture'); };` });
    assert.equal(await run('Archify.motionGovernor.pause()'), true);
    assert.equal(await run('Archify.motionGovernor.resume()'), false);
    await snapshot('storage-unavailable');
  });

  await t.test('claims preempt cleanup, normal release does not, and SVG owners fall back automatically', async () => {
    await load();
    await run(`window.main = document.querySelector('.diagram-container > svg'); main.setAttribute('data-focus-active','true'); main.setAttribute('data-route-active','true');`);
    await run(`motionWait(() => Archify.motionGovernor.owner() === 'route')`);
    assert.equal((await snapshot('derived-route')).rootOwner, 'route');
    const claims = await run(`(() => {
      const m=Archify.motionGovernor, events=[];
      const a=m.claim('story',()=>events.push('A'));
      const b=m.claim('story',()=>{ events.push('B'); throw new Error('cleanup fixture'); });
      const stale=m.release(a), ownerAfterStale=m.owner();
      const c=m.claim('handoff',()=>events.push('C'));
      const released=m.release(c), repeated=m.release(c);
      return {events,stale,ownerAfterStale,released,repeated,increasing:a<b&&b<c,owner:m.owner(),empty:m.claim('')};
    })()`);
    assert.deepEqual(claims, { events:['A','B'], stale:false, ownerAfterStale:'story', released:true, repeated:false, increasing:true, owner:'route', empty:0 });
    await run(`main.removeAttribute('data-route-active')`);
    await run(`motionWait(() => Archify.motionGovernor.owner() === 'focus')`);
    const focus = await snapshot('derived-focus'); assert.equal(focus.rootOwner, 'focus'); assert.match(focus.aria, /focus/i);
    await run(`main.removeAttribute('data-focus-active')`);
    await run(`motionWait(() => Archify.motionGovernor.owner() === '')`);
    assert.equal((await snapshot('derived-empty')).rootOwner, null);
  });

  await t.test('counted suspensions and the existing visibility-key interaction remain distinct', async () => {
    await load();
    const values = await run(`(() => {
      const m=Archify.motionGovernor, a=m.suspend('a'), b=m.suspend('a'), c=m.suspend('c');
      const first=[c(),m.isPaused(),b(),m.isPaused(),b(),a(),m.isPaused()];
      const releaseVisibility=m.suspend('visibility');
      Object.defineProperty(document,'hidden',{configurable:true,value:true}); document.dispatchEvent(new Event('visibilitychange'));
      const hidden=m.mode();
      Object.defineProperty(document,'hidden',{configurable:true,value:false}); document.dispatchEvent(new Event('visibilitychange'));
      const shown=m.mode(),released=releaseVisibility(); delete document.hidden;
      return {first,hidden,shown,released,hiddenAttr:document.documentElement.getAttribute('data-document-hidden')};
    })()`);
    assert.deepEqual(values, {first:[true,true,true,true,false,true,false],hidden:'still',shown:'live',released:true,hiddenAttr:null});
    await snapshot('visibility-key-fixture');
  });

  await t.test('ambient cancellation, empty targets and optional platform interfaces keep their fallback', async () => {
    await load();
    const cancelled = await run(`(() => {
      const root=document.documentElement, svg=document.querySelector('.diagram-container > svg');
      const before=root.getAttribute('data-ambient-motion');
      svg.dispatchEvent(new Event('animationcancel',{bubbles:true}));
      const ignored=root.getAttribute('data-ambient-motion');
      svg.querySelectorAll('[data-animate="edge"], [data-animate="node"]').forEach(e=>e.dispatchEvent(new Event('animationcancel',{bubbles:true})));
      return {before,ignored,after:root.getAttribute('data-ambient-motion'),reason:root.getAttribute('data-ambient-settle-reason')};
    })()`);
    assert.deepEqual(cancelled, {before:'running',ignored:'running',after:'settled',reason:'complete'});
    await load('architecture', { fixture: `const query = Element.prototype.querySelectorAll; Element.prototype.querySelectorAll = function (s) { return s === '[data-animate="edge"], [data-animate="node"]' ? [] : query.call(this,s); };` });
    assert.equal((await snapshot('empty-target-fixture')).reason, 'empty');
    for (const [name, fixture] of [
      ['no-media', 'window.matchMedia = undefined;'],
      ['legacy-media', `const nativeMatch=window.matchMedia.bind(window); window.matchMedia=q=>{const m=nativeMatch(q); m.addEventListener=undefined; return m;};`],
      ['no-observer', 'window.MutationObserver = undefined;'],
    ]) {
      await load('architecture', { fixture });
      assert.equal(await run('Archify.motionGovernor.pause()'), true);
      assert.equal((await snapshot(name)).mode, 'still');
      if (name === 'legacy-media') {
        await run('Archify.motionGovernor.resume()'); await media(true);
        await run('motionWait(() => document.getElementById("btn-motion").disabled)');
        assert.equal((await snapshot('legacy-media-changed')).mode, 'still');
      }
    }
    for (const query of ['&embed=1', '&play=1']) {
      await load('architecture', { query });
      // Share playback sets its root flag after Governor initialization.
      await run(`motionWait(() => document.documentElement.getAttribute('data-ambient-settle-reason') === 'suppressed')`);
      assert.equal((await snapshot('suppressed-' + query)).ambient, 'settled');
    }
    await load('architecture', { fixture: `Object.defineProperty(document,'hidden',{configurable:true,value:true});` });
    assert.equal((await snapshot('initial-hidden-fixture')).mode, 'still');
  });

  await t.test('Motion pauses actual Story, handoff and Route without discarding elapsed dwell', async () => {
    await load();
    await run(`Archify.guidedViews.activate('request-path'); motionWait(() => !Archify.guidedViews.handoff())`);
    assert.equal(await run('Archify.guidedViews.play()'), true);
    assert.equal(await run('Archify.guidedViews.isPlaying()'), true);
    await run('Archify.motionGovernor.pause()');
    assert.equal(await run('Archify.guidedViews.isPlaying()'), false);
    await run('Archify.motionGovernor.resume()');
    assert.equal(await run('Archify.guidedViews.isPlaying()'), false);
    await load();
    await run(`Archify.guidedViews.activate('request-path'); motionWait(() => !Archify.guidedViews.handoff())`);
    const handoff = await run(`(() => {
      Archify.guidedViews.activate('identity-and-cache');
      const before=Archify.guidedViews.handoff(); Archify.motionGovernor.pause();
      return {before:!!before,after:Archify.guidedViews.handoff()};
    })()`);
    assert.deepEqual(handoff, {before:true,after:null});
    await load();
    const route = await run(`(async () => {
      Archify.routeProbe.begin({source:'users'}); Archify.routeProbe.choose('db');
      const schedule=window.setTimeout, delays=[];
      window.setTimeout=function(callback,delay,...args){ delays.push(delay); return schedule(callback,delay,...args); };
      const started=Archify.routeProbe.playJourney();
      await new Promise(resolve=>schedule(resolve,180));
      const before=Archify.routeProbe.result(), pauses=[], pause=Archify.routeProbe.pauseJourney;
      Archify.routeProbe.pauseJourney=function(options){pauses.push(options); return pause(options);};
      Object.defineProperty(document,'hidden',{configurable:true,value:true}); document.dispatchEvent(new Event('visibilitychange'));
      const paused=Archify.routeProbe.result();
      Object.defineProperty(document,'hidden',{configurable:true,value:false}); document.dispatchEvent(new Event('visibilitychange'));
      const autoResumed=Archify.routeProbe.isJourneyPlaying();
      delays.length=0; const resumed=Archify.routeProbe.playJourney();
      const remaining=delays.at(-1); window.setTimeout=schedule; delete document.hidden;
      Archify.routeProbe.pauseJourney=pause; pause();
      return {started,before,paused,autoResumed,resumed,remaining,pauses};
    })()`);
    assert.equal(route.started, true); assert.equal(route.before.playing, true); assert.equal(route.paused.playing, false);
    assert.equal(route.paused.journey, route.before.journey); assert.equal(route.autoResumed, false); assert.equal(route.resumed, true);
    assert.ok(route.pauses.some(options => options.preserveElapsed === true && options.reason === 'hidden'));
    assert.ok(route.remaining > 0 && route.remaining < 1000, JSON.stringify(route));
    await snapshot('route-hidden-pause');
  });

  await t.test('dark and light modes expose the same controls and computed Still state', async () => {
    for (const theme of ['dark', 'light']) {
      await load('architecture', { theme });
      await run(`motionWait(() => document.documentElement.getAttribute('data-ambient-motion') === 'settled')`);
      const live = await snapshot('live-' + theme); assert.equal(live.pressed, 'true');
      await screenshot('live-' + theme);
      await run(`document.getElementById('btn-motion').click()`);
      const still = await snapshot('still-' + theme); assert.equal(still.mode, 'still'); assert.equal(still.pressed, 'false');
      assert.match(still.aria, /resume/i);
      assert.equal(await run(`getComputedStyle(document.querySelector('.pulse-dot')).animationName`), 'none');
      await screenshot('still-' + theme);
      await run(`Archify.focus.set('api', {toggle:false}); motionWait(() => Archify.motionGovernor.owner() === 'focus')`);
      const exported = await run(`(async () => {
        const svg=document.querySelector('.diagram-container > svg'), m=Archify.motionGovernor;
        const before={svg:svg.outerHTML,mode:m.mode(),owner:m.owner()}, original=URL.createObjectURL;
        let blob, serialized;
        URL.createObjectURL=function(value){if(value.type.startsWith('image/svg+xml'))blob=value;return original.call(URL,value);};
        // Capture serialization synchronously; the later download click can
        // reach the existing outside-click Focus handler.
        try {
          const pending=Archify.exportMenu.run('svg');
          serialized={svg:svg.outerHTML,mode:m.mode(),owner:m.owner()};
          await pending;
        } finally { URL.createObjectURL=original; }
        const text=await blob.text(), root=new DOMParser().parseFromString(text,'image/svg+xml').documentElement;
        return {unchanged:before.svg===serialized.svg&&before.mode===serialized.mode&&before.owner===serialized.owner,
          modeUnchanged:before.mode===m.mode(),
          clean:!root.querySelector('[data-focus-selected], [data-radar-node-id]')&&!root.hasAttribute('data-focus-active'),
          geometry:root.getAttribute('viewBox')===svg.getAttribute('viewBox')};
      })()`);
      assert.deepEqual(exported, {unchanged:true,modeUnchanged:true,clean:true,geometry:true});
      await snapshot('export-' + theme);

    }
  });
});
```

## test/motion-governor.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-motion-governor-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example, animation = 'trace') {
  const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8'));
  doc.meta = { ...doc.meta };
  if (animation) doc.meta.animation = animation;
  else delete doc.meta.animation;
  const input = path.join(tmp, `${mode}-${animation || 'static'}.json`);
  const output = path.join(tmp, `${mode}-${animation || 'static'}.html`);
  fs.writeFileSync(input, JSON.stringify(doc));
  execFileSync('node', [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, output], {
    stdio: ['ignore', 'ignore', 'pipe'],
  });
  return fs.readFileSync(output, 'utf8');
}

function svgBlock(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all renderers inherit one viewer-only Live/Still Motion Governor', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="btn-motion"[^>]+hidden[^>]+aria-label="Pause motion"/, mode);
    assert.match(html, /Archify\.motionGovernor = \(function \(\)/, mode);
    assert.match(html, /var capable = !!\(svg && svg\.getAttribute\('data-animation'\) === 'trace'\)/, mode);
    assert.match(svgBlock(html), /data-animation="trace"/, mode);
    assert.doesNotMatch(svgBlock(html), /data-motion-(?:capable|owner)|motion-control/, mode);
  }
});

test('static artifacts stay truly still while trace artifacts opt into ambient motion', () => {
  const html = render('architecture', CASES.architecture, null);
  const svg = svgBlock(html);
  assert.doesNotMatch(svg, /data-animation=/);
  assert.match(html, /\.pulse-dot \{[\s\S]*?animation: none;/);
  assert.match(html, /html\[data-motion-capable="true"\] \.pulse-dot \{ animation: pulse 2s infinite; \}/);
  assert.match(html, /html\[data-motion-capable="true"\]\[data-preset="signal-flow"\]\[data-ambient-motion="running"\] \.diagram-container::before/);
  assert.match(html, /if \(!capable\) \{[\s\S]*?btn\.hidden = true;[\s\S]*?capable: false/);
  assert.match(html, /html\.setAttribute\('data-motion-capable', 'true'\);[\s\S]*?btn\.hidden = false/);
});

test('reader pause is persistent, explicit, and reduced-motion aware', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /var STORAGE_KEY = 'archify-motion'/);
  assert.match(html, /localStorage\.setItem\(STORAGE_KEY, 'still'\)/);
  assert.match(html, /localStorage\.removeItem\(STORAGE_KEY\)/);
  assert.match(html, /html\.setAttribute\('data-motion', paused \? 'still' : 'live'\)/);
  assert.match(html, /btn\.setAttribute\('aria-pressed', paused \? 'false' : 'true'\)/);
  assert.match(html, /Motion paused by reduced-motion preference/);
  assert.match(html, /motionQuery\.addEventListener\('change', render\)/);
  assert.match(html, /document\.addEventListener\('visibilitychange', syncVisibility\)/);
  assert.match(html, /Archify\.guidedViews\.isPlaying\(\)[\s\S]*?Archify\.guidedViews\.pause\(\)/);
  assert.match(html, /play\.disabled = !playing && !automaticPlaybackAllowed/);
  assert.match(html, /Story playback unavailable while motion is Still/);
  assert.match(html, /\.pulse-dot \{ animation: none !important; \}/);
  assert.match(html, /html\[data-motion="still"\] \.story-trail-flow/);
});

test('strong semantic intent receives the single motion budget', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /if \(svg\.hasAttribute\('data-story-playing'\) \|\| svg\.hasAttribute\('data-story-follow'\)\) return 'story'/);
  assert.match(html, /if \(svg\.hasAttribute\('data-story-active'\)\) return 'chapter'/);
  assert.match(html, /data-route-picking'[\s\S]*?return 'route'/);
  assert.match(html, /data-lens-active'\)\) return 'lens'/);
  assert.match(html, /data-relationship-preview-active'\)\) return 'relationship'/);
  assert.match(html, /data-intent-trace-active'\)\) return 'intent'/);
  assert.match(html, /data-focus-active'\)\) return 'focus'/);
  assert.match(html, /data-legend-preview-active'\)\) return 'legend'/);
  assert.match(html, /new MutationObserver\(function \(\) \{ publishOwner\(\); \}\)/);
  assert.match(html, /html\[data-motion-owner\] svg\[data-animation="trace"\] \[data-animate\]/);
  assert.match(html, /function claim\(next, cleanup\)[\s\S]*?return ownerToken/);
  assert.match(html, /function release\(token\)[\s\S]*?token !== ownerToken/);
  assert.match(html, /function clearClaim\(preempted\)[\s\S]*?try \{ cleanup\(\); \} catch/);
  assert.match(html, /function claim\(next, cleanup\)[\s\S]*?clearClaim\(true\)/);
  assert.match(html, /animation: archify-route-probe-flow 1\.1s[\s\S]*?1 both/);
  assert.doesNotMatch(html, /archify-route-probe-flow[^;]*infinite/);
});

test('motion control is mobile-contained, embed-safe, and export-neutral', () => {
  const html = render('sequence', CASES.sequence);
  assert.match(html, /\.toolbar #btn-motion\[hidden\] \{ display: none !important; \}/);
  assert.match(html, /\.toolbar button \{[\s\S]*?min-height: 2\.75rem;/);
  assert.match(html, /@media \(max-width: 360px\) \{[\s\S]*?\.toolbar #btn-motion \{ min-width: 4\.4rem;/);
  assert.match(html, /#theme-label, #preset-label, #present-label \{ display: none; \}/);
  assert.match(html, /html\[data-embed="true"\] \.diagram-container::before/);
  assert.match(html, /html\[data-share-playback="true"\] \.diagram-container::before/);
  assert.match(html, /Still also parks bounded[\s\S]*?viewer signals without discarding their static meaning/);
  assert.match(html, /recordExportReceipt\('svg', blob, d\.canonicalStateClean\)/);
  assert.doesNotMatch(svgBlock(html), /btn-motion|data-motion=|data-motion-owner/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/offline-font-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';
import { assertFontCss, inspectDocuments } from './helpers/offline-fonts.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;
const options = { skip: chrome ? false : 'Set ARCHIFY_CHROME for offline font and export browser acceptance.' };

async function evaluate(browser, expression) {
  const result = await browser.cdp.send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true }, await browser.sessionPromise, 30000);
  assert.ok(!result.exceptionDetails, result.exceptionDetails?.exception?.description || result.exceptionDetails?.text);
  return result.result?.value;
}

async function prepare(browser, blocked) {
  const session = await browser.sessionPromise;
  await browser.cdp.send('Network.enable', {}, session);
  await browser.cdp.send('Network.setCacheDisabled', { cacheDisabled: true }, session);
  await browser.cdp.send('Network.setBlockedURLs', { urls: blocked ? ['http://*', 'https://*'] : [] }, session);
  await browser.cdp.send('DOM.enable', {}, session);
  await browser.cdp.send('CSS.enable', {}, session);
  await browser.cdp.send('CSS.setLocalFontsEnabled', { enabled: false }, session);
  const requests = [];
  let buffer = '';
  browser.cdp.readPipe.on('data', (chunk) => {
    buffer += chunk;
    let end;
    while ((end = buffer.indexOf('\0')) >= 0) {
      const message = JSON.parse(buffer.slice(0, end));
      buffer = buffer.slice(end + 1);
      if (message.method === 'Network.requestWillBeSent' && /^https?:/.test(message.params.request.url)) requests.push(message.params.request.url);
    }
  });
  return requests;
}

async function actualFonts(browser, selector) {
  const session = await browser.sessionPromise;
  const { root } = await browser.cdp.send('DOM.getDocument', {}, session);
  const { nodeId } = await browser.cdp.send('DOM.querySelector', { nodeId: root.nodeId, selector }, session);
  assert.ok(nodeId, selector);
  return (await browser.cdp.send('CSS.getPlatformFontsForNode', { nodeId }, session)).fonts;
}

function render(input, output, quality = 'standard') {
  execFileSync(process.execPath, [path.join(skillRoot, 'bin/archify.mjs'), 'render', 'architecture', input, output, '--quality', quality], { stdio: 'pipe' });
}

const exportCapture = `(() => {
  const create = URL.createObjectURL.bind(URL);
  const blobs = new Map();
  URL.createObjectURL = function(blob) {
    const url = create(blob); blobs.set(url, blob);
    if (blob.type.includes('svg')) window.__svgBlob = blob;
    return url;
  };
  HTMLAnchorElement.prototype.click = function() {
    if (this.download) window.__download = blobs.get(this.href);
  };
})()`;

async function exported(browser, format) {
  return evaluate(browser, `(async () => {
    window.__download = null; window.__svgBlob = null;
    document.querySelector('[data-format="${format}"]').click();
    for (let i = 0; i < 1000 && !window.__download; i++) await new Promise(r => setTimeout(r, 10));
    if (!window.__download) throw new Error('Export did not finish: ' + document.documentElement.dataset.lastExportError);
    const blob = window.__download;
    return { type: blob.type, bytes: blob.size, svg: window.__svgBlob ? await window.__svgBlob.text() : null,
      canonical: document.documentElement.dataset.lastExportCanonical,
      width: document.documentElement.dataset.lastExportWidth, height: document.documentElement.dataset.lastExportHeight };
  })()`);
}

test('fresh viewers use bundled fonts with local fonts disabled and identical offline layout', options, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-font-layout-'));
  const fixtures = [
    ['production', 'production-deployment.architecture.json', 'showcase'],
    ['web', 'web-app.architecture.json', 'standard'],
  ];
  try {
    for (const [name, source, quality] of fixtures) render(path.join(skillRoot, 'examples', source), path.join(tmp, `${name}.html`), quality);
    const mixed = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json')));
    mixed.meta.title = 'Fonts A Ā Ѡ Ж Ω ắ 中文';
    fs.writeFileSync(path.join(tmp, 'mixed.json'), JSON.stringify(mixed));
    render(path.join(tmp, 'mixed.json'), path.join(tmp, 'mixed.html'));
    const compare = path.join(tmp, 'compare.html');
    execFileSync(process.execPath, [path.join(skillRoot, 'bin/archify.mjs'), 'compare', 'architecture', path.join(skillRoot, 'examples/checkout-platform.base.architecture.json'), path.join(skillRoot, 'examples/checkout-platform.head.architecture.json'), compare], { stdio: 'pipe' });
    const online = new Map();
    for (const blocked of [false, true]) {
      const browser = new ChromeVisualBrowser(chrome);
      try {
        const requests = await prepare(browser, blocked);
        for (const name of ['production', 'web', 'mixed']) for (const theme of ['light', 'dark']) {
          const metrics = await browser.inspect({ artifactPath: path.join(tmp, `${name}.html`), width: 1440, height: 900, theme });
          const snapshot = { readerWidth: metrics.readerWidth, diagramWidth: metrics.diagramWidth, scrollHeight: metrics.scrollHeight };
          const key = `${name}/${theme}`;
          assert.ok(snapshot.readerWidth > 0 && snapshot.diagramWidth > 0, key);
          if (blocked) assert.deepEqual(snapshot, online.get(key), key);
          else online.set(key, snapshot);
          const fonts = await actualFonts(browser, '.diagram-container svg text[data-node-label]');
          assert.ok(fonts.some(f => f.isCustomFont && /JetBrains Mono/.test(f.familyName)), JSON.stringify(fonts));
          const loaded = await evaluate(browser, `(async () => {
            await Promise.all([400,500,600,700].map(w => document.fonts.load(w + ' 16px "JetBrains Mono"', 'A Ā Ѡ Ж Ω ắ')));
            return Array.from(document.fonts).filter(f => /JetBrains Mono/.test(f.family)).map(f => f.status);
          })()`);
          assert.deepEqual(loaded, Array(6).fill('loaded'));
        }
        await browser.inspect({ artifactPath: compare, width: 1440, height: 900, theme: 'light' });
        const frames = await evaluate(browser, `(async () => Promise.all(Array.from(document.querySelectorAll('iframe[srcdoc]')).map(async frame => {
          await frame.contentDocument.fonts.load('400 16px "JetBrains Mono"', 'A Ā Ѡ Ж Ω ắ');
          return Array.from(frame.contentDocument.fonts).filter(f => /JetBrains Mono/.test(f.family)).map(f => f.status);
        })))()`);
        assert.deepEqual(frames, [Array(6).fill('loaded'), Array(6).fill('loaded')]);
        assert.deepEqual(requests, [], 'viewers must not attempt an HTTP(S) request');
      } finally { await browser.close(); }
    }
  } finally { fs.rmSync(tmp, { recursive: true, force: true }); }
});

test('SVG and raster exports preserve the viewer font with local fonts and network disabled', options, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-font-export-'));
  const browser = new ChromeVisualBrowser(chrome);
  try {
    const requests = await prepare(browser, true);
    const artifact = path.join(tmp, 'web.html');
    render(path.join(skillRoot, 'examples/web-app.architecture.json'), artifact);
    await browser.inspect({ artifactPath: artifact, width: 1440, height: 900, theme: 'light' });
    await evaluate(browser, exportCapture);
    const svg = await exported(browser, 'svg');
    assert.equal(svg.canonical, 'true');
    assertFontCss(inspectDocuments(svg.svg)[0].styles.join('\n'), 'exported SVG');
    fs.writeFileSync(path.join(tmp, 'export.svg'), svg.svg);
    for (const format of ['png', 'jpeg', 'webp', 'share-card']) {
      const result = await exported(browser, format);
      assert.ok(result.bytes > 1000, format);
      assert.equal(result.type, `image/${format === 'share-card' ? 'png' : format}`);
      assertFontCss(inspectDocuments(result.svg)[0].styles.join('\n'), format);
      if (format === 'share-card') assert.deepEqual([result.width, result.height], ['1200', '630']);
    }
    // A negative control proves the font bytes affect actual Image/Canvas
    // rendering, rather than merely surviving serialization as inert text.
    const pixelsDiffer = await evaluate(browser, `(async () => {
      const source = await window.__svgBlob.text();
      async function pixels(svg) {
        const url = URL.createObjectURL(new Blob([svg], {type:'image/svg+xml'}));
        try {
          const image = new Image(); image.src = url; await image.decode();
          const canvas = document.createElement('canvas'); canvas.width=image.width; canvas.height=image.height;
          canvas.getContext('2d').drawImage(image,0,0); return canvas.toDataURL();
        } finally { URL.revokeObjectURL(url); }
      }
      return await pixels(source) !== await pixels(source.replace(/@font-face\\s*\\{[^}]+\\}/g,''));
    })()`);
    assert.equal(pixelsDiffer, true, 'embedded font must affect rasterized glyphs');
    const session = await browser.sessionPromise;
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    await browser.cdp.send('Page.navigate', { url: pathToFileURL(path.join(tmp, 'export.svg')).href }, session);
    await loaded;
    await evaluate(browser, 'document.fonts.ready');
    const fonts = await actualFonts(browser, 'text[data-node-label]');
    assert.ok(fonts.some(f => f.isCustomFont && /JetBrains Mono/.test(f.familyName)), JSON.stringify(fonts));
    assert.deepEqual(requests, []);
  } finally { await browser.close(); fs.rmSync(tmp, { recursive: true, force: true }); }
});

for (const format of ['png', 'share-card']) test(`${format} requested at DOMContentLoaded matches a font-settled repeat`, options, async (t) => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-font-early-export-'));
  const browser = new ChromeVisualBrowser(chrome);
  try {
    const requests = await prepare(browser, true);
    const artifact = path.join(tmp, 'early.html');
    render(path.join(skillRoot, 'examples/web-app.architecture.json'), artifact);
    const session = await browser.sessionPromise;
    await browser.cdp.send('Page.addScriptToEvaluateOnNewDocument', { source: exportCapture + `
      window.__fontDraws = [];
      const fillText = CanvasRenderingContext2D.prototype.fillText;
      CanvasRenderingContext2D.prototype.fillText = function(text, ...args) {
        window.__fontDraws.push(document.fonts.check(this.font, text));
        return fillText.call(this, text, ...args);
      };
      document.addEventListener('DOMContentLoaded', () => {
        window.__fontStatusAtClick = document.fonts.status;
        document.querySelector('[data-format="${format}"]').click();
      }, { once: true });
    ` }, session);
    await browser.inspect({ artifactPath: artifact, width: 1440, height: 900, theme: 'light' });
    const early = await evaluate(browser, `(async () => {
      for(let i=0;i<500&&!window.__download;i++) await new Promise(r=>setTimeout(r,10));
      if (!window.__download) throw new Error('early export did not finish');
      window.__firstCardBytes = Array.from(new Uint8Array(await window.__download.arrayBuffer()));
      return { statusAtClick: window.__fontStatusAtClick, readyAtDraw: window.__fontDraws };
    })()`);
    if (format === 'share-card') assert.ok(early.readyAtDraw.length > 0);
    assert.ok(early.readyAtDraw.every(Boolean), JSON.stringify(early));
    await exported(browser, format);
    assert.equal(await evaluate(browser, `(async () => {
      const current = new Uint8Array(await window.__download.arrayBuffer());
      return current.length === window.__firstCardBytes.length && current.every((byte,i) => byte === window.__firstCardBytes[i]);
    })()`), true);
    t.diagnostic(`${format}: fonts at initial click: ${early.statusAtClick}; fonts ready for every Canvas text draw`);
    assert.deepEqual(requests, []);
  } finally { await browser.close(); fs.rmSync(tmp, { recursive: true, force: true }); }
});
```

## test/offline-self-containment.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { assertFontCss, assertOfflineArtifact, inspectDocuments } from './helpers/offline-fonts.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const repoRoot = path.resolve(skillRoot, '..');
const cli = path.join(skillRoot, 'bin/archify.mjs');
const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8');
const DIAGRAMS = [
  ['architecture', 'web-app.architecture.json', 'web-app-rendered.html'],
  ['workflow', 'agent-tool-call.workflow.json', 'workflow-agent-tool-call-rendered.html'],
  ['sequence', 'cache-miss-request.sequence.json', 'sequence-cache-miss-request.html'],
  ['dataflow', 'product-analytics.dataflow.json', 'dataflow-product-analytics.html'],
  ['lifecycle', 'agent-run.lifecycle.json', 'lifecycle-agent-run.html'],
];

test('the viewer template carries its own font and readable provenance', () => {
  assertOfflineArtifact(template, 'template');
  const license = fs.readFileSync(path.join(skillRoot, 'assets/JetBrainsMono-OFL.txt'), 'utf8').trim();
  assert.ok(inspectDocuments(template)[0].styles.some((css) => css.includes(license)), 'standalone font CSS must carry the full license');
  const notices = fs.readFileSync(path.join(skillRoot, 'THIRD_PARTY_NOTICES.md'), 'utf8');
  assert.match(notices, /## JetBrains Mono/);
  assert.match(notices, /assets\/JetBrainsMono-OFL\.txt/);
});

test('font checks accept equivalent CSS but reject missing bytes, coverage and local overrides', () => {
  const css = inspectDocuments(template)[0].styles.join('\n');
  const reordered = css.replaceAll("font-family: 'JetBrains Mono'; font-style: normal;", 'font-style:normal; font-family:"JetBrains Mono";');
  assertFontCss(reordered, 'equivalent CSS');
  assert.throws(() => assertFontCss(css.replace(/@font-face\s*\{[^}]+\}/, ''), 'missing face'));
  assert.throws(() => assertFontCss(css.replace('base64,', 'base64,A'), 'corrupt bytes'));
  assert.throws(() => assertFontCss(css.replace('U+0460-052F', 'U+0460-052E'), 'missing character'));
  assert.throws(() => assertFontCss(css.replace('src: url(', "src: local('JetBrains Mono'), url("), 'local override'));
});

test('each compare srcdoc must carry its own font and reject external resources', () => {
  const frame = (html) => `<iframe srcdoc="${html.replaceAll('&', '&amp;').replaceAll('"', '&quot;').replaceAll('<', '&lt;')}"></iframe>`;
  const emptyFont = template.replace(/@font-face\s*\{[^}]+\}/g, '');
  assert.equal(assertOfflineArtifact(frame(template) + frame(template), 'compare'), 2);
  assert.throws(() => assertOfflineArtifact(frame(template) + frame(emptyFont), 'compare'), /srcdoc\[1\]/);
  for (const resource of ['<link rel="stylesheet" href="//fonts.example/font.css">', '<style>@import "https://fonts.example/font.css";</style>', '<img srcset="https://images.example/1.png 1x, https://images.example/2.png 2x">']) {
    assert.throws(() => assertOfflineArtifact(frame(template) + frame(template + resource), 'compare'), /external subresource/);
  }
  assertOfflineArtifact(template + '<!-- https://github.com/JetBrains/JetBrainsMono --><a href="https://github.com/JetBrains/JetBrainsMono">Source</a>', 'attribution');
});

test('a freshly delivered artifact of every type reaches no external origin', () => {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-offline-'));
  try {
    for (const [type, input] of DIAGRAMS) {
      const output = path.join(dir, `${type}.html`);
      const result = spawnSync(process.execPath, [cli, 'deliver', type, path.join(skillRoot, 'examples', input), output, '--quality', 'showcase', '--json'], { encoding: 'utf8' });
      assert.equal(result.status, 0, `${type}: ${result.stderr}`);
      assertOfflineArtifact(fs.readFileSync(output, 'utf8'), type);
    }
    const output = path.join(dir, 'compare.html');
    const result = spawnSync(process.execPath, [cli, 'compare', 'architecture', path.join(skillRoot, 'examples/checkout-platform.base.architecture.json'), path.join(skillRoot, 'examples/checkout-platform.head.architecture.json'), output, '--json'], { encoding: 'utf8' });
    assert.equal(result.status, 0, result.stderr);
    assert.equal(assertOfflineArtifact(fs.readFileSync(output, 'utf8'), 'fresh compare'), 2);
  } finally {
    fs.rmSync(dir, { recursive: true, force: true });
  }
});

test('every checked-in viewer artifact carries its font and reaches no external origin', () => {
  // Delivery-chain roots only; frozen experiments are not maintained viewers.
  const tracked = spawnSync('git', ['ls-files', '-z', '--', 'archify/examples', 'docs', 'examples'], { cwd: repoRoot, encoding: 'utf8' });
  assert.equal(tracked.status, 0, tracked.stderr);
  const artifacts = tracked.stdout.split('\0').filter((entry) => entry.endsWith('.html'))
    .filter((entry) => /Archify\.readerLayout/.test(fs.readFileSync(path.join(repoRoot, entry), 'utf8')));
  for (const required of ['examples/checkout-platform-delta.html', ...DIAGRAMS.map(([, , output]) => `archify/examples/${output}`)]) {
    assert.ok(artifacts.includes(required), `missing delivery-chain artifact: ${required}`);
  }
  for (const relative of artifacts) assertOfflineArtifact(fs.readFileSync(path.join(repoRoot, relative), 'utf8'), relative);
});
```

## test/open-artifact.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';

import { openArtifact, openLoopbackUrl } from '../bin/open-artifact.mjs';

const target = path.resolve("/tmp/-复杂 path 'quoted'/diagram.html");

test('open artifact: uses argument arrays without shell interpolation on every supported platform', () => {
  const cases = [
    {
      platform: 'darwin',
      command: 'open',
      args: [target],
      method: 'open',
    },
    {
      platform: 'linux',
      command: 'xdg-open',
      args: [target],
      method: 'xdg-open',
    },
    {
      platform: 'win32',
      command: 'powershell.exe',
      args: [
        '-NoProfile',
        '-NonInteractive',
        '-Command',
        'Start-Process -FilePath $args[0]',
        target,
      ],
      method: 'powershell',
    },
  ];

  for (const expected of cases) {
    let invocation;
    const result = openArtifact(target, {
      platform: expected.platform,
      spawn(command, args, options) {
        invocation = { command, args, options };
        return { status: 0 };
      },
    });

    assert.deepEqual(result, {
      requested: true,
      status: 'opened',
      target,
      method: expected.method,
    });
    assert.equal(invocation.command, expected.command);
    assert.deepEqual(invocation.args, expected.args);
    assert.equal(invocation.options.shell, false);
    assert.equal(invocation.options.timeout, 5000);
  }
});

test('open artifact: distinguishes missing support from opener execution failure', () => {
  const missing = openArtifact(target, {
    platform: 'linux',
    spawn() {
      return { error: Object.assign(new Error('missing'), { code: 'ENOENT' }) };
    },
  });
  assert.equal(missing.status, 'unsupported');
  assert.equal(missing.method, 'xdg-open');

  const timedOut = openArtifact(target, {
    platform: 'darwin',
    spawn() {
      return { error: Object.assign(new Error('timed out'), { code: 'ETIMEDOUT' }) };
    },
  });
  assert.equal(timedOut.status, 'failed');
  assert.equal(timedOut.method, 'open');

  const unknown = openArtifact(target, { platform: 'plan9' });
  assert.deepEqual(unknown, {
    requested: true,
    status: 'unsupported',
    target,
    method: null,
  });
});

test('open artifact: live preview opens only an exact loopback HTTP root', () => {
  const url = 'http://127.0.0.1:43127/';
  let invocation;
  const result = openLoopbackUrl(url, {
    platform: 'darwin',
    spawn(command, args, options) {
      invocation = { command, args, options };
      return { status: 0 };
    },
  });

  assert.deepEqual(result, {
    requested: true,
    status: 'opened',
    target: url,
    method: 'open',
  });
  assert.deepEqual(invocation.args, [url]);
  assert.equal(invocation.options.shell, false);

  for (const rejected of [
    'https://127.0.0.1:43127/',
    'http://localhost:43127/',
    'http://0.0.0.0:43127/',
    'http://127.0.0.1:43127/path',
    'http://127.0.0.1:43127/?source=secret',
    'not a url',
  ]) {
    assert.throws(() => openLoopbackUrl(rejected), /loopback|valid/i, rejected);
  }
});
```

## test/ordinary-model-floor.test.mjs

```js
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const repoRoot = path.resolve(skillRoot, '..');
const benchmark = path.join(repoRoot, 'benchmarks/ordinary-model-floor/benchmark.mjs');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-ordinary-model-floor-'));

function writeJson(name, value) {
  const file = path.join(tmp, name);
  fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
  return file;
}

function run(args) {
  return spawnSync(process.execPath, [benchmark, ...args], {
    cwd: repoRoot,
    encoding: 'utf8',
  });
}

function renameCandidateIds(candidate, mapping) {
  for (const collection of ['components', 'nodes', 'participants', 'states']) {
    for (const node of candidate[collection] || []) {
      node.id = mapping.get(node.id) || node.id;
    }
  }
  for (const collection of ['connections', 'edges', 'messages', 'flows', 'transitions']) {
    for (const relationship of candidate[collection] || []) {
      relationship.from = mapping.get(relationship.from) || relationship.from;
      relationship.to = mapping.get(relationship.to) || relationship.to;
    }
  }
  for (const activation of candidate.activations || []) {
    activation.participant = mapping.get(activation.participant) || activation.participant;
  }
  if (Array.isArray(candidate.mainPath)) {
    candidate.mainPath = candidate.mainPath.map((id) => mapping.get(id) || id);
  }
  for (const boundary of candidate.boundaries || []) {
    boundary.wraps = boundary.wraps.map((id) => mapping.get(id) || id);
  }
  for (const view of candidate.meta?.views || []) {
    view.focus = view.focus.map((id) => mapping.get(id) || id);
  }
  return candidate;
}

test('benchmark verifies one first-pass architecture candidate through semantic, renderer, and visual-review gates', () => {
  const caseFile = writeJson('web-runtime.case.json', {
    schema_version: 1,
    id: 'web-runtime-architecture',
    diagram_type: 'architecture',
    quality_profile: 'showcase',
    requirements: {
      node_ids: ['users', 'cdn', 'lb', 'api', 'db'],
      relationships: [
        { from: 'users', to: 'cdn' },
        { from: 'cdn', to: 'lb' },
        { from: 'lb', to: 'api' },
        { from: 'api', to: 'db' },
      ],
    },
  });
  const runFile = writeJson('web-runtime.run.json', {
    schema_version: 1,
    case_id: 'web-runtime-architecture',
    agent: 'fixture-agent',
    model: 'fixture-model',
    attempt: 1,
    visual_review: {
      status: 'passed',
      reviewer: 'fixture-reviewer',
      defects: [],
    },
  });
  const candidate = path.join(skillRoot, 'examples/web-app.architecture.json');

  const result = run(['verify', '--case', caseFile, '--candidate', candidate, '--run', runFile]);

  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.equal(result.stderr, '');
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.schemaVersion, 1);
  assert.equal(receipt.benchmark, 'ordinary-model-floor');
  assert.equal(receipt.caseId, 'web-runtime-architecture');
  assert.deepEqual(receipt.run, {
    agent: 'fixture-agent',
    model: 'fixture-model',
    attempt: 1,
  });
  assert.equal(receipt.gates.semantic.ok, true);
  assert.deepEqual(receipt.gates.semantic.missingNodeIds, []);
  assert.deepEqual(receipt.gates.semantic.missingRelationships, []);
  assert.equal(receipt.gates.validation.ok, true);
  assert.equal(receipt.gates.validation.checksPassed, 9);
  assert.deepEqual(receipt.gates.validation.composition, { errors: 0, warnings: 0 });
  assert.deepEqual(receipt.gates.visualReview, {
    status: 'passed',
    reviewer: 'fixture-reviewer',
    defects: [],
  });
  assert.equal(receipt.firstPassUsable, true);
});

test('benchmark rejects a renderer-valid candidate that changes required technical roles or relationship labels', () => {
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  source.components.find((component) => component.id === 'cache').type = 'frontend';
  source.connections.find((connection) => connection.from === 'api' && connection.to === 'db').label = 'HTTP';
  const candidate = writeJson('semantic-drift.architecture.json', source);
  const caseFile = writeJson('semantic-drift.case.json', {
    schema_version: 1,
    id: 'semantic-drift-architecture',
    diagram_type: 'architecture',
    quality_profile: 'showcase',
    requirements: {
      nodes: [
        { id: 'cache', type: 'database' },
        { id: 'db', type: 'database' },
      ],
      relationships: [
        { from: 'api', to: 'cache', label: 'read-through' },
        { from: 'api', to: 'db', label: 'SQL' },
      ],
    },
  });
  const runFile = writeJson('semantic-drift.run.json', {
    schema_version: 1,
    case_id: 'semantic-drift-architecture',
    agent: 'fixture-agent',
    model: 'fixture-model',
    attempt: 1,
    visual_review: {
      status: 'passed',
      reviewer: 'fixture-reviewer',
      defects: [],
    },
  });

  const result = run(['verify', '--case', caseFile, '--candidate', candidate, '--run', runFile]);

  assert.equal(result.status, 1, result.stderr || result.stdout);
  assert.equal(result.stderr, '');
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.gates.validation.ok, true, 'the deterministic renderer should still accept this controlled drift');
  assert.equal(receipt.gates.semantic.ok, false);
  assert.deepEqual(receipt.gates.semantic.mismatchedNodes, [
    { id: 'cache', field: 'type', expected: 'database', actual: 'frontend' },
  ]);
  assert.deepEqual(receipt.gates.semantic.missingRelationships, [
    { from: 'api', to: 'db', label: 'SQL' },
  ]);
  assert.equal(receipt.firstPassUsable, false);
});

test('benchmark never accepts a visual pass without an identified reviewer', () => {
  const caseFile = writeJson('unreviewed.case.json', {
    schema_version: 1,
    id: 'unreviewed-architecture',
    diagram_type: 'architecture',
    quality_profile: 'showcase',
    requirements: {
      node_ids: ['users', 'api', 'db'],
      relationships: [{ from: 'api', to: 'db' }],
    },
  });
  const runFile = writeJson('unreviewed.run.json', {
    schema_version: 1,
    case_id: 'unreviewed-architecture',
    agent: 'fixture-agent',
    model: 'fixture-model',
    attempt: 1,
    visual_review: {
      status: 'passed',
      reviewer: '',
      defects: [],
    },
  });
  const candidate = path.join(skillRoot, 'examples/web-app.architecture.json');

  const result = run(['verify', '--case', caseFile, '--candidate', candidate, '--run', runFile]);

  assert.equal(result.status, 1, result.stderr || result.stdout);
  assert.equal(result.stderr, '');
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.gates.semantic.ok, true);
  assert.equal(receipt.gates.validation.ok, true);
  assert.deepEqual(receipt.gates.visualReview, {
    status: 'invalid',
    reviewer: null,
    defects: [],
    reason: 'passed visual review requires a non-empty reviewer identity',
  });
  assert.equal(receipt.firstPassUsable, false);
});

test('benchmark applies the same semantic and delivery seam to workflow, sequence, data-flow, and lifecycle candidates', () => {
  const cases = [
    {
      type: 'workflow',
      example: 'agent-tool-call.workflow.json',
      nodes: [{ id: 'approval', type: 'security' }, { id: 'tool', type: 'messagebus' }],
      relationships: [{ from: 'router', to: 'approval', label: 'needs approval?' }],
    },
    {
      type: 'sequence',
      example: 'cache-miss-request.sequence.json',
      nodes: [{ id: 'redis', type: 'database' }, { id: 'db', type: 'database' }],
      relationships: [{ from: 'redis', to: 'api', label: 'miss' }],
    },
    {
      type: 'dataflow',
      example: 'product-analytics.dataflow.json',
      nodes: [{ id: 'consent', type: 'security' }, { id: 'pii', type: 'security' }],
      relationships: [{ from: 'consent', to: 'pii', label: 'identity map' }],
    },
    {
      type: 'lifecycle',
      example: 'agent-run.lifecycle.json',
      nodes: [{ id: 'approval', type: 'waiting' }, { id: 'cancelled', type: 'failure' }],
      relationships: [{ from: 'approval', to: 'cancelled', variant: 'security' }],
    },
  ];

  for (const item of cases) {
    const caseId = `${item.type}-representative`;
    const caseFile = writeJson(`${caseId}.case.json`, {
      schema_version: 1,
      id: caseId,
      diagram_type: item.type,
      quality_profile: 'showcase',
      requirements: {
        nodes: item.nodes,
        relationships: item.relationships,
      },
    });
    const runFile = writeJson(`${caseId}.run.json`, {
      schema_version: 1,
      case_id: caseId,
      agent: 'fixture-agent',
      model: 'fixture-model',
      attempt: 1,
      visual_review: {
        status: 'passed',
        reviewer: 'fixture-reviewer',
        defects: [],
      },
    });
    const candidate = path.join(skillRoot, 'examples', item.example);

    const result = run(['verify', '--case', caseFile, '--candidate', candidate, '--run', runFile]);

    assert.equal(result.status, 0, `${item.type}: ${result.stderr || result.stdout}`);
    const receipt = JSON.parse(result.stdout);
    assert.equal(receipt.gates.semantic.ok, true, item.type);
    assert.equal(receipt.gates.validation.ok, true, item.type);
    assert.equal(receipt.firstPassUsable, true, item.type);
  }
});

test('benchmark report separates first-pass usable rate from semantic, validation, and visual-review failures by configuration', () => {
  const resultsFile = path.join(tmp, 'benchmark-results.jsonl');
  const rows = [
    {
      schemaVersion: 1,
      benchmark: 'ordinary-model-floor',
      caseId: 'architecture-runtime',
      run: { agent: 'codex', model: 'strong', attempt: 1 },
      gates: {
        semantic: { ok: true },
        validation: { ok: true },
        visualReview: { status: 'passed', reviewer: 'reviewer', defects: [] },
      },
      firstPassUsable: true,
    },
    {
      schemaVersion: 1,
      benchmark: 'ordinary-model-floor',
      caseId: 'sequence-cache-miss',
      run: { agent: 'codex', model: 'strong', attempt: 1 },
      gates: {
        semantic: { ok: true },
        validation: { ok: true },
        visualReview: { status: 'passed', reviewer: 'reviewer', defects: [] },
      },
      firstPassUsable: true,
    },
    {
      schemaVersion: 1,
      benchmark: 'ordinary-model-floor',
      caseId: 'architecture-runtime',
      run: { agent: 'opencode', model: 'ordinary', attempt: 1 },
      gates: {
        semantic: { ok: false },
        validation: { ok: true },
        visualReview: { status: 'passed', reviewer: 'reviewer', defects: [] },
      },
      firstPassUsable: false,
    },
    {
      schemaVersion: 1,
      benchmark: 'ordinary-model-floor',
      caseId: 'sequence-cache-miss',
      run: { agent: 'opencode', model: 'ordinary', attempt: 1 },
      gates: {
        semantic: { ok: true },
        validation: { ok: false },
        visualReview: { status: 'skipped', reviewer: null, defects: [] },
      },
      firstPassUsable: false,
    },
  ];
  fs.writeFileSync(resultsFile, `${rows.map((row) => JSON.stringify(row)).join('\n')}\n`);

  const result = run(['report', '--results', resultsFile]);

  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.equal(result.stderr, '');
  const report = JSON.parse(result.stdout);
  assert.equal(report.schemaVersion, 1);
  assert.equal(report.benchmark, 'ordinary-model-floor');
  assert.deepEqual(report.overall, {
    runs: 4,
    firstPassUsable: 2,
    firstPassUsableRate: 0.5,
    failureClusters: {
      semantic: 1,
      validation: 1,
      visualReview: 1,
      operational: 0,
    },
  });
  assert.deepEqual(report.byConfiguration, [
    {
      agent: 'codex',
      model: 'strong',
      runs: 2,
      firstPassUsable: 2,
      firstPassUsableRate: 1,
      failureClusters: { semantic: 0, validation: 0, visualReview: 0, operational: 0 },
    },
    {
      agent: 'opencode',
      model: 'ordinary',
      runs: 2,
      firstPassUsable: 0,
      firstPassUsableRate: 0,
      failureClusters: { semantic: 1, validation: 1, visualReview: 1, operational: 0 },
    },
  ]);
});

test('benchmark records a timeout without a candidate as a complete first-pass failure', () => {
  const caseFile = writeJson('timeout.case.json', {
    schema_version: 1,
    id: 'timeout-architecture',
    diagram_type: 'architecture',
    requirements: { nodes: [] },
  });
  const runFile = writeJson('timeout.run.json', {
    schema_version: 1,
    case_id: 'timeout-architecture',
    agent: 'pi',
    model: 'glm-5.2-low',
    attempt: 1,
  });

  const recorded = run([
    'record-failure',
    '--case', caseFile,
    '--run', runFile,
    '--failure', 'timeout',
  ]);

  assert.equal(recorded.status, 1, recorded.stderr || recorded.stdout);
  assert.equal(recorded.stderr, '');
  const failureReceipt = JSON.parse(recorded.stdout);
  assert.deepEqual(failureReceipt.operational, { status: 'failed', reason: 'timeout' });
  assert.equal(failureReceipt.gates.semantic.status, 'not_run');
  assert.equal(failureReceipt.gates.validation.status, 'not_run');
  assert.equal(failureReceipt.gates.visualReview.status, 'skipped');
  assert.equal(failureReceipt.firstPassUsable, false);

  const resultsFile = path.join(tmp, 'timeout-results.jsonl');
  fs.writeFileSync(resultsFile, `${JSON.stringify(failureReceipt)}\n`);
  const manifestFile = writeJson('timeout.manifest.json', {
    id: 'timeout-suite',
    cases: [{ case: caseFile }],
  });
  const reported = run(['report', '--results', resultsFile, '--manifest', manifestFile]);

  assert.equal(reported.status, 0, reported.stderr || reported.stdout);
  const report = JSON.parse(reported.stdout);
  assert.equal(report.evidenceEligible, true);
  assert.deepEqual(report.overall.failureClusters, {
    semantic: 0,
    validation: 0,
    visualReview: 0,
    operational: 1,
  });
  assert.equal(report.overall.firstPassUsableRate, 0);
});

test('benchmark semantic requirements bind by accepted technical labels instead of forcing model-authored internal IDs', () => {
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  source.components.find((component) => component.id === 'users').label = 'Browser Users';
  const rename = new Map([
    ['users', 'browser-users-v1'],
    ['api', 'service-api-v1'],
    ['cache', 'redis-cache-v1'],
  ]);
  for (const component of source.components) component.id = rename.get(component.id) || component.id;
  for (const connection of source.connections) {
    connection.from = rename.get(connection.from) || connection.from;
    connection.to = rename.get(connection.to) || connection.to;
  }
  source.connections.find(
    (connection) => connection.from === 'service-api-v1' && connection.to === 'redis-cache-v1',
  ).label = 'cache read-through GET / SET';
  for (const view of source.meta.views || []) {
    view.focus = view.focus.map((id) => rename.get(id) || id);
  }
  for (const boundary of source.boundaries || []) {
    boundary.wraps = boundary.wraps.map((id) => rename.get(id) || id);
  }
  const candidate = writeJson('semantic-aliases.architecture.json', source);
  const caseFile = writeJson('semantic-aliases.case.json', {
    schema_version: 1,
    id: 'semantic-aliases-architecture',
    diagram_type: 'architecture',
    quality_profile: 'showcase',
    requirements: {
      nodes: [
        { key: 'users', labels: ['Users'], type: 'external' },
        { key: 'api', labels: ['API', 'API Server'], type: 'backend' },
        { key: 'cache', labels: ['Redis', 'Redis Cache'], type: 'database' },
      ],
      relationships: [
        { from: 'api', to: 'cache', labels: ['read-through', 'cache read'] },
      ],
    },
  });
  const runFile = writeJson('semantic-aliases.run.json', {
    schema_version: 1,
    case_id: 'semantic-aliases-architecture',
    agent: 'fixture-agent',
    model: 'fixture-model',
    attempt: 1,
    visual_review: {
      status: 'passed',
      reviewer: 'fixture-reviewer',
      defects: [],
    },
  });

  const result = run(['verify', '--case', caseFile, '--candidate', candidate, '--run', runFile]);

  assert.equal(result.status, 0, result.stderr || result.stdout);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.gates.semantic.ok, true);
  assert.deepEqual(receipt.gates.semantic.bindings, {
    users: 'browser-users-v1',
    api: 'service-api-v1',
    cache: 'redis-cache-v1',
  });
  assert.equal(receipt.firstPassUsable, true);
});

test('checked-in cases accept equivalent ordinary-model vocabulary without weakening required topology', () => {
  const suiteRoot = path.join(repoRoot, 'benchmarks/ordinary-model-floor');
  const fixtures = [
    {
      type: 'architecture',
      example: 'web-app.architecture.json',
      caseFile: 'web-runtime.architecture.case.json',
      mapping: new Map([
        ['users', 'browser-clients'], ['cdn', 'cdn-edge'], ['api', 'app-api'],
        ['cache', 'redis-store'], ['db', 'postgres-primary'],
      ]),
      mutate(candidate) {
        candidate.components.find((node) => node.id === 'browser-clients').label = 'Browser Clients';
        candidate.components.find((node) => node.id === 'cdn-edge').label = 'CDN Edge';
        candidate.components.find((node) => node.id === 'app-api').label = 'App API';
      },
    },
    {
      type: 'workflow',
      example: 'agent-tool-call.workflow.json',
      caseFile: 'agent-tool-call.workflow.case.json',
      mapping: new Map([
        ['planner', 'task-planner'], ['router', 'risk-router'], ['approval', 'consent-check'],
        ['tool', 'tool-runner'], ['blocked', 'request-blocked'], ['external', 'service-provider'],
      ]),
      mutate(candidate) {
        candidate.nodes.find((node) => node.id === 'task-planner').label = 'Intent Planner';
        Object.assign(candidate.nodes.find((node) => node.id === 'risk-router'), { label: 'Route Decision', type: 'security' });
        candidate.nodes.find((node) => node.id === 'consent-check').label = 'Approval';
        candidate.nodes.find((node) => node.id === 'tool-runner').label = 'Tool Dispatch';
        candidate.nodes.find((node) => node.id === 'request-blocked').label = 'Held';
        candidate.nodes.find((node) => node.id === 'service-provider').label = 'Remote API';
        candidate.edges.find((edge) => edge.from === 'risk-router' && edge.to === 'consent-check').label = 'requires approval?';
      },
    },
    {
      type: 'sequence',
      example: 'cache-miss-request.sequence.json',
      caseFile: 'cache-miss.sequence.case.json',
      mapping: new Map([
        ['web', 'browser-tab'], ['api', 'dashboard-api'], ['auth', 'token-guard'],
        ['redis', 'response-cache'], ['db', 'account-store'],
      ]),
      mutate(candidate) {
        Object.assign(candidate.participants.find((node) => node.id === 'browser-tab'), { label: 'Browser', type: 'external' });
        candidate.participants.find((node) => node.id === 'dashboard-api').label = 'Dashboard API';
        candidate.participants.find((node) => node.id === 'token-guard').label = 'JWT Guard';
        candidate.messages.find((message) => message.id === 'verify-jwt').label = 'verify Bearer JWT';
        candidate.messages.find((message) => message.id === 'cache-read').label = 'GET dashboard key';
        candidate.messages.find((message) => message.id === 'profile-query').label = 'SELECT profile + metrics';
        candidate.messages.find((message) => message.id === 'cache-write').label = 'SETEX profile 300';
      },
    },
    {
      type: 'dataflow',
      example: 'product-analytics.dataflow.json',
      caseFile: 'product-analytics.dataflow.case.json',
      mapping: new Map([
        ['edge', 'edge-collector'], ['consent', 'consent-policy'], ['stream', 'event-stream'],
        ['pii', 'identity-vault'], ['warehouse', 'facts-warehouse'], ['dashboard', 'metric-dashboards'],
      ]),
      mutate(candidate) {
        candidate.nodes.find((node) => node.id === 'edge-collector').label = 'Edge Ingestion';
        candidate.nodes.find((node) => node.id === 'consent-policy').label = 'Consent Policy';
        candidate.nodes.find((node) => node.id === 'identity-vault').label = 'Identity Vault';
        candidate.nodes.find((node) => node.id === 'facts-warehouse').label = 'Analytics Warehouse';
        candidate.nodes.find((node) => node.id === 'metric-dashboards').label = 'Analytics UI';
        candidate.flows.find((flow) => flow.id === 'consent-enrichment').label = 'identity context';
        candidate.flows.find((flow) => flow.id === 'accepted-events').label = 'telemetry';
        candidate.flows.find((flow) => flow.id === 'identity-map').label = 'encrypted identity';
        candidate.flows.find((flow) => flow.id === 'metrics-query').label = 'metrics query';
      },
    },
    {
      type: 'lifecycle',
      example: 'agent-run.lifecycle.json',
      caseFile: 'agent-run.lifecycle.case.json',
      mapping: new Map([
        ['queued', 'run-queued'], ['executing', 'run-executing'], ['reviewing', 'run-reviewing'],
        ['approval', 'approval-wait'], ['blocked', 'input-wait'], ['cancelled', 'user-cancelled'],
        ['expired', 'run-expired'],
      ]),
      mutate(candidate) {
        candidate.states.find((node) => node.id === 'approval-wait').label = 'Approval Pending';
        candidate.states.find((node) => node.id === 'input-wait').label = 'Pending Input';
        candidate.states.push({
          id: 'fatal-failure',
          type: 'failure',
          label: 'Failed',
          sublabel: 'budget exhausted',
          lane: 'terminal',
          col: 2,
          tag: 'terminal',
        });
      },
    },
  ];

  for (const fixture of fixtures) {
    const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', fixture.example), 'utf8'));
    const candidate = renameCandidateIds(source, fixture.mapping);
    fixture.mutate(candidate);
    const candidateFile = writeJson(`calibrated-${fixture.type}.json`, candidate);
    const benchmarkCase = JSON.parse(fs.readFileSync(
      path.join(suiteRoot, 'cases', fixture.caseFile),
      'utf8',
    ));
    const runFile = writeJson(`calibrated-${fixture.type}.run.json`, {
      schema_version: 1,
      case_id: benchmarkCase.id,
      agent: 'ordinary-agent',
      model: 'ordinary-model',
      attempt: 1,
      visual_review: { status: 'passed', reviewer: 'fixture-reviewer', defects: [] },
    });

    const result = run([
      'verify', '--case', path.join(suiteRoot, 'cases', fixture.caseFile),
      '--candidate', candidateFile, '--run', runFile,
    ]);

    assert.equal(result.status, 0, `${fixture.type}: ${result.stderr || result.stdout}`);
    assert.equal(JSON.parse(result.stdout).gates.semantic.ok, true, fixture.type);

    if (fixture.type === 'workflow') {
      const wrongRoleCandidate = structuredClone(candidate);
      wrongRoleCandidate.nodes.find((node) => node.id === 'risk-router').type = 'external';
      const wrongRoleFile = writeJson('calibrated-workflow-wrong-role.json', wrongRoleCandidate);
      const wrongRole = run([
        'verify', '--case', path.join(suiteRoot, 'cases', fixture.caseFile),
        '--candidate', wrongRoleFile, '--run', runFile,
      ]);
      assert.equal(wrongRole.status, 1, wrongRole.stderr || wrongRole.stdout);
      assert.deepEqual(JSON.parse(wrongRole.stdout).gates.semantic.mismatchedNodes, [{
        id: 'router',
        field: 'type',
        expected: ['backend', 'security'],
        actual: 'external',
      }]);

      candidate.edges = candidate.edges.filter(
        (edge) => !(edge.from === 'consent-check' && edge.to === 'tool-runner'),
      );
      const brokenCandidate = writeJson('calibrated-workflow-missing-route.json', candidate);
      const broken = run([
        'verify', '--case', path.join(suiteRoot, 'cases', fixture.caseFile),
        '--candidate', brokenCandidate, '--run', runFile,
      ]);
      assert.equal(broken.status, 1, broken.stderr || broken.stdout);
      assert.equal(JSON.parse(broken.stdout).gates.semantic.missingRelationships.length, 1);
    }
  }
});

test('checked-in benchmark suite covers all five diagram types without presenting reference fixtures as model evidence', () => {
  const manifest = path.join(repoRoot, 'benchmarks/ordinary-model-floor/manifest.json');

  const result = run(['check', '--manifest', manifest]);

  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.equal(result.stderr, '');
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.schemaVersion, 1);
  assert.equal(receipt.benchmark, 'ordinary-model-floor');
  assert.equal(receipt.suiteId, 'ordinary-model-floor-v1');
  assert.equal(receipt.purpose, 'suite-integrity');
  assert.equal(receipt.evidenceEligible, false);
  assert.equal(receipt.caseCount, 5);
  assert.deepEqual(receipt.diagramTypes, [
    'architecture',
    'dataflow',
    'lifecycle',
    'sequence',
    'workflow',
  ]);
  assert.equal(receipt.cases.length, 5);
  for (const item of receipt.cases) {
    assert.equal(item.promptOk, true, item.caseId);
    assert.equal(item.semanticOk, true, item.caseId);
    assert.equal(item.validationOk, true, item.caseId);
  }
});

test('checked-in prompts permit bundled CLI repair while retaining external validation authority', () => {
  const suiteRoot = path.join(repoRoot, 'benchmarks/ordinary-model-floor');
  const manifest = JSON.parse(fs.readFileSync(path.join(suiteRoot, 'manifest.json'), 'utf8'));

  for (const entry of manifest.cases) {
    const prompt = fs.readFileSync(path.join(suiteRoot, entry.prompt), 'utf8');
    assert.match(
      prompt,
      /Use the bundled Archify CLI to validate and repair the candidate when shell access is available\./,
      entry.prompt,
    );
    assert.match(
      prompt,
      /The external harness will independently validate the frozen candidate\./,
      entry.prompt,
    );
  }
});

test('suite integrity rejects a long prompt that omits the attempt-1 file contract', () => {
  const sourceSuite = path.join(repoRoot, 'benchmarks/ordinary-model-floor');
  const incompletePrompt = path.join(tmp, 'incomplete-benchmark-prompt.md');
  fs.writeFileSync(
    incompletePrompt,
    `# Plausible but incomplete prompt\n\n${'Describe the requested system accurately. '.repeat(12)}`,
  );
  const manifest = JSON.parse(fs.readFileSync(path.join(sourceSuite, 'manifest.json'), 'utf8'));
  manifest.cases = manifest.cases.map((entry, index) => ({
    ...entry,
    case: path.resolve(sourceSuite, entry.case),
    prompt: index === 0 ? incompletePrompt : path.resolve(sourceSuite, entry.prompt),
    reference_fixture: path.resolve(sourceSuite, entry.reference_fixture),
  }));
  const manifestFile = writeJson('prompt-contract.manifest.json', manifest);

  const result = run(['check', '--manifest', manifestFile]);

  assert.equal(result.status, 1, result.stderr || result.stdout);
  assert.equal(result.stderr, '');
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.cases.find((item) => item.caseId === 'web-runtime-architecture').promptOk, false);
});

test('benchmark fails closed with machine-readable errors for malformed JSON and mismatched run identity', () => {
  const caseFile = writeJson('identity.case.json', {
    schema_version: 1,
    id: 'identity-case',
    diagram_type: 'architecture',
    requirements: { node_ids: ['api'] },
  });
  const candidate = path.join(skillRoot, 'examples/web-app.architecture.json');
  const mismatchedRun = writeJson('identity.run.json', {
    schema_version: 1,
    case_id: 'different-case',
    agent: 'fixture-agent',
    model: 'fixture-model',
    attempt: 1,
    visual_review: { status: 'skipped', reviewer: null, defects: [] },
  });

  const mismatch = run(['verify', '--case', caseFile, '--candidate', candidate, '--run', mismatchedRun]);

  assert.equal(mismatch.status, 2, mismatch.stderr || mismatch.stdout);
  assert.equal(mismatch.stderr, '');
  assert.deepEqual(JSON.parse(mismatch.stdout), {
    schemaVersion: 1,
    benchmark: 'ordinary-model-floor',
    error: {
      code: 'RUN_CASE_MISMATCH',
      message: 'run case_id "different-case" does not match benchmark case "identity-case"',
    },
  });

  const malformedCandidate = path.join(tmp, 'malformed.architecture.json');
  fs.writeFileSync(malformedCandidate, '{ definitely not JSON\n');
  const validRun = writeJson('valid-identity.run.json', {
    schema_version: 1,
    case_id: 'identity-case',
    agent: 'fixture-agent',
    model: 'fixture-model',
    attempt: 1,
    visual_review: { status: 'skipped', reviewer: null, defects: [] },
  });

  const malformed = run(['verify', '--case', caseFile, '--candidate', malformedCandidate, '--run', validRun]);

  assert.equal(malformed.status, 2, malformed.stderr || malformed.stdout);
  assert.equal(malformed.stderr, '');
  const malformedReceipt = JSON.parse(malformed.stdout);
  assert.equal(malformedReceipt.error.code, 'INVALID_JSON');
  assert.match(malformedReceipt.error.message, /malformed\.architecture\.json/);
  assert.doesNotMatch(malformed.stdout, /SyntaxError|at JSON\.parse/);
});

test('benchmark report marks only a complete first-pass matrix as evidence and rejects duplicate runs', () => {
  const manifestFile = path.join(repoRoot, 'benchmarks/ordinary-model-floor/manifest.json');
  const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
  const rows = manifest.cases.map((entry) => {
    const benchmarkCase = JSON.parse(fs.readFileSync(
      path.resolve(path.dirname(manifestFile), entry.case),
      'utf8',
    ));
    return {
      schemaVersion: 1,
      benchmark: 'ordinary-model-floor',
      caseId: benchmarkCase.id,
      run: { agent: 'fixture-agent', model: 'ordinary-model', attempt: 1 },
      gates: {
        semantic: { ok: true },
        validation: { ok: true },
        visualReview: { status: 'passed', reviewer: 'human-reviewer', defects: [] },
      },
      firstPassUsable: true,
    };
  });
  const completeResults = path.join(tmp, 'complete-results.jsonl');
  fs.writeFileSync(completeResults, `${rows.map((row) => JSON.stringify(row)).join('\n')}\n`);

  const complete = run(['report', '--results', completeResults, '--manifest', manifestFile]);

  assert.equal(complete.status, 0, complete.stderr || complete.stdout);
  const report = JSON.parse(complete.stdout);
  assert.equal(report.suiteId, 'ordinary-model-floor-v1');
  assert.equal(report.evidenceEligible, true);
  assert.deepEqual(report.coverage, [{
    agent: 'fixture-agent',
    model: 'ordinary-model',
    expected: 5,
    present: 5,
    missingCaseIds: [],
    unexpectedCaseIds: [],
    complete: true,
  }]);

  const duplicateResults = path.join(tmp, 'duplicate-results.jsonl');
  fs.writeFileSync(
    duplicateResults,
    `${[...rows, rows[0]].map((row) => JSON.stringify(row)).join('\n')}\n`,
  );

  const duplicate = run(['report', '--results', duplicateResults, '--manifest', manifestFile]);

  assert.equal(duplicate.status, 2, duplicate.stderr || duplicate.stdout);
  assert.equal(duplicate.stderr, '');
  assert.equal(JSON.parse(duplicate.stdout).error.code, 'DUPLICATE_RESULT');
});

test('benchmark documentation locks the fair-run and truthful-evidence contract', () => {
  const readme = fs.readFileSync(
    path.join(repoRoot, 'benchmarks/ordinary-model-floor/README.md'),
    'utf8',
  );

  for (const required of [
    'firstPassUsable',
    'same prompt',
    'same repository commit',
    'packaged skill root',
    'model-visible working tree',
    'bundled Archify CLI',
    'independently revalidates',
    'attempt 1',
    'no post-hoc edits',
    'Reference fixtures are not benchmark evidence',
    '`record-failure`',
    '`timeout`',
    '`no_candidate`',
    '`provider_error`',
    '`passed`',
    '`failed`',
    '`skipped`',
    'check --manifest',
    'verify --case',
    'report --results',
  ]) {
    assert.match(readme, new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), required);
  }
});

test('packaged skill puts a bounded ordinary-model path before progressive feature references', () => {
  const skill = fs.readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8');
  const authoring = fs.readFileSync(path.join(skillRoot, 'references', 'authoring-contract.md'), 'utf8');
  const viewer = fs.readFileSync(path.join(skillRoot, 'references', 'viewer-runtime.md'), 'utf8');
  const fastPath = skill.indexOf('## Fast authoring path');
  const progressiveReferences = skill.indexOf('references/authoring-contract.md');

  assert.ok(fastPath > 0, 'fast authoring path must exist');
  assert.ok(fastPath < progressiveReferences, 'fast authoring path must precede progressive references');
  assert.ok(skill.trimEnd().split('\n').length <= 160, 'ordinary authors must not ingest the viewer catalogue');
  for (const required of [
    'one matching schema',
    'one matching JSON example',
    'the next tool action must write the candidate',
    'Do not plan exact coordinates in prose',
    'Fresh authorship means new stable IDs, domain wording, and layout',
    'Write the candidate before inspecting renderer internals',
    'Start with automatic routes and labels',
    'Do not add `via`, `channelX`, `channelY`, or `labelAt` before a diagnostic',
    'Set `meta.quality_profile` to `"showcase"`',
    'A recoverable state uses `type: "failure"` plus a real transition back to the active state',
    'after every candidate edit',
    'A passing final validation freezes the candidate: never edit it afterward',
    'A receipt with only 4 artifact checks is basic validation, never showcase acceptance',
    'a showcase pass must report all 9 artifact checks with 0 composition errors and 0 warnings',
    'If the candidate omits or misspells the exact `meta.quality_profile` field',
    '`deliver` is the final acceptance command',
    'deliver <type> <candidate.json> <output.html> --quality showcase --json',
    'A non-zero exit can never be described as success',
    'Continue focused correction while the objective error count reaches a new minimum',
    'If two consecutive rounds do not improve that best count',
    'Do not read `renderers/shared/geometry.mjs`',
    'validate <type>',
    'supportedFixes',
  ]) {
    assert.match(
      skill.slice(fastPath, progressiveReferences),
      new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'),
    );
  }
  assert.match(authoring, /componentType/);
  assert.match(authoring, /clear gap between boxes, not center distance/i);
  assert.match(viewer, /Direct Relationship Pin/);
});

test('dated three-model evidence retains every frozen attempt-1 candidate and truthful gate result', () => {
  const evidence = JSON.parse(fs.readFileSync(path.join(
    repoRoot,
    'benchmarks/ordinary-model-floor/results/2026-07-26-pi-three-models.json',
  ), 'utf8'));

  assert.equal(evidence.generation.repositoryCommit, '66414c7d2366d16a70c9e7282836e416b7917d51');
  assert.equal(evidence.generation.packageSha256, '1f32354a466ec10c21f56346634ce66e170b6e0d23306cc2e9a9cbb68283f05a');
  assert.equal(evidence.generation.attempt, 1);
  assert.equal(evidence.report.evidenceEligible, true);
  assert.deepEqual(evidence.report.overall, {
    runs: 15,
    firstPassUsable: 10,
    firstPassUsableRate: 2 / 3,
    failureClusters: { semantic: 0, validation: 5, visualReview: 5, operational: 0 },
  });
  assert.equal(evidence.runs.length, 15);

  const identities = new Set();
  for (const entry of evidence.runs) {
    identities.add(`${entry.agent}\0${entry.model}\0${entry.caseId}`);
    assert.equal(entry.run.attempt, 1, entry.caseId);
    assert.equal(entry.run.case_id, entry.caseId, entry.caseId);
    assert.equal(entry.receipt.caseId, entry.caseId, entry.caseId);
    assert.equal(entry.receipt.gates.semantic.ok, true, entry.caseId);
    assert.equal(entry.candidate.schema_version, 1, entry.caseId);
    assert.ok(entry.candidate.diagram_type, entry.caseId);
  }
  assert.equal(identities.size, 15);
});

test('post-fix evidence keeps the complete matrix and reports the no-uplift comparison truthfully', () => {
  const evidence = JSON.parse(fs.readFileSync(path.join(
    repoRoot,
    'benchmarks/ordinary-model-floor/results/2026-07-26-pi-three-models-postfix.json',
  ), 'utf8'));

  assert.equal(evidence.generation.repositoryCommit, '2dce766ab19ff5871020828eb85d770745f1069d');
  assert.equal(evidence.generation.packageSha256, 'cc34ab9484ca84e43fce9fce3de612b11c525c6dd5c797646645c6f35954b24e');
  assert.equal(evidence.report.evidenceEligible, true);
  assert.deepEqual(evidence.report.overall, {
    runs: 15,
    firstPassUsable: 8,
    firstPassUsableRate: 8 / 15,
    failureClusters: { semantic: 2, validation: 6, visualReview: 7, operational: 0 },
  });
  assert.equal(evidence.comparison.baselineReverified.firstPassUsable, 8);
  assert.equal(evidence.comparison.postFix.firstPassUsable, 8);
  assert.match(evidence.comparison.outcome, /no measured overall uplift/i);

  const identities = new Set();
  for (const entry of evidence.runs) {
    identities.add(`${entry.agent}\0${entry.model}\0${entry.caseId}`);
    assert.equal(entry.run.attempt, 1, entry.caseId);
    assert.equal(entry.receipt.caseId, entry.caseId, entry.caseId);
    assert.equal(entry.candidate.schema_version, 1, entry.caseId);
    assert.ok(entry.transcript.length > 0, entry.caseId);
  }
  assert.equal(identities.size, 15);

  const qwenArchitecture = evidence.runs.find(
    (entry) => entry.model === 'codewiz-anthropic/qwen3.7-plus'
      && entry.caseId === 'web-runtime-architecture',
  );
  assert.equal(qwenArchitecture.receipt.firstPassUsable, true);
  assert.equal(qwenArchitecture.run.visual_review.reviewer, 'codex-browser-visual-audit-2026-07-26-route-fix');

  const minimaxLifecycle = evidence.runs.find(
    (entry) => entry.model === 'codewiz-anthropic/minimax-m3'
      && entry.caseId === 'agent-run-lifecycle',
  );
  assert.equal(minimaxLifecycle.receipt.gates.semantic.ok, false);
  assert.equal(minimaxLifecycle.receipt.gates.visualReview.status, 'failed');
  assert.ok(minimaxLifecycle.receipt.gates.visualReview.defects.includes(
    'recoverable-failure-has-no-retry-transition-to-execution',
  ));
});

test('quality-first evidence preserves the complete matrix and the measured lifecycle gain without overstating uplift', () => {
  const evidence = JSON.parse(fs.readFileSync(path.join(
    repoRoot,
    'benchmarks/ordinary-model-floor/results/2026-07-26-pi-three-models-quality-first.json',
  ), 'utf8'));

  assert.equal(evidence.generation.repositoryCommit, '7eef4db36a97d04da74a9cb1d1bc3f735058c074');
  assert.equal(evidence.generation.packageSha256, '92135b360ee1502080dac8f2eea6258bb8fa0aa7f9a59cba119b02233f797593');
  assert.equal(evidence.generation.timeLimitSeconds, null);
  assert.match(evidence.generation.latencyPolicy, /not a quality failure/i);
  assert.equal(evidence.generation.processRecovery, undefined);
  assert.ok(evidence.verification.calibratedAliases.includes('Admitted'));
  assert.equal(evidence.report.evidenceEligible, true);
  assert.deepEqual(evidence.report.overall, {
    runs: 15,
    firstPassUsable: 8,
    firstPassUsableRate: 8 / 15,
    failureClusters: { semantic: 2, validation: 5, visualReview: 7, operational: 0 },
  });
  assert.equal(evidence.comparison.baselineReverified.firstPassUsable, 8);
  assert.equal(evidence.comparison.postFixReverified.firstPassUsable, 8);
  assert.equal(evidence.comparison.qualityFirst.firstPassUsable, 8);
  assert.equal(evidence.comparison.qualityFirst.firstPassUsableByCase['agent-run-lifecycle'], 1);
  assert.equal(evidence.comparison.qualityFirst.firstPassUsableByCase['web-runtime-architecture'], 3);
  assert.match(evidence.comparison.outcome, /no measured overall uplift/i);

  const identities = new Set();
  for (const entry of evidence.runs) {
    identities.add(`${entry.agent}\0${entry.model}\0${entry.caseId}`);
    assert.equal(entry.run.attempt, 1, entry.caseId);
    assert.equal(entry.receipt.caseId, entry.caseId, entry.caseId);
    assert.equal(entry.candidate.schema_version, 1, entry.caseId);
    assert.ok(entry.transcript.length > 0, entry.caseId);
  }
  assert.equal(identities.size, 15);

  const minimaxLifecycle = evidence.runs.find(
    (entry) => entry.model === 'codewiz-anthropic/minimax-m3'
      && entry.caseId === 'agent-run-lifecycle',
  );
  assert.equal(minimaxLifecycle.receipt.firstPassUsable, true);
  assert.equal(minimaxLifecycle.receipt.gates.validation.checksPassed, 9);
  assert.ok(minimaxLifecycle.candidate.states.some(
    (state) => state.id === 'admitted' && state.label === 'Admitted',
  ));
  assert.equal(
    minimaxLifecycle.run.visual_review.reviewer,
    'codex-browser-visual-audit-2026-07-27-quality-first-clean-rerun',
  );
  assert.equal(
    minimaxLifecycle.receipt.gates.visualReview.reviewer,
    minimaxLifecycle.run.visual_review.reviewer,
  );

  const deepseekWorkflow = evidence.runs.find(
    (entry) => entry.model === 'seal/deepseek-v4-flash'
      && entry.caseId === 'agent-tool-call-workflow',
  );
  assert.equal(deepseekWorkflow.receipt.firstPassUsable, false);
  assert.ok(deepseekWorkflow.receipt.gates.visualReview.defects.includes(
    'card-claims-retry-loop-without-authored-return-edge',
  ));
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/output-path.test.mjs

```js
import { spawn, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import assert from 'node:assert/strict';
import { startPreview } from '../bin/preview.mjs';
import { loadDiagram, writeDiagram } from '../renderers/shared/cli.mjs';
import { pathsAlias } from '../renderers/shared/output-path.mjs';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const cli = path.join(skillRoot, 'bin/archify.mjs');
const workflowFixture = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const baseFixture = path.join(skillRoot, 'examples/checkout-platform.base.architecture.json');
const headFixture = path.join(skillRoot, 'examples/checkout-platform.head.architecture.json');

function run(args, cwd) {
  return spawnSync(process.execPath, [cli, ...args], {
    cwd,
    encoding: 'utf8',
  });
}

function copyInstalledSkill(target) {
  fs.cpSync(skillRoot, target, {
    recursive: true,
    filter(source) {
      const relative = path.relative(skillRoot, source);
      return relative !== 'node_modules'
        && !relative.startsWith(`node_modules${path.sep}`)
        && relative !== 'test'
        && !relative.startsWith(`test${path.sep}`);
    },
  });
}

function directoryAliasesNames(directory, authoredName, lookupName) {
  const authoredPath = path.join(directory, authoredName);
  const lookupPath = path.join(directory, lookupName);
  fs.writeFileSync(authoredPath, 'filesystem semantics probe', { flag: 'wx' });
  try {
    let authored;
    let lookup;
    try {
      authored = fs.statSync(authoredPath);
      lookup = fs.statSync(lookupPath);
    } catch (error) {
      if (error.code === 'ENOENT') return false;
      throw error;
    }
    return authored.dev === lookup.dev && authored.ino === lookup.ino;
  } finally {
    fs.unlinkSync(authoredPath);
  }
}

test('future-path aliases follow the containing directory case and Unicode semantics', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-semantics-'));
  const caseInsensitive = directoryAliasesNames(cwd, 'ArchifyCaseProbe', 'archifycaseprobe');
  const normalizationInsensitive = directoryAliasesNames(
    cwd,
    'archify-norm-\u00e9-probe',
    'archify-norm-e\u0301-probe',
  );

  assert.equal(
    pathsAlias(path.join(cwd, 'Future.HTML'), path.join(cwd, 'future.html')),
    caseInsensitive,
  );
  assert.equal(
    pathsAlias(path.join(cwd, 'Caf\u00e9.html'), path.join(cwd, 'Cafe\u0301.html')),
    normalizationInsensitive,
  );
});

test('compare rejects case-only future targets before input work when the directory aliases case', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-compare-case-'));
  const caseInsensitive = directoryAliasesNames(cwd, 'ArchifyCaseProbe', 'archifycaseprobe');
  const output = path.join(cwd, 'Future.HTML');
  const receiptPath = path.join(cwd, 'future.html');

  const result = run([
    'compare', 'architecture',
    path.join(cwd, 'missing-base.json'),
    path.join(cwd, 'missing-head.json'),
    output,
    '--receipt', receiptPath,
    '--json',
  ], cwd);

  assert.equal(result.status, 1);
  const receipt = JSON.parse(result.stdout);
  assert.equal(
    receipt.diagnostics[0].code,
    caseInsensitive ? 'output/target-alias' : 'output/cli-extension',
  );
  assert.equal(receipt.stage, 'prepare');
});

test('render reports an output symlink cycle as a structured output diagnostic', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-cycle-'));
  const input = path.join(cwd, 'diagram.workflow.json');
  const output = path.join(cwd, 'cycle-a.html');
  const otherLink = path.join(cwd, 'cycle-b.html');
  fs.copyFileSync(workflowFixture, input);
  fs.symlinkSync(otherLink, output, 'file');
  fs.symlinkSync(output, otherLink, 'file');

  const result = spawnSync(
    process.execPath,
    [path.join(skillRoot, 'renderers/workflow/render-workflow.mjs'), input, output],
    {
      cwd,
      encoding: 'utf8',
      env: { ...process.env, ARCHIFY_DIAGNOSTIC_FORMAT: 'json' },
    },
  );

  assert.equal(result.status, 1);
  const failure = JSON.parse(result.stderr);
  assert.equal(failure.diagnostics[0].code, 'output/symlink-cycle');
  assert.equal(failure.diagnostics[0].subject.output, output);
  assert.ok(failure.diagnostics[0].supportedFixes.length > 0);
});

test('render rejects an output symlink that aliases its JSON input', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-render-'));
  const input = path.join(cwd, 'diagram.workflow.json');
  const output = path.join(cwd, 'diagram.html');
  const source = fs.readFileSync(workflowFixture);
  fs.writeFileSync(input, source);
  fs.symlinkSync(input, output, 'file');

  const result = run(['render', 'workflow', input, output], cwd);

  assert.equal(result.status, 1);
  assert.match(result.stderr, /output must not replace an input/i);
  assert.deepEqual(fs.readFileSync(input), source);
});

test('render rejects an existing output hard link to its JSON input', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-render-hardlink-'));
  const input = path.join(cwd, 'diagram.workflow.json');
  const output = path.join(cwd, 'diagram.html');
  const source = fs.readFileSync(workflowFixture);
  fs.writeFileSync(input, source);
  fs.linkSync(input, output);

  const result = run(['render', 'workflow', input, output], cwd);

  assert.equal(result.status, 1);
  assert.match(result.stderr, /output must not replace an input/i);
  assert.deepEqual(fs.readFileSync(input), source);
});

test('render rejects an absolute meta.output when no CLI output is provided', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-meta-absolute-'));
  const input = path.join(cwd, 'diagram.workflow.json');
  const output = path.join(cwd, 'authored.html');
  const source = JSON.parse(fs.readFileSync(workflowFixture, 'utf8'));
  source.meta.output = output;
  fs.writeFileSync(input, JSON.stringify(source));

  const result = run(['render', 'workflow', input], cwd);

  assert.equal(result.status, 1);
  assert.match(result.stderr, /meta\.output must be a relative path/i);
  assert.equal(fs.existsSync(output), false);
});

test('render rejects a relative meta.output that escapes the working directory', () => {
  const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-meta-parent-'));
  const cwd = path.join(parent, 'work');
  fs.mkdirSync(cwd);
  const input = path.join(cwd, 'diagram.workflow.json');
  const output = path.join(parent, 'escaped.html');
  const source = JSON.parse(fs.readFileSync(workflowFixture, 'utf8'));
  source.meta.output = '../escaped.html';
  fs.writeFileSync(input, JSON.stringify(source));

  const result = run(['render', 'workflow', input], cwd);

  assert.equal(result.status, 1);
  assert.match(result.stderr, /meta\.output must stay inside the current working directory/i);
  assert.equal(fs.existsSync(output), false);
});

test('render rejects a meta.output that escapes through a directory symlink', () => {
  const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-meta-link-'));
  const cwd = path.join(parent, 'work');
  const outside = path.join(parent, 'outside');
  fs.mkdirSync(cwd);
  fs.mkdirSync(outside);
  fs.symlinkSync(outside, path.join(cwd, 'linked'), 'dir');
  const input = path.join(cwd, 'diagram.workflow.json');
  const output = path.join(outside, 'authored.html');
  const source = JSON.parse(fs.readFileSync(workflowFixture, 'utf8'));
  source.meta.output = 'linked/authored.html';
  fs.writeFileSync(input, JSON.stringify(source));

  const result = run(['render', 'workflow', input], cwd);

  assert.equal(result.status, 1);
  assert.match(result.stderr, /meta\.output must stay inside the current working directory/i);
  assert.equal(fs.existsSync(output), false);
});

test('render requires a meta.output target with an html extension', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-meta-extension-'));
  const input = path.join(cwd, 'diagram.workflow.json');
  const output = path.join(cwd, 'authored.json');
  const source = JSON.parse(fs.readFileSync(workflowFixture, 'utf8'));
  source.meta.output = 'authored.json';
  fs.writeFileSync(input, JSON.stringify(source));

  const result = run(['render', 'workflow', input], cwd);

  assert.equal(result.status, 1);
  assert.match(result.stderr, /meta\.output must target an? \.html file/i);
  assert.equal(fs.existsSync(output), false);
});

test('render rejects a meta.output symlink that resolves to a non-html target', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-meta-extension-link-'));
  const input = path.join(cwd, 'diagram.workflow.json');
  const target = path.join(cwd, 'authored.json');
  const output = path.join(cwd, 'authored.html');
  const source = JSON.parse(fs.readFileSync(workflowFixture, 'utf8'));
  source.meta.output = 'authored.html';
  fs.writeFileSync(input, JSON.stringify(source));
  fs.writeFileSync(target, 'trusted target');
  fs.symlinkSync(target, output, 'file');

  const result = run(['render', 'workflow', input], cwd);

  assert.equal(result.status, 1);
  assert.match(result.stderr, /meta\.output must resolve to an? \.html file/i);
  assert.equal(fs.readFileSync(target, 'utf8'), 'trusted target');
});

test('deliver rejects a future-path alias of its JSON input with a structured diagnostic', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-deliver-'));
  const realDirectory = path.join(cwd, 'real');
  const linkedDirectory = path.join(cwd, 'linked');
  fs.mkdirSync(realDirectory);
  fs.symlinkSync(realDirectory, linkedDirectory, 'dir');
  const input = path.join(realDirectory, 'diagram.workflow.json');
  const output = path.join(linkedDirectory, 'diagram.workflow.json');
  const source = fs.readFileSync(workflowFixture);
  fs.writeFileSync(input, source);

  const result = run(['deliver', 'workflow', input, output, '--json'], cwd);

  assert.equal(result.status, 1);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.stage, 'prepare');
  assert.equal(receipt.diagnostics[0].code, 'output/input-alias');
  assert.deepEqual(fs.readFileSync(input), source);
});

test('deliver rechecks aliases immediately before committing a verified candidate', { timeout: 10000 }, async () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-deliver-race-'));
  const installedRoot = path.join(cwd, 'skill');
  const installedBin = path.join(installedRoot, 'bin');
  const installedShared = path.join(installedRoot, 'renderers/shared');
  const installedRenderer = path.join(installedRoot, 'renderers/workflow');
  const installedScripts = path.join(installedRoot, 'scripts');
  fs.mkdirSync(installedBin, { recursive: true });
  fs.mkdirSync(installedShared, { recursive: true });
  fs.mkdirSync(installedRenderer, { recursive: true });
  fs.mkdirSync(installedScripts, { recursive: true });
  fs.copyFileSync(cli, path.join(installedBin, 'archify.mjs'));
  fs.copyFileSync(
    path.join(skillRoot, 'renderers/shared/output-path.mjs'),
    path.join(installedShared, 'output-path.mjs'),
  );
  fs.writeFileSync(path.join(installedRenderer, 'render-workflow.mjs'), `
import fs from 'node:fs';
const [, output] = process.argv.slice(2);
fs.writeFileSync(process.env.ARCHIFY_TEST_RENDER_STARTED, output);
await new Promise((resolve) => setTimeout(resolve, 500));
fs.writeFileSync(output, '<!doctype html><title>verified candidate</title><svg></svg>');
`);
  fs.writeFileSync(path.join(installedScripts, 'check-render-output.mjs'), `
console.log(JSON.stringify({
  ok: true,
  checks: [{ name: 'single_svg', ok: true }],
  composition: {
    profile: 'showcase',
    status: 'pass',
    summary: { errors: 0, warnings: 0 }
  }
}));
`);

  const inputDirectory = path.join(cwd, 'input');
  const initialOutputDirectory = path.join(cwd, 'safe-output');
  const linkedDirectory = path.join(cwd, 'linked-output');
  fs.mkdirSync(inputDirectory);
  fs.mkdirSync(initialOutputDirectory);
  fs.symlinkSync(initialOutputDirectory, linkedDirectory, 'dir');
  const input = path.join(inputDirectory, 'diagram.html');
  const output = path.join(linkedDirectory, 'diagram.html');
  const source = Buffer.from('{"meta":{"title":"race input"}}');
  fs.writeFileSync(input, source);
  const marker = path.join(cwd, 'renderer-started');

  const child = spawn(process.execPath, [
    path.join(installedBin, 'archify.mjs'),
    'deliver', 'workflow', input, output, '--json',
  ], {
    cwd,
    encoding: 'utf8',
    env: { ...process.env, ARCHIFY_TEST_RENDER_STARTED: marker },
    stdio: ['ignore', 'pipe', 'pipe'],
  });
  let stdout = '';
  let stderr = '';
  child.stdout.setEncoding('utf8');
  child.stderr.setEncoding('utf8');
  child.stdout.on('data', (chunk) => { stdout += chunk; });
  child.stderr.on('data', (chunk) => { stderr += chunk; });

  const started = Date.now();
  while (!fs.existsSync(marker) && Date.now() - started < 3000) {
    await new Promise((resolve) => setTimeout(resolve, 20));
  }
  assert.equal(fs.existsSync(marker), true, `renderer did not start; stderr=${stderr}`);
  const candidatePath = fs.readFileSync(marker, 'utf8');
  const candidateRelative = path.relative(linkedDirectory, candidatePath);
  fs.mkdirSync(path.dirname(path.join(inputDirectory, candidateRelative)), { recursive: true });
  fs.unlinkSync(linkedDirectory);
  fs.symlinkSync(inputDirectory, linkedDirectory, 'dir');

  const status = await new Promise((resolve) => child.once('close', resolve));

  assert.equal(status, 1, stderr);
  const receipt = JSON.parse(stdout);
  assert.equal(receipt.stage, 'commit');
  assert.equal(receipt.diagnostics[0].code, 'output/input-alias');
  assert.deepEqual(fs.readFileSync(input), source);
});

test('compare rejects an artifact path that aliases either architecture input', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-compare-'));
  const realDirectory = path.join(cwd, 'real');
  const linkedDirectory = path.join(cwd, 'linked');
  fs.mkdirSync(realDirectory);
  fs.symlinkSync(realDirectory, linkedDirectory, 'dir');
  const base = path.join(realDirectory, 'review.html');
  const output = path.join(linkedDirectory, 'review.html');
  const baseSource = fs.readFileSync(baseFixture);
  fs.writeFileSync(base, baseSource);

  const result = run(['compare', 'architecture', base, headFixture, output, '--json'], cwd);

  assert.equal(result.status, 1);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.stage, 'prepare');
  assert.equal(receipt.diagnostics[0].code, 'output/input-alias');
  assert.deepEqual(fs.readFileSync(base), baseSource);
});

test('compare rejects a receipt path that aliases either architecture input', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-compare-receipt-'));
  const base = path.join(cwd, 'base.json');
  const output = path.join(cwd, 'delta.html');
  const baseSource = fs.readFileSync(baseFixture);
  fs.writeFileSync(base, baseSource);

  const result = run([
    'compare', 'architecture', base, headFixture, output,
    '--receipt', base, '--json',
  ], cwd);

  assert.equal(result.status, 1);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.stage, 'prepare');
  assert.equal(receipt.diagnostics[0].code, 'output/input-alias');
  assert.deepEqual(fs.readFileSync(base), baseSource);
  assert.equal(fs.existsSync(output), false);
});

test('compare rejects a dangling receipt symlink to the future artifact path', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-compare-pair-'));
  const output = path.join(cwd, 'delta.html');
  const receiptPath = path.join(cwd, 'delta.receipt.json');
  fs.symlinkSync(output, receiptPath, 'file');

  const result = run([
    'compare', 'architecture', baseFixture, headFixture, output,
    '--receipt', receiptPath, '--json',
  ], cwd);

  assert.equal(result.status, 1);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.stage, 'prepare');
  assert.equal(receipt.diagnostics[0].code, 'output/target-alias');
  assert.equal(fs.lstatSync(receiptPath).isSymbolicLink(), true);
  assert.equal(fs.existsSync(output), false);
});

test('preview applies the meta.output relative-path boundary before starting a server', async () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-preview-meta-'));
  const input = path.join(cwd, 'diagram.workflow.json');
  const output = path.join(cwd, 'authored.html');
  const source = JSON.parse(fs.readFileSync(workflowFixture, 'utf8'));
  source.meta.output = output;
  fs.writeFileSync(input, JSON.stringify(source));

  const failure = await startPreview({
    type: 'workflow',
    input,
    open: false,
    watch: false,
    cwd,
  }).then(async (preview) => {
    await preview.stop();
    return null;
  }, (error) => error);

  assert.ok(failure instanceof Error);
  assert.match(failure.message, /meta\.output must be a relative path/i);
  assert.equal(fs.existsSync(output), false);
});

test('the shared renderer rechecks its guarded output immediately before writing', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-render-race-'));
  const inputDirectory = path.join(cwd, 'input');
  const initialOutputDirectory = path.join(cwd, 'safe-output');
  const linkedDirectory = path.join(cwd, 'linked-output');
  fs.mkdirSync(inputDirectory);
  fs.mkdirSync(initialOutputDirectory);
  fs.symlinkSync(initialOutputDirectory, linkedDirectory, 'dir');
  const input = path.join(inputDirectory, 'diagram.workflow.html');
  const output = path.join(linkedDirectory, 'diagram.workflow.html');
  const source = fs.readFileSync(workflowFixture);
  fs.writeFileSync(input, source);

  const loaded = loadDiagram({
    rendererDir: path.join(skillRoot, 'renderers/workflow'),
    diagramType: 'workflow',
    defaultExample: 'agent-tool-call.workflow.json',
    argv: ['node', 'render-workflow.mjs', input, output],
  });
  fs.unlinkSync(linkedDirectory);
  fs.symlinkSync(inputDirectory, linkedDirectory, 'dir');

  assert.throws(
    () => writeDiagram({
      outPath: loaded.outPath,
      template: loaded.template,
      diagramType: 'workflow',
      meta: loaded.diagram.meta,
      svg: '<svg role="img"></svg>',
      cards: [],
    }),
    /output must not replace an input/i,
  );
  assert.deepEqual(fs.readFileSync(input), source);
});

test('compare rechecks every target immediately before committing the artifact pair', { timeout: 10000 }, async () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-compare-race-'));
  const installedRoot = path.join(cwd, 'skill');
  const installedBin = path.join(installedRoot, 'bin');
  const installedShared = path.join(installedRoot, 'renderers/shared');
  const installedRenderer = path.join(installedRoot, 'renderers/architecture');
  const installedScripts = path.join(installedRoot, 'scripts');
  const installedDelta = path.join(installedRoot, 'delta');
  for (const directory of [installedBin, installedShared, installedRenderer, installedScripts, installedDelta]) {
    fs.mkdirSync(directory, { recursive: true });
  }
  fs.copyFileSync(cli, path.join(installedBin, 'archify.mjs'));
  fs.copyFileSync(
    path.join(skillRoot, 'renderers/shared/output-path.mjs'),
    path.join(installedShared, 'output-path.mjs'),
  );
  fs.writeFileSync(path.join(installedRenderer, 'render-architecture.mjs'), `
import fs from 'node:fs';
import path from 'node:path';
const [, output] = process.argv.slice(2);
if (path.basename(output) === 'head.html') {
  const marker = process.env.ARCHIFY_TEST_RENDER_STARTED;
  const markerCandidate = marker + '.tmp';
  fs.writeFileSync(markerCandidate, output);
  fs.renameSync(markerCandidate, marker);
  await new Promise((resolve) => setTimeout(resolve, 500));
}
fs.writeFileSync(output, '<!doctype html><svg role="img"></svg>');
`);
  fs.writeFileSync(path.join(installedScripts, 'check-render-output.mjs'), `
console.log(JSON.stringify({
  ok: true,
  checks: [{ name: 'single_svg', ok: true }],
  composition: {
    profile: 'showcase',
    status: 'pass',
    summary: { errors: 0, warnings: 0 }
  }
}));
`);
  fs.writeFileSync(path.join(installedDelta, 'architecture-delta.mjs'), `
export class ArchitectureDeltaError extends Error {}
export const annotateArchitectureSideSvg = (svg) => svg;
export const buildDeltaSvg = () => '<svg role="img"></svg>';
export const canonicalArchitecture = (value) => value;
export const canonicalArchitectureJson = (value) => JSON.stringify(value);
export const compareArchitecture = () => ({
  command: 'compare',
  base: {},
  head: {},
  completeness: 'complete',
  proofLevel: 'authored'
});
export const extractArchitectureSvg = () => '<svg role="img"></svg>';
export const extractArtifactCss = () => '';
export const renderArchitectureDeltaHtml = () => '<!doctype html><svg role="img"></svg>';
export const validateArchitectureDeltaHtml = () => ({ checksPassed: 1, checkCount: 1 });
`);

  const inputDirectory = path.join(cwd, 'input');
  const initialOutputDirectory = path.join(cwd, 'safe-output');
  const linkedDirectory = path.join(cwd, 'linked-output');
  fs.mkdirSync(inputDirectory);
  fs.mkdirSync(initialOutputDirectory);
  fs.symlinkSync(initialOutputDirectory, linkedDirectory, 'dir');
  const base = path.join(inputDirectory, 'diagram.html');
  const head = path.join(cwd, 'head.json');
  const output = path.join(linkedDirectory, 'diagram.html');
  const source = Buffer.from('{"side":"base"}');
  fs.writeFileSync(base, source);
  fs.writeFileSync(head, '{"side":"head"}');
  const marker = path.join(cwd, 'renderer-started');

  const child = spawn(process.execPath, [
    path.join(installedBin, 'archify.mjs'),
    'compare', 'architecture', base, head, output, '--json',
  ], {
    cwd,
    encoding: 'utf8',
    env: { ...process.env, ARCHIFY_TEST_RENDER_STARTED: marker },
    stdio: ['ignore', 'pipe', 'pipe'],
  });
  let stdout = '';
  let stderr = '';
  child.stdout.setEncoding('utf8');
  child.stderr.setEncoding('utf8');
  child.stdout.on('data', (chunk) => { stdout += chunk; });
  child.stderr.on('data', (chunk) => { stderr += chunk; });

  const started = Date.now();
  while (!fs.existsSync(marker) && Date.now() - started < 3000) {
    await new Promise((resolve) => setTimeout(resolve, 20));
  }
  assert.equal(fs.existsSync(marker), true, `renderer did not start; stderr=${stderr}`);
  const candidatePath = fs.readFileSync(marker, 'utf8');
  const candidateRelative = path.relative(linkedDirectory, candidatePath);
  fs.mkdirSync(path.dirname(path.join(inputDirectory, candidateRelative)), { recursive: true });
  fs.unlinkSync(linkedDirectory);
  fs.symlinkSync(inputDirectory, linkedDirectory, 'dir');

  const status = await new Promise((resolve) => child.once('close', resolve));

  assert.equal(status, 1, stderr);
  const receipt = JSON.parse(stdout);
  assert.equal(receipt.stage, 'commit');
  assert.equal(receipt.diagnostics[0].code, 'output/input-alias');
  assert.deepEqual(fs.readFileSync(base), source);
});

test('doctor reports a missing output-path safety runtime in an installed skill', () => {
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-doctor-'));
  const installedRoot = path.join(cwd, 'skill');
  copyInstalledSkill(installedRoot);
  fs.rmSync(path.join(installedRoot, 'renderers/shared/output-path.mjs'));

  const result = spawnSync(process.execPath, [path.join(installedRoot, 'bin/archify.mjs'), 'doctor'], {
    cwd: installedRoot,
    encoding: 'utf8',
  });

  assert.equal(result.status, 1);
  assert.match(result.stdout, /\[missing\] Output path safety runtime/);
});
```

## test/presentation.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-presentation-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function svg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers ship the same presentation stage contract', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="btn-present"[^>]+aria-label="Enter presentation stage"[^>]+aria-pressed="false"/, mode);
    assert.match(html, /Archify\.presentation = \(function \(\)/, mode);
    assert.match(html, /enter: function \(\) \{ return setActive\(true\); \}/, mode);
    assert.match(html, /exit: function \(\) \{ return setActive\(false\); \}/, mode);
    assert.match(html, /html\[data-present="true"\]:not\(\[data-embed="true"\]\) \.diagram-container/, mode);
    assert.match(html, /height: 100dvh/, mode);
    assert.match(html, /\.cards \{ display: none; \}/, mode);
    assert.doesNotMatch(html.match(/<html[^>]*>/)?.[0] || '', /data-present=/, mode);
    assert.doesNotMatch(svg(html), /data-present|btn-present|Presentation Stage/, mode);
  }
});

test('presentation stage supports direct links and preserves view hashes', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /get\('present'\) === '1'/);
  assert.match(html, /document\.documentElement\.setAttribute\('data-present', 'true'\)/);
  assert.match(html, /url\.searchParams\.set\('present', '1'\)/);
  assert.match(html, /url\.searchParams\.delete\('present'\)/);
  assert.match(html, /url\.pathname \+ url\.search \+ url\.hash/);
  assert.match(html, /Embed mode wins when both query parameters are present/);
});

test('presentation keyboard behavior exits in layers and remains accessible', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /e\.defaultPrevented\) return/);
  assert.match(html, /e\.key === 'f' \|\| e\.key === 'F'/);
  assert.match(html, /Archify\.presentation\.toggle\(\)/);
  assert.match(html, /e\.key === 'Escape' && Archify\.focus\.active\(\)/);
  assert.match(html, /e\.key === 'Escape' && Archify\.presentation\.active\(\)/);
  assert.match(html, /btn\.setAttribute\('aria-pressed', next \? 'true' : 'false'\)/);
  assert.match(html, /Exit presentation stage \(F or Escape\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/preset-tryon.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preset-tryon-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, preset) {
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', CASES[mode]), 'utf8'));
  if (preset === undefined) delete source.meta.visual_preset;
  else source.meta.visual_preset = preset;
  source.meta.animation = 'none';
  const fixtureName = preset || 'default';
  const input = path.join(tmp, `${mode}-${fixtureName}.json`);
  const output = path.join(tmp, `${mode}-${fixtureName}.html`);
  fs.writeFileSync(input, JSON.stringify(source));
  execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, output]);
  return fs.readFileSync(output, 'utf8');
}

function svgBlock(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

function presetRuntime(html) {
  return html.match(/Archify\.preset = \(function \(\) \{[\s\S]*?\n    \}\)\(\);/)?.[0] || '';
}

test('all five renderers expose one reader-controlled visual style picker', () => {
  for (const mode of Object.keys(CASES)) {
    const html = render(mode);
    assert.match(html, /id="btn-preset"[^>]+aria-haspopup="menu"[^>]+aria-controls="preset-menu"/, mode);
    assert.match(html, /id="preset-label"/, mode);
    assert.match(html, /title="Choose visual style \(S cycles\)"/, mode);
    assert.match(html, /id="preset-menu" role="menu" aria-label="Visual style"/, mode);
    for (const preset of ['classic', 'signal-flow', 'blueprint', 'editorial']) {
      assert.match(html, new RegExp(`data-preset-value="${preset}"[^>]+role="menuitemradio"`), `${mode}: ${preset}`);
    }
    assert.match(html, /Archify\.preset = \(function \(\)/, mode);
    assert.match(html, /S -> cycle visual style/, mode);
  }
});

test('style selection synchronizes page, picker, and canonical SVG without touching geometry', () => {
  const html = render('architecture');
  const runtime = presetRuntime(html);
  assert.match(runtime, /\['classic', 'signal-flow', 'blueprint', 'editorial'\]/);
  assert.match(runtime, /html\.setAttribute\('data-preset', preset\)/);
  assert.match(runtime, /svg\.setAttribute\('data-preset', preset\)/);
  assert.match(runtime, /data-preset-option/);
  assert.match(runtime, /option\.setAttribute\('aria-checked', String\(selected\)\)/);
  assert.match(runtime, /return \{ cycle: cycle, apply: apply, current: current, authored: authored, open: open, close: close, isOpen: isOpen \}/);
});

test('omitted visual preset opens as Classic and theme switching cannot change it', () => {
  const html = render('architecture');
  const themeRuntime = html.match(/Archify\.theme = \(function \(\) \{[\s\S]*?\n    \}\)\(\);/)?.[0] || '';
  assert.match(html, /<html lang="en" data-theme="dark" data-preset="classic">/);
  assert.match(svgBlock(html), /<svg\b[^>]* data-preset="classic"/);
  assert.match(themeRuntime, /html\.setAttribute\('data-theme', theme\)/);
  assert.doesNotMatch(themeRuntime, /data-preset|Archify\.preset/);
});

test('style picker follows the accessible menu-button interaction contract', () => {
  const html = render('architecture');
  const runtime = presetRuntime(html);
  assert.match(runtime, /function open\(focusLast\)/);
  assert.match(runtime, /function close\(focusTrigger\)/);
  assert.match(runtime, /e\.key === 'ArrowDown' \|\| e\.key === 'ArrowUp'/);
  assert.match(runtime, /e\.key === 'Escape'/);
  assert.match(runtime, /e\.key === 'Tab'/);
  assert.match(runtime, /case 'Home':/);
  assert.match(runtime, /case 'End':/);
  assert.match(runtime, /document\.addEventListener\('click'/);
  assert.match(html, /\.preset-option-swatch\.editorial/);
  assert.match(
    html,
    /@media \(max-width: 720px\)[\s\S]*?\.toolbar \{[\s\S]*?position: relative;/,
    'the mobile toolbar must preserve its stacking context so the fixed preset menu stays above guided views',
  );
});

test('style try-on is session-only and unavailable to passive embeds', () => {
  const html = render('workflow', 'signal-flow');
  const runtime = presetRuntime(html);
  assert.match(runtime, /html\.getAttribute\('data-embed'\) === 'true'/);
  assert.doesNotMatch(runtime, /localStorage|sessionStorage|history\.|location\.|URLSearchParams/);
  assert.match(html, /html\[data-embed="true"\] \.toolbar/);
  assert.match(html, /@media print/);
});

test('same topology keeps identical canonical SVG geometry across all four styles', () => {
  const normalize = (svg) => svg.replace(/ data-preset="(?:classic|signal-flow|blueprint|editorial)"/, '');
  const variants = ['classic', 'signal-flow', 'blueprint', 'editorial'].map((preset) => normalize(svgBlock(render('architecture', preset))));
  assert.equal(variants[1], variants[0]);
  assert.equal(variants[2], variants[0]);
  assert.equal(variants[3], variants[0]);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/preview-contract.test.mjs

```js
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import assert from 'node:assert/strict';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const repoRoot = path.resolve(skillRoot, '..');
const skill = fs.readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8');
const delivery = fs.readFileSync(path.join(skillRoot, 'references', 'delivery-contract.md'), 'utf8');
const readme = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8');
const english = fs.readFileSync(path.join(repoRoot, 'README_EN.md'), 'utf8');
const chinese = fs.readFileSync(path.join(repoRoot, 'README_ZH.md'), 'utf8');

test('preview contract: the skill keeps live preview explicit, desktop-only, and last-good', () => {
  assert.match(delivery, /archify\.mjs preview <type> <input>\.json <output>\.html/);
  assert.match(delivery, /active desktop authoring loop/i);
  assert.match(delivery, /previous verified revision on screen and on disk/i);
  assert.match(delivery, /never start it by default/i);
  assert.match(delivery, /CI, unattended agents, remote sharing, or mobile use/i);
  assert.match(delivery, /must never enter the generated artifact or any export/i);
});

test('preview contract: all README languages document the same optional command without changing the hero', () => {
  assert.equal(readme, english);
  for (const text of [readme, chinese]) {
    assert.match(text, /bin\/archify\.mjs preview workflow/);
    assert.match(text, /--no-open/);
    assert.match(text, /127\.0\.0\.1/);
    assert.match(text, /Ctrl-C/);
    assert.match(text, /docs\/assets\/archify-readme-hero\.png/);
  }
});

test('preview contract: the canonical delivery reference owns no-leak and zero-dependency boundaries', () => {
  assert.match(delivery, /Last-Good Live Preview/);
  assert.match(delivery, /zero-dependency Skill ZIP/i);
  assert.match(delivery, /Server state, port, source path, diagnostics, error text, and reload tokens must never enter/i);
});
```

## test/preview.test.mjs

```js
import { createHash } from 'node:crypto';
import { EventEmitter } from 'node:events';
import fs from 'node:fs';
import http from 'node:http';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import assert from 'node:assert/strict';
import vm from 'node:vm';

import { startPreview } from '../bin/preview.mjs';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');

function sha256(file) {
  return createHash('sha256').update(fs.readFileSync(file)).digest('hex');
}

async function stateAt(url) {
  const response = await fetch(new URL('/state', url));
  assert.equal(response.status, 200);
  return response.json();
}

async function waitForState(url, predicate, message, timeoutMs = 12000) {
  const started = Date.now();
  let latest;
  while (Date.now() - started < timeoutMs) {
    latest = await stateAt(url);
    if (predicate(latest)) return latest;
    await new Promise((resolve) => setTimeout(resolve, 40));
  }
  assert.fail(`${message}; latest state: ${JSON.stringify(latest)}`);
}

function rawRequest(url, { method = 'GET', pathname = '/', hostHeader } = {}) {
  const target = new URL(url);
  return new Promise((resolve, reject) => {
    const request = http.request({
      hostname: target.hostname,
      port: target.port,
      method,
      path: pathname,
      headers: hostHeader ? { Host: hostHeader } : undefined,
    }, (response) => {
      let body = '';
      response.setEncoding('utf8');
      response.on('data', (chunk) => { body += chunk; });
      response.on('end', () => resolve({ status: response.statusCode, body, headers: response.headers }));
    });
    request.on('error', reject);
    request.end();
  });
}

test('preview: rejects destructive or unsupported startup targets before watching', async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-startup-'));
  const input = path.join(tmp, 'diagram.json');
  fs.writeFileSync(input, '{}');
  await assert.rejects(
    startPreview({ type: 'architecture', input, output: input, open: false }),
    /must not replace its JSON input/i,
  );
  await assert.rejects(
    startPreview({ type: 'mindmap', input, output: path.join(tmp, 'out.html'), open: false }),
    /Unknown diagram type/i,
  );
  await assert.rejects(
    startPreview({ type: 'architecture', input, output: path.join(tmp, 'out.html'), quality: 'pretty', open: false }),
    /Unknown quality profile/i,
  );

  const realDirectory = path.join(tmp, 'real');
  const linkedDirectory = path.join(tmp, 'linked');
  fs.mkdirSync(realDirectory);
  fs.symlinkSync(realDirectory, linkedDirectory, 'dir');
  await assert.rejects(
    startPreview({
      type: 'architecture',
      input: path.join(realDirectory, 'future.json'),
      output: path.join(linkedDirectory, 'future.json'),
      open: false,
    }),
    /must not replace its JSON input/i,
  );
  assert.deepEqual(fs.readdirSync(tmp).filter((name) => name.startsWith('.archify-preview-')), []);
});

test('preview: invalid candidates preserve the last verified artifact and repair automatically', { timeout: 30000 }, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-last-good-'));
  const input = path.join(tmp, 'diagram.architecture.json');
  const output = path.join(tmp, 'diagram.html');
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  source.meta.title = 'Last Good One';
  fs.writeFileSync(input, JSON.stringify(source));

  const preview = await startPreview({
    type: 'architecture',
    input,
    output,
    quality: 'showcase',
    open: false,
    debounceMs: 60,
    pollMs: 80,
  });

  try {
    const first = await waitForState(preview.url, (state) => state.status === 'verified' && state.revision === 1, 'first revision did not verify');
    assert.equal(first.generation, 1);
    assert.equal(first.lastVerified.sha256, sha256(output));
    const firstSha = sha256(output);
    const firstArtifact = await (await fetch(new URL('/artifact.html', preview.url))).text();
    assert.match(firstArtifact, /Last Good One/);

    fs.rmSync(input);
    const missing = await waitForState(preview.url, (state) => state.status === 'needs-fix' && state.generation === 2, 'deleted source did not report failure');
    assert.equal(missing.failure.stage, 'input');
    assert.equal(missing.revision, 1);
    assert.equal(sha256(output), firstSha, 'deleted input replaced the last verified output');

    fs.writeFileSync(input, '{"meta":');
    const failed = await waitForState(preview.url, (state) => state.status === 'needs-fix' && state.generation === 3, 'invalid source did not report failure');
    assert.equal(failed.revision, 1);
    assert.equal(failed.failure.stage, 'input');
    assert.match(failed.failure.message, /Could not read delivery input/);
    assert.doesNotMatch(JSON.stringify(failed), new RegExp(input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
    assert.equal(sha256(output), firstSha, 'invalid input replaced the last verified output');
    assert.equal(await (await fetch(new URL('/artifact.html', preview.url))).text(), firstArtifact);

    source.components[0].unexpected = true;
    fs.writeFileSync(input, JSON.stringify(source));
    const schemaFailed = await waitForState(preview.url, (state) => state.status === 'needs-fix' && state.generation === 4, 'schema failure did not report render stage');
    assert.equal(schemaFailed.failure.stage, 'render');
    assert.match(schemaFailed.failure.message, /\/components\/0.*additional properties/i);
    assert.doesNotMatch(schemaFailed.failure.message, /file:\/\/|\/Users\/|node:internal/);
    assert.equal(sha256(output), firstSha, 'schema failure replaced the last verified output');

    delete source.components[0].unexpected;
    source.meta.title = 'Verified Repair';
    source.components[0].label = 'Repaired Browser';
    fs.writeFileSync(input, JSON.stringify(source));
    const repaired = await waitForState(preview.url, (state) => state.status === 'verified' && state.revision === 2, 'repaired source did not publish');
    assert.equal(repaired.generation, 5);
    assert.notEqual(repaired.lastVerified.sha256, firstSha);
    const repairedArtifact = await (await fetch(new URL('/artifact.html', preview.url))).text();
    assert.match(repairedArtifact, /Verified Repair/);
    assert.match(repairedArtifact, /Repaired Browser/);
    assert.equal(repaired.lastVerified.sha256, sha256(output));

    const page = await rawRequest(preview.url);
    assert.equal(page.status, 200);
    assert.match(page.body, /Archify Live Preview/);
    assert.match(page.body, /<summary role="button" aria-controls="diagnostic-panel">View diagnostic<\/summary>/);
    assert.match(page.headers['content-security-policy'], /default-src 'none'/);
    const script = page.body.match(/<script>\n([\s\S]*?)\n  <\/script>/)?.[1];
    assert.ok(script, 'preview shell script missing');
    assert.doesNotThrow(() => new vm.Script(script));
    assert.equal((await rawRequest(preview.url, { method: 'POST' })).status, 405);
    assert.equal((await rawRequest(preview.url, { pathname: '/../../etc/passwd' })).status, 404);
    assert.equal((await rawRequest(preview.url, { hostHeader: 'example.com' })).status, 403);
  } finally {
    await preview.stop();
  }

  await assert.rejects(fetch(preview.url));
  assert.deepEqual(fs.readdirSync(tmp).filter((name) => name.startsWith('.archify-preview-')), []);
});

test('preview: content digests suppress identical writes and a burst publishes only its stable tail', { timeout: 30000 }, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-burst-'));
  const input = path.join(tmp, 'diagram.workflow.json');
  const output = path.join(tmp, 'diagram.html');
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8'));
  const original = JSON.stringify(source);
  fs.writeFileSync(input, original);
  const preview = await startPreview({
    type: 'workflow',
    input,
    output,
    open: false,
    debounceMs: 90,
    pollMs: 70,
  });

  try {
    await waitForState(preview.url, (state) => state.status === 'verified' && state.revision === 1, 'initial workflow did not verify');
    fs.writeFileSync(input, original);
    await new Promise((resolve) => setTimeout(resolve, 350));
    let state = await stateAt(preview.url);
    assert.equal(state.generation, 1);
    assert.equal(state.revision, 1);

    fs.writeFileSync(input, JSON.stringify(source, null, 2));
    state = await waitForState(preview.url, (candidate) => candidate.status === 'verified' && candidate.generation === 2, 'semantically identical source did not settle');
    assert.equal(state.revision, 1, 'identical artifact bytes triggered a browser revision');

    for (let index = 0; index < 8; index += 1) {
      source.meta.title = `Burst ${index}`;
      fs.writeFileSync(input, JSON.stringify(source));
      await new Promise((resolve) => setTimeout(resolve, 12));
    }
    source.meta.title = 'Stable Tail';
    fs.writeFileSync(input, JSON.stringify(source));

    state = await waitForState(preview.url, (candidate) => candidate.status === 'verified' && candidate.revision === 2, 'stable burst tail did not verify');
    assert.equal(state.generation, 3);
    const artifact = await (await fetch(new URL('/artifact.html', preview.url))).text();
    assert.match(artifact, /Stable Tail/);
    assert.doesNotMatch(artifact, /Burst 7/);
  } finally {
    await preview.stop();
  }
});

test('preview: a superseded slow candidate can never become a published revision', { timeout: 30000 }, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-latest-wins-'));
  const input = path.join(tmp, 'diagram.json');
  const output = path.join(tmp, 'diagram.html');
  const deliveryCli = path.join(tmp, 'fake-delivery.mjs');
  fs.writeFileSync(deliveryCli, `
import { createHash } from 'node:crypto';
import fs from 'node:fs';
const [, , input, output] = process.argv.slice(2);
const source = JSON.parse(fs.readFileSync(input, 'utf8'));
await new Promise((resolve) => setTimeout(resolve, source.title === 'Slow Old' ? 550 : 40));
const artifact = Buffer.from('<!doctype html><title>' + source.title + '</title><svg></svg>');
fs.writeFileSync(output, artifact);
console.log(JSON.stringify({
  ok: true,
  artifact: { sha256: createHash('sha256').update(artifact).digest('hex'), bytes: artifact.byteLength },
  validation: { checksPassed: 1, checkCount: 1, compositionProfile: 'showcase', compositionStatus: 'pass' }
}));
`);
  fs.writeFileSync(input, JSON.stringify({ title: 'Slow Old' }));

  const preview = await startPreview({
    type: 'architecture',
    input,
    output,
    open: false,
    debounceMs: 25,
    pollMs: 40,
    deliveryCli,
  });
  try {
    await waitForState(preview.url, (state) => state.status === 'checking' && state.generation === 1, 'slow generation did not start');
    await new Promise((resolve) => setTimeout(resolve, 100));
    fs.writeFileSync(input, JSON.stringify({ title: 'Fast New' }));
    const state = await waitForState(preview.url, (candidate) => candidate.status === 'verified' && candidate.generation === 2, 'latest generation did not publish');
    assert.equal(state.revision, 1, 'superseded generation was published before the latest one');
    const artifact = await (await fetch(new URL('/artifact.html', preview.url))).text();
    assert.match(artifact, /Fast New/);
    assert.doesNotMatch(artifact, /Slow Old/);
    assert.equal(fs.readFileSync(output, 'utf8'), artifact);
  } finally {
    await preview.stop();
  }
});

test('preview: each delivery reads the immutable bytes bound to its observed digest', { timeout: 30000 }, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-snapshot-'));
  const input = path.join(tmp, 'diagram.json');
  const output = path.join(tmp, 'diagram.html');
  const deliveryCli = path.join(tmp, 'snapshot-delivery.mjs');
  const readMarker = path.join(tmp, 'delivery-read.txt');
  fs.writeFileSync(deliveryCli, `
import { createHash } from 'node:crypto';
import fs from 'node:fs';
const [, , input, output] = process.argv.slice(2);
await new Promise((resolve) => setTimeout(resolve, 120));
const source = JSON.parse(fs.readFileSync(input, 'utf8'));
fs.writeFileSync(${JSON.stringify(readMarker)}, source.title);
await new Promise((resolve) => setTimeout(resolve, 180));
const artifact = Buffer.from('<!doctype html><title>' + source.title + '</title><svg></svg>');
fs.writeFileSync(output, artifact);
console.log(JSON.stringify({
  ok: true,
  artifact: { sha256: createHash('sha256').update(artifact).digest('hex'), bytes: artifact.byteLength },
  validation: { checksPassed: 1, checkCount: 1, compositionProfile: 'showcase', compositionStatus: 'pass' }
}));
`);
  fs.writeFileSync(input, JSON.stringify({ title: 'Source A' }));

  const preview = await startPreview({
    type: 'architecture',
    input,
    output,
    open: false,
    debounceMs: 10,
    pollMs: 5000,
    watch: false,
    deliveryCli,
  });
  try {
    await waitForState(preview.url, (state) => state.status === 'checking' && state.generation === 1, 'snapshot generation did not start');
    fs.writeFileSync(input, JSON.stringify({ title: 'Source B' }));
    const markerStarted = Date.now();
    while (!fs.existsSync(readMarker) && Date.now() - markerStarted < 3000) {
      await new Promise((resolve) => setTimeout(resolve, 20));
    }
    assert.ok(fs.existsSync(readMarker), 'fake delivery never read its generation input');
    fs.writeFileSync(input, JSON.stringify({ title: 'Source A' }));

    const state = await waitForState(preview.url, (candidate) => candidate.status === 'verified' && candidate.revision === 1, 'snapshot generation did not verify');
    assert.equal(state.generation, 1, 'an unobserved A → B → A edit started a second generation');
    assert.equal(fs.readFileSync(readMarker, 'utf8'), 'Source A');
    assert.match(fs.readFileSync(output, 'utf8'), /Source A/);
    assert.doesNotMatch(fs.readFileSync(output, 'utf8'), /Source B/);
  } finally {
    await preview.stop();
  }
});

test('preview: commit rechecks the live digest when watcher and poll have not seen a newer save', { timeout: 10000 }, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-commit-race-'));
  const input = path.join(tmp, 'diagram.json');
  const output = path.join(tmp, 'diagram.html');
  const deliveryCli = path.join(tmp, 'commit-race-delivery.mjs');
  fs.writeFileSync(deliveryCli, `
import { createHash } from 'node:crypto';
import fs from 'node:fs';
const [, , input, output] = process.argv.slice(2);
const source = JSON.parse(fs.readFileSync(input, 'utf8'));
await new Promise((resolve) => setTimeout(resolve, source.title === 'Prior Good' ? 30 : 260));
const artifact = Buffer.from('<!doctype html><title>' + source.title + '</title><svg></svg>');
fs.writeFileSync(output, artifact);
console.log(JSON.stringify({
  ok: true,
  artifact: { sha256: createHash('sha256').update(artifact).digest('hex'), bytes: artifact.byteLength },
  validation: { checksPassed: 1, checkCount: 1, compositionProfile: 'showcase', compositionStatus: 'pass' }
}));
`);
  fs.writeFileSync(input, JSON.stringify({ title: 'Prior Good' }));

  const preview = await startPreview({
    type: 'architecture',
    input,
    output,
    open: false,
    debounceMs: 10,
    pollMs: 800,
    watch: false,
    deliveryCli,
  });
  try {
    await waitForState(preview.url, (state) => state.status === 'verified' && state.revision === 1, 'prior good revision did not verify');
    const priorArtifact = fs.readFileSync(output, 'utf8');
    fs.writeFileSync(input, JSON.stringify({ title: 'Intermediate A' }));
    await waitForState(preview.url, (state) => state.status === 'checking' && state.generation === 2, 'intermediate generation did not start');
    fs.writeFileSync(input, JSON.stringify({ title: 'Current B' }));

    await new Promise((resolve) => setTimeout(resolve, 340));
    assert.equal(fs.readFileSync(output, 'utf8'), priorArtifact, 'superseded intermediate bytes replaced the prior last-good output');
    const pending = await stateAt(preview.url);
    assert.equal(pending.revision, 1, 'superseded intermediate bytes advanced the browser revision');

    const current = await waitForState(preview.url, (state) => state.status === 'verified' && state.generation === 3, 'current generation did not verify');
    assert.equal(current.revision, 2);
    assert.match(fs.readFileSync(output, 'utf8'), /Current B/);
    assert.doesNotMatch(fs.readFileSync(output, 'utf8'), /Intermediate A/);
  } finally {
    await preview.stop();
  }
});

test('preview: stopping drains an active delivery without publishing it', { timeout: 30000 }, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-stop-'));
  const input = path.join(tmp, 'diagram.json');
  const output = path.join(tmp, 'diagram.html');
  const deliveryCli = path.join(tmp, 'slow-delivery.mjs');
  const prior = '<!doctype html><title>Prior verified artifact</title>';
  fs.writeFileSync(output, prior);
  fs.writeFileSync(input, JSON.stringify({ title: 'Do not publish after stop' }));
  fs.writeFileSync(deliveryCli, `
import { createHash } from 'node:crypto';
import fs from 'node:fs';
const [, , , output] = process.argv.slice(2);
await new Promise((resolve) => setTimeout(resolve, 450));
const artifact = Buffer.from('<!doctype html><title>Late candidate</title><svg></svg>');
fs.writeFileSync(output, artifact);
console.log(JSON.stringify({
  ok: true,
  artifact: { sha256: createHash('sha256').update(artifact).digest('hex'), bytes: artifact.byteLength },
  validation: { checksPassed: 1, checkCount: 1, compositionProfile: 'showcase', compositionStatus: 'pass' }
}));
`);

  const preview = await startPreview({
    type: 'architecture',
    input,
    output,
    open: false,
    debounceMs: 10,
    pollMs: 100,
    deliveryCli,
  });
  await waitForState(preview.url, (state) => state.status === 'checking' && state.generation === 1, 'slow stop candidate did not start');
  await new Promise((resolve) => setTimeout(resolve, 90));
  const stoppedAt = Date.now();
  await preview.stop();
  assert.ok(Date.now() - stoppedAt >= 250, 'preview did not drain the active delivery');
  assert.equal(fs.readFileSync(output, 'utf8'), prior);
  assert.deepEqual(fs.readdirSync(tmp).filter((name) => name.startsWith('.archify-preview-')), []);
});

test('preview: stopping has a bounded kill path for a delivery that never exits', { timeout: 5000 }, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-hung-stop-'));
  const input = path.join(tmp, 'diagram.json');
  const output = path.join(tmp, 'diagram.html');
  const deliveryCli = path.join(tmp, 'hung-delivery.mjs');
  const prior = '<!doctype html><title>Keep me</title>';
  fs.writeFileSync(input, JSON.stringify({ title: 'Never completes' }));
  fs.writeFileSync(output, prior);
  fs.writeFileSync(deliveryCli, `
process.on('SIGTERM', () => {});
setInterval(() => {}, 1000);
`);

  const preview = await startPreview({
    type: 'architecture',
    input,
    output,
    open: false,
    debounceMs: 10,
    pollMs: 5000,
    deliveryCli,
    stopGraceMs: 80,
    stopKillMs: 80,
  });
  await waitForState(preview.url, (state) => state.status === 'checking' && state.generation === 1, 'hung generation did not start');
  await new Promise((resolve) => setTimeout(resolve, 80));
  const stoppedAt = Date.now();
  await preview.stop();
  assert.ok(Date.now() - stoppedAt < 1000, 'hung delivery kept preview shutdown open');
  assert.equal(fs.readFileSync(output, 'utf8'), prior);
  await assert.rejects(fetch(preview.url));
  assert.deepEqual(fs.readdirSync(tmp).filter((name) => name.startsWith('.archify-preview-')), []);
});

test('preview: checker failures keep their actionable detail instead of a generic stage only', { timeout: 30000 }, async () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-checker-'));
  const input = path.join(tmp, 'diagram.json');
  const output = path.join(tmp, 'diagram.html');
  const deliveryCli = path.join(tmp, 'checker-failure.mjs');
  fs.writeFileSync(input, '{}');
  fs.writeFileSync(deliveryCli, `
console.log(JSON.stringify({
  ok: false,
  stage: 'check',
  error: 'Final artifact check failed; the previous artifact was preserved.',
  checker: { checks: [{ name: 'single_svg', ok: false, details: ['found 2 <svg> blocks; expected exactly one'] }] }
}));
process.exitCode = 1;
`);
  const preview = await startPreview({
    type: 'architecture',
    input,
    output,
    open: false,
    debounceMs: 10,
    pollMs: 100,
    deliveryCli,
  });
  try {
    const state = await waitForState(preview.url, (candidate) => candidate.status === 'needs-fix', 'checker failure did not surface');
    assert.equal(state.failure.stage, 'check');
    assert.match(state.failure.message, /Final artifact check failed/);
    assert.match(state.failure.message, /found 2 <svg> blocks; expected exactly one/);
  } finally {
    await preview.stop();
  }
});

test('preview: all five typed renderers reach a verified first revision', { timeout: 60000 }, async () => {
  const cases = {
    architecture: 'web-app.architecture.json',
    workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json',
    dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };

  for (const [type, example] of Object.entries(cases)) {
    const tmp = fs.mkdtempSync(path.join(os.tmpdir(), `archify-preview-${type}-`));
    const input = path.join(tmp, example);
    const output = path.join(tmp, `${type}.html`);
    fs.copyFileSync(path.join(skillRoot, 'examples', example), input);
    const preview = await startPreview({ type, input, output, open: false, debounceMs: 10, pollMs: 500 });
    try {
      const state = await waitForState(preview.url, (candidate) => candidate.status === 'verified', `${type} did not verify`);
      assert.equal(state.revision, 1, type);
      assert.equal(state.lastVerified.checksPassed, state.lastVerified.checkCount, type);
      assert.equal(state.lastVerified.sha256, sha256(output), type);
    } finally {
      await preview.stop();
    }
  }
});


test('preview: polling continues after an asynchronous watcher error', { timeout: 30000 }, async (t) => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-preview-watch-error-'));
  t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
  const input = path.join(tmp, 'diagram.architecture.json');
  const output = path.join(tmp, 'diagram.html');
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  fs.writeFileSync(input, JSON.stringify(source));

  const watcher = new EventEmitter();
  let closeCount = 0;
  watcher.close = () => { closeCount += 1; };
  t.mock.method(fs, 'watch', () => watcher);
  const preview = await startPreview({ type: 'architecture', input, output, open: false, pollMs: 40, debounceMs: 20 });
  try {
    await waitForState(preview.url, (state) => state.status === 'verified' && state.revision === 1, 'initial artifact did not verify');
    watcher.emit('error', Object.assign(new Error('watch limit reached'), { code: 'EMFILE' }));
    assert.equal(closeCount, 1, 'the failed watcher must be closed');
    source.meta.title = 'Recovered using polling';
    fs.writeFileSync(input, JSON.stringify(source));
    await waitForState(preview.url, (state) => state.status === 'verified' && state.revision === 2, 'polling did not publish the edited source');
    const artifact = await (await fetch(new URL('/artifact.html', preview.url))).text();
    assert.match(artifact, /Recovered using polling/);
  } finally {
    await preview.stop();
  }
  assert.equal(closeCount, 1, 'shutdown must not close the failed watcher again');
  await assert.rejects(fetch(preview.url));
});
```

## test/proof-aperture.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const landing = fs.readFileSync(path.resolve(__dirname, '..', '..', 'docs', 'index.html'), 'utf8');

function cssRule(selector) {
  const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const match = landing.match(new RegExp(`${escaped}\\s*\\{([^}]+)\\}`));
  assert.ok(match, `${selector}: CSS rule missing`);
  return match[1];
}

test('landing declares a truthful first-fold proof aperture in document order', () => {
  assert.match(landing, /<section class="hero" data-proof-aperture="first-fold">/);
  const heroStart = landing.indexOf('data-proof-aperture="first-fold"');
  const headline = landing.indexOf('data-i18n="hero-h1"', heroStart);
  const actions = landing.indexOf('class="hero-actions', headline);
  const proof = landing.indexOf('id="hero-proof-stage"', actions);
  assert.ok(heroStart < headline && headline < actions && actions < proof);
});

test('desktop hero budget exposes live diagram content without shrinking its canvas', () => {
  assert.match(cssRule('.hero'), /padding-top:9rem/);
  assert.match(cssRule('.hero-bento'), /grid-template-columns:repeat\(12,1fr\)/);
  assert.match(cssRule('.hero-intro'), /grid-column:1 \/ 8/);
  assert.match(cssRule('.proof-main'), /grid-column:8 \/ 13/);
  assert.match(cssRule('.proof-main'), /grid-row:1 \/ 3/);
  assert.match(cssRule('.hero-actions .btn'), /min-height:44px/);
  assert.match(cssRule('.proof-viewport'), /min-height:430px/);
});

test('narrow viewport preserves a contained fallback without adding a mobile product surface', () => {
  const mobile = landing.match(/@media\(max-width:640px\)\s*\{([\s\S]+?)\n\s*\}\n\s*<\/style>/)?.[1];
  assert.ok(mobile, 'narrow mobile media query missing');
  assert.match(mobile, /\.hero\s*\{\s*padding-top:6\.75rem;\s*\}/);
  assert.match(mobile, /\.hero-actions \.btn\s*\{\s*flex:1;\s*justify-content:center;\s*\}/);
  assert.match(mobile, /\.proof-viewport\s*\{\s*min-height:360px;\s*\}/);
  assert.match(mobile, /\.proof-rail\s*\{\s*grid-template-columns:1fr;\s*\}/);
});

test('proof aperture remains one real eager sandboxed artifact with explicit user-selected identities', () => {
  assert.equal((landing.match(/<iframe id="hero-proof-frame"/g) || []).length, 1);
  assert.match(landing, /loading="eager"/);
  assert.match(landing, /sandbox="allow-scripts"/);
  assert.doesNotMatch(landing, /sandbox="[^"]*allow-same-origin/);
  assert.equal((landing.match(/class="spec-card"/g) || []).length, 3);
  assert.match(landing, /data-proof-playback="first-fold-once"/);
  assert.match(landing, /\?embed=1&amp;play=1&amp;theme=dark#view=happy-path/);
  assert.doesNotMatch(landing, /setInterval\(|scrollIntoView\(|scroll-triggered|proof-carousel/);
});

test('initial proof playback uses one sandboxed load without parent-frame reach-through', () => {
  assert.match(landing, /src="gallery\/artifacts\/agent-tool-call\.workflow\.html\?embed=1&amp;play=1&amp;theme=dark#view=happy-path"/);
  assert.doesNotMatch(landing, /initialProof|proofFrameDocumentIsReady|proofFrame\.contentWindow|proofFrame\.contentDocument/);
  assert.match(landing, /proofFrame\.addEventListener\('load', \(\) => \{/);
  assert.match(landing, /proofStage\.classList\.remove\('is-loading'\)/);
});

test('proof playback delegates reduced motion to the artifact and keeps deliberate-choice boundaries', () => {
  assert.match(landing, /renderProof\(tab\.dataset\.proof, \{ deliberate: true \}\)/);
  assert.match(landing, /renderProof\(tabs\[next\]\.dataset\.proof, \{ focus: true, deliberate: true \}\)/);
  assert.match(landing, /proofEmbedUrl\(proof, \{ play: deliberate \}\)/);
  assert.match(landing, /document\.querySelectorAll\('\.fade-up'\)\.forEach\(el => el\.classList\.add\('visible'\)\)/);
  assert.doesNotMatch(landing, /addEventListener\('scroll'/);
});

test('aperture uses normal flow and preserves reduced-motion boundaries', () => {
  const hero = cssRule('.hero');
  const proof = cssRule('.proof-main');
  assert.doesNotMatch(hero + proof, /position:absolute|transform:|top:-|margin-top:-|height:100vh/);
  assert.match(landing, /@media\s*\(prefers-reduced-motion:\s*reduce\)/);
  assert.match(landing, /\.fade-up\s*\{\s*opacity:1!important;\s*transform:none!important;/);
  assert.match(landing, /\.pulse-dot,\.proof-live::before\s*\{\s*animation:none!important;\s*\}/);
});
```

## test/reach-share-card.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-reach-share-card-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one active-reach-only Reach Share Card item', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /data-action="reach-share-card"[^>]*hidden disabled[^>]*>[\s\S]*?Reach Share Card[\s\S]*?1200(?:&times;|×)630 PNG/, mode);
    assert.match(html, /function syncReachShareItem\(\)/, mode);
    assert.match(html, /reachShareItem\.hidden = !snapshot;/, mode);
    assert.match(html, /reachShareItem\.disabled = !snapshot;/, mode);
    assert.match(html, /function open\(focusLast\)[\s\S]*?syncReachShareItem\(\);/, mode);
    assert.doesNotMatch(html, /id="reach-share-card"|class="reach-share-card"/, mode);
    assert.doesNotMatch(canonicalSvg(html), /data-share-reach(?:-|=)/, mode);
  }
});

test('Reach Share Card snapshots copy the active authored closure without rerunning traversal', () => {
  const html = render('architecture', CASES.architecture);
  const start = html.indexOf('function reachabilitySnapshot() {');
  const end = html.indexOf('\n      function setPassportValue', start);
  const snapshotBlock = start >= 0 && end > start ? html.slice(start, end) : '';

  assert.match(snapshotBlock, /activeReachability\.nodeIds\.slice\(\)/);
  assert.match(snapshotBlock, /activeReachability\.edgeKeys\.slice\(\)/);
  assert.match(snapshotBlock, /activeReachability\.depths/);
  assert.match(snapshotBlock, /direction: reachabilityMode/);
  assert.match(snapshotBlock, /origin: \{ id: originId, label:/);
  assert.match(snapshotBlock, /maxDepth: activeReachability\.maxDepth/);
  assert.match(snapshotBlock, /seenNodeIds = Object\.create\(null\)/);
  assert.match(snapshotBlock, /seenEdgeKeys = Object\.create\(null\)/);
  assert.match(snapshotBlock, /drawableFragments = fragments\.filter\(hasDrawableGeometry\)/);
  assert.match(snapshotBlock, /drawableFragments\.length !== 1/);
  assert.match(snapshotBlock, /return null/);
  assert.doesNotMatch(snapshotBlock, /computeReachability|reachabilityFor|queue\s*=|shortestDirectedPath/);
  assert.match(html, /reachabilitySnapshot: reachabilitySnapshot/);
});

test('Reach variant decorates only a finite canonical clone with static authored identity', () => {
  const html = render('workflow', CASES.workflow);
  const start = html.indexOf('function applyReachSnapshot(clone, snapshot) {');
  const end = html.indexOf('\n      function serializeSvg', start);
  const applyBlock = start >= 0 && end > start ? html.slice(start, end) : '';

  assert.match(applyBlock, /snapshot\.direction !== 'upstream'/);
  assert.match(applyBlock, /snapshot\.direction !== 'downstream'/);
  assert.match(applyBlock, /snapshot\.nodeIds\.length < 2/);
  assert.match(applyBlock, /snapshot\.origin\.label\.trim\(\)/);
  assert.match(applyBlock, /nodeId === snapshot\.origin\.id \? depth !== 0 : depth < 1/);
  assert.match(applyBlock, /edge\.depth !== Math\.max\(snapshot\.depths\[edge\.from\], snapshot\.depths\[edge\.to\]\)/);
  assert.match(applyBlock, /matchedNodes\.length !== 1/);
  assert.match(applyBlock, /drawableMatches\.length !== 1/);
  assert.match(applyBlock, /data-share-reach-match/);
  assert.match(applyBlock, /data-share-reach-origin/);
  assert.match(applyBlock, /data-share-reach-depth/);
  assert.match(applyBlock, /clone\.setAttribute\('data-share-reach', snapshot\.direction\)/);
  assert.doesNotMatch(applyBlock, /setAttribute\('data-reach-(?:active|match|origin|depth)/);
  assert.doesNotMatch(applyBlock, /animation:|setTimeout|requestAnimationFrame/);
  assert.match(html, /canonicalStateClean && finiteSvgDimensions && !opts\.routeSnapshot && applyReachSnapshot\(clone, opts\.reachSnapshot\)/);
  assert.ok(html.indexOf('var canonicalStateClean =') < html.indexOf('applyReachSnapshot(clone, opts.reachSnapshot)'), 'canonical cleanup must precede reach decoration');
});

test('Reach styling preserves context, direction, and Blueprint restraint without motion', () => {
  const html = render('dataflow', CASES.dataflow);
  assert.match(html, /svg\[data-share-reach\] \[data-node-id\], svg\[data-share-reach\] \[data-edge-from\] \{ opacity: 0\.14; \}/);
  assert.match(html, /svg\[data-share-reach\] \[data-share-reach-match\] \{ opacity: 1; \}/);
  assert.match(html, /data-share-reach=\\?"upstream\\?"[\s\S]*?--database-stroke/);
  assert.match(html, /data-share-reach=\\?"downstream\\?"[\s\S]*?--backend-stroke/);
  assert.match(html, /data-preset=\\?"blueprint\\?"\]\[data-share-reach\][\s\S]*?filter: none/);
  const reachStyleBlock = html.match(/if \(opts\.reachSnapshot\) \{[\s\S]*?\n        \}/)?.[0] || '';
  assert.doesNotMatch(reachStyleBlock, /animation:|display:\s*none|transform:/);
});

test('Reach Share Card reuses the 1200x630 seam and publishes a truthful scoped receipt', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /options\.variant !== 'route' && options\.variant !== 'reach'/);
  assert.match(html, /Archify\.focus\.reachabilitySnapshot\(\)/);
  assert.match(html, /renderShareCard\(\{ reachSnapshot: snapshot \}\)/);
  assert.doesNotMatch(html, /function rasterizeReachShareCard|reachShareCard:/);
  assert.match(html, /viewerText\('viewer\.export\.card\.reachSummary'/);
  assert.match(html, /direction: directionLabel/);
  assert.match(html, /origin: reachSnapshot\.origin\.label/);
  assert.match(html, /reachSnapshot\.nodeIds\.length - 1/);
  assert.match(html, /reachSnapshot\.edges\.length/);
  assert.match(html, /reachSnapshot\.maxDepth/);
  assert.match(html, /recordExportReceipt\('share-card', blob, false, \{ width: SHARE_CARD_WIDTH, height: SHARE_CARD_HEIGHT \}, 'reach', false, true\)/);
  assert.match(html, /'-' \+ snapshot\.direction \+ '-reach-share-card\.png'/);
  assert.match(html, /data-last-export-reach-state-clean/);
  assert.match(html, /Trace authored reach before exporting a Reach Share Card/);
  assert.match(html, /downloadReachShareCard: runReachShareCard/);
});

test('Skill, product docs, and READMEs keep the optional truthful boundary explicit', () => {
  const viewer = fs.readFileSync(path.join(skillRoot, 'references', 'viewer-runtime.md'), 'utf8');
  assert.match(viewer, /Export → Reach Share Card/);
  assert.match(viewer, /variant=reach/);
  assert.match(viewer, /data-share-reach-\*/);
  assert.match(viewer, /authored reachability/i);
  assert.match(viewer, /download-only/i);

  for (const readme of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const text = fs.readFileSync(path.join(repoRoot, readme), 'utf8');
    assert.match(text, /Reach Share Card/, readme);
    assert.match(text, /docs\/assets\/mco-runtime-reach-share-card\.png/, readme);
  }
  const png = fs.readFileSync(path.join(repoRoot, 'docs/assets/mco-runtime-reach-share-card.png'));
  assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a');
  assert.equal(png.readUInt32BE(16), 1200);
  assert.equal(png.readUInt32BE(20), 630);

  const product = fs.readFileSync(path.join(repoRoot, 'PRODUCT.md'), 'utf8');
  const design = fs.readFileSync(path.join(repoRoot, 'DESIGN.md'), 'utf8');
  assert.match(product, /Reach Share Card/);
  assert.match(design, /Reach Share Card/);
  assert.match(design, /not (?:runtime )?(?:impact|causality|breakage)/i);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/reader-layout-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chromePath = process.env.ARCHIFY_CHROME ? findChrome() : null;
const cases = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

test('Reader Layout preserves final-artifact behavior across its ownership boundaries', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-reader-browser-'));
  const evidence = process.env.ARCHIFY_READER_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const artifacts = {};
  for (const [mode, example] of Object.entries(cases)) {
    const output = path.join(scratch, `${mode}.html`);
    // Optional captured base artifacts let the same behavioral cases establish
    // a pre-extraction baseline without changing the implementation under test.
    if (process.env.ARCHIFY_READER_BASELINE_DIR) {
      fs.copyFileSync(path.join(process.env.ARCHIFY_READER_BASELINE_DIR, `${mode}.html`), output);
    } else {
      execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
        path.join(skillRoot, 'examples', example), output]);
    }
    artifacts[mode] = output;
  }
  const browser = new ChromeVisualBrowser(chromePath);
  const records = [];
  try {
    const session = await browser.sessionPromise;
    await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
    const send = (method, params = {}) => browser.cdp.send(method, params, session);
    async function evaluate(expression, awaitPromise = false) {
      const result = await send('Runtime.evaluate', { expression, awaitPromise, returnByValue: true });
      assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
      return result.result?.value;
    }
    await send('Page.addScriptToEvaluateOnNewDocument', {
      source: `window.readerTestErrors = [];
        addEventListener('error', function (event) { readerTestErrors.push(event.message); });
        addEventListener('unhandledrejection', function (event) { readerTestErrors.push(String(event.reason)); });`,
    });
    async function viewport(width, height) {
      await send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: false });
    }
    async function media(theme = 'dark', reduced = false, print = false) {
      await send('Emulation.setEmulatedMedia', { media: print ? 'print' : '', features: [
        { name: 'prefers-color-scheme', value: theme },
        { name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' },
      ] });
    }
    async function stable() {
      await evaluate(`(async function () {
        for (var i = 0; i < 2; i += 1) {
          await Archify.readerLayout.whenStable();
          await Archify.viewerChromeLayout.whenStable();
        }
      })()`, true);
    }
    async function snapshot(label) {
      const value = await evaluate(`(function () {
        var html = document.documentElement;
        var diagram = document.querySelector('.diagram-container');
        var svg = diagram.querySelector(':scope > svg');
        return {
          active: Archify.readerLayout.active(), receipt: Archify.readerLayout.receipt(),
          width: html.style.getPropertyValue('--archify-reader-width'),
          layout: html.getAttribute('data-reader-layout'), overflow: html.getAttribute('data-reader-overflow'),
          wide: diagram.getAttribute('data-wide-diagram'), shape: html.getAttribute('data-diagram-shape'),
          geometry: ['viewBox', 'width', 'height'].map(function (name) { return svg.getAttribute(name); }),
          shellWidth: document.querySelector('.container').getBoundingClientRect().width,
          scrollHeight: Math.max(html.scrollHeight, document.body.scrollHeight),
          innerWidth: innerWidth, innerHeight: innerHeight,
          theme: html.getAttribute('data-theme'), reduced: matchMedia('(prefers-reduced-motion: reduce)').matches,
          errors: window.readerTestErrors,
          externalResources: performance.getEntriesByType('resource').map(function (entry) { return entry.name; }).filter(function (name) { return /^https?:/.test(name); })
        };
      })()`);
      assert.deepEqual(value.errors, [], `${label}: uncaught Viewer errors`);
      assert.deepEqual(value.externalResources, [], `${label}: external runtime assets`);
      records.push({ label, ...value });
      return value;
    }
    async function load(file, { width = 1440, height = 900, theme = 'dark', reduced = false, query = '', print = false, waitForLayout = true } = {}) {
      await viewport(width, height);
      await media(theme, reduced, print);
      const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
      const result = await send('Page.navigate', { url: pathToFileURL(file).href + `?theme=${theme}${query}` });
      assert.equal(result.errorText, undefined, result.errorText);
      await loaded;
      assert.deepEqual(await evaluate('window.readerTestErrors'), [], 'Viewer initialization');
      if (waitForLayout) await stable();
    }
    function variant(name, { ratio, beforeViewer = '' } = {}) {
      let html = fs.readFileSync(artifacts.architecture, 'utf8');
      if (ratio !== undefined) assert.match(html, /<svg\b[^>]*\bviewBox="[^"]+"/, 'Reader viewBox fixture anchor');
      if (beforeViewer) assert.ok(html.includes('  <script>\n    var Archify = {};'), 'Reader setup fixture anchor');
      if (ratio !== undefined) html = html.replace(/(<svg\b[^>]*\bviewBox=")[^"]+(")/, (_, start, end) => `${start}0 0 ${ratio * 1000} 1000${end}`);
      if (beforeViewer) html = html.replace('  <script>\n    var Archify = {};', () => `  <script>${beforeViewer}</script>\n  <script>\n    var Archify = {};`);
      const file = path.join(scratch, `${name}.html`);
      fs.writeFileSync(file, html);
      return file;
    }
    function inactive(state, wide = true) {
      assert.equal(state.active, false);
      assert.equal(state.width, '');
      assert.equal(state.layout, null);
      assert.equal(state.overflow, null);
      assert.equal(state.receipt.width, 0);
      assert.equal(state.wide, wide ? 'true' : null);
      assert.equal(state.shape, wide ? 'wide' : null);
    }

    await t.test('five modes initialize, export clean SVG, and honor themes and reduced motion', async () => {
      for (const [mode, file] of Object.entries(artifacts)) {
        for (const theme of ['dark', 'light']) {
          await load(file, { theme, reduced: theme === 'light' });
          const state = await snapshot(`${mode}-${theme}`);
          assert.equal(state.theme, theme);
          assert.equal(state.reduced, theme === 'light');
          assert.equal(state.active, state.receipt.ratio >= 1.55);
          await evaluate('Archify.view.zoomIn()');
          await stable();
          const exported = await evaluate(`(async function () {
            var original = URL.createObjectURL;
            var captured;
            URL.createObjectURL = function (blob) {
              if (blob.type.indexOf('image/svg+xml') === 0) captured = blob;
              return original.call(URL, blob);
            };
            try {
              await Archify.exportMenu.run('svg');
              if (!captured) throw new Error('SVG export did not produce a blob');
              var text = await captured.text();
              var svg = new DOMParser().parseFromString(text, 'image/svg+xml').documentElement;
              return { text: text, geometry: ['viewBox', 'width', 'height'].map(function (name) { return svg.getAttribute(name); }),
                dirty: !!svg.querySelector('[data-focus-match], [data-story-step], [data-route-match], [data-reader-layout], [data-source-evidence-beacon]') ||
                  svg.hasAttribute('data-view-scale') || svg.hasAttribute('data-focus-active') || svg.hasAttribute('data-route-active') };
            } finally { URL.createObjectURL = original; }
          })()`, true);
          assert.equal(exported.dirty, false);
          assert.equal(exported.geometry[0], state.geometry[0]);
          if (evidence) fs.writeFileSync(path.join(evidence, `${mode}-${theme}.svg`), exported.text);
          await evaluate('Archify.view.reset()');
          await stable();
          const reset = await snapshot(`${mode}-${theme}-reset`);
          assert.deepEqual(reset.geometry, state.geometry);
          if (evidence && mode === 'architecture') {
            const capture = await send('Page.captureScreenshot', { format: 'png' });
            fs.writeFileSync(path.join(evidence, `architecture-1440x900-${theme}.png`), Buffer.from(capture.data, 'base64'));
          }
        }
      }
    });

    await t.test('ratio and desktop thresholds preserve shape while clearing temporary state', async () => {
      for (const ratio of [1.549, 1.55, 1.551]) {
        await load(variant(`ratio-${ratio}`, { ratio }));
        const before = await snapshot(`ratio-${ratio}`);
        assert.equal(before.active, ratio >= 1.55);
        if (ratio < 1.55) inactive(before, false);
        for (const width of [1023, 1024, 1025, 1023, 1440]) {
          await viewport(width, 900);
          await stable();
          const state = await snapshot(`ratio-${ratio}-width-${width}`);
          assert.deepEqual(state.geometry, before.geometry);
          if (ratio >= 1.55 && width >= 1024) assert.equal(state.active, true);
          else inactive(state, ratio >= 1.55);
        }
      }
    });

    const wide = variant('wide', { ratio: 3 });
    await t.test('desktop budgets, extreme content and limited horizontal space preserve geometry', async () => {
      for (const [width, height] of [[1440, 900], [1600, 1000], [1920, 1080], [2048, 1320]]) {
        await load(wide, { width, height });
        const state = await snapshot(`desktop-${width}x${height}`);
        assert.equal(state.active, true);
        assert.ok(state.receipt.width >= 960 && state.receipt.width <= Math.min(width, 1920));
      }
      await load(wide, { width: 2048, height: 3000 });
      assert.equal((await snapshot('maximum-width')).receipt.width, 1920);
      await evaluate(`document.body.style.paddingLeft = '100px'; document.body.style.paddingRight = '100px'`);
      await viewport(1024, 900);
      await stable();
      assert.equal((await snapshot('available-width-below-floor')).receipt.width, 824);
      await load(wide, { width: 1440, height: 300 });
      const geometry = (await snapshot('short-window')).geometry;
      await evaluate(`document.querySelector('.header').style.minHeight = '1000px';
        document.querySelector('.cards').innerHTML = '<div style="height:1200px">Long content</div>'`);
      await stable();
      const overflow = await snapshot('long-content');
      assert.equal(overflow.receipt.width, 960);
      assert.equal(overflow.overflow, 'authored');
      assert.ok(overflow.scrollHeight > overflow.innerHeight);
      assert.deepEqual(overflow.geometry, geometry);
    });

    await t.test('embed, presentation and print return to ordinary layout without clearing shape', async () => {
      for (const mode of ['embed', 'present', 'print']) {
        await load(wide, { query: mode === 'print' ? '' : `&${mode}=1`, print: mode === 'print' });
        inactive(await snapshot(`initial-${mode}`));
        if (mode === 'print') await media();
        else if (mode === 'present') await evaluate('Archify.presentation.exit()');
        else await evaluate("document.documentElement.removeAttribute('data-embed')");
        await stable();
        assert.equal((await snapshot(`exit-${mode}`)).active, true);
        for (let attempt = 0; attempt < 2; attempt += 1) {
          if (mode === 'print') await media('dark', false, true);
          else if (mode === 'present') await evaluate('Archify.presentation.enter()');
          else await evaluate("document.documentElement.setAttribute('data-embed', 'true')");
          await stable();
          inactive(await snapshot(`enter-${mode}-${attempt}`));
          if (mode === 'print') await media();
          else if (mode === 'present') await evaluate('Archify.presentation.exit()');
          else await evaluate("document.documentElement.removeAttribute('data-embed')");
          await stable();
          assert.equal((await snapshot(`return-${mode}-${attempt}`)).active, true);
        }
      }
    });

    await t.test('content observers and burst scheduling converge while camera state remains usable', async () => {
      await load(wide, { width: 1920, height: 1080 });
      const before = await snapshot('before-content');
      await evaluate(`document.querySelector('.header h1').textContent = 'Long reader title '.repeat(30);
        document.querySelector('.cards').innerHTML += '<div class="card" style="height:500px">Late card</div>';
        for (var i = 0; i < 30; i += 1) { dispatchEvent(new Event('resize')); Archify.readerLayout.schedule(); }
        Archify.view.zoomIn();`);
      await stable();
      const changed = await snapshot('after-content');
      assert.ok(changed.receipt.width <= before.receipt.width);
      assert.deepEqual(changed.geometry, before.geometry);
      await evaluate('Archify.view.reset()');
      await stable();
      const first = await snapshot('settled-1');
      await stable();
      assert.deepEqual(await snapshot('settled-2'), first);
    });

    await t.test('optional content and browser interfaces retain their fallback behavior', async () => {
      const file = variant('optional', { ratio: 3, beforeViewer: `
        document.querySelector('.cards').remove();
        // Other Viewer modules require the chapter control IDs. Only remove
        // Reader's optional layout selector, keeping those controls available.
        document.querySelector('.guided-views')?.classList.remove('guided-views');
        window.ResizeObserver = undefined;
        window.MutationObserver = undefined;
        Object.defineProperty(document, 'fonts', { value: undefined });
      ` });
      await load(file);
      assert.equal((await snapshot('optional-interfaces-absent')).active, true);
      await viewport(1023, 900);
      await stable();
      inactive(await snapshot('optional-resize-out'));
      await viewport(1440, 900);
      await stable();
      assert.equal((await snapshot('optional-resize-back')).active, true);
    });

    await t.test('font readiness gates sampling and pending-frame timeout remains explicit', async () => {
      const delayedFonts = variant('delayed-fonts', { ratio: 3, beforeViewer: `
        window.readerTestOriginalFonts = document.fonts;
        Object.defineProperty(document, 'fonts', { configurable: true, value: {
          ready: new Promise(function (resolve) { window.readerTestReleaseFonts = resolve; })
        } });
      ` });
      await load(delayedFonts, { waitForLayout: false });
      const fontGate = await evaluate(`(async function () {
        var resolved = false;
        var ready = Archify.readerLayout.whenStable().then(function () { resolved = true; });
        document.querySelector('.cards').style.minHeight = '450px';
        await new Promise(function (resolve) { requestAnimationFrame(function () { requestAnimationFrame(resolve); }); });
        var beforeReady = resolved;
        readerTestReleaseFonts();
        await ready;
        Object.defineProperty(document, 'fonts', { configurable: true, value: readerTestOriginalFonts });
        return { beforeReady: beforeReady, afterReady: resolved };
      })()`, true);
      assert.deepEqual(fontGate, { beforeReady: false, afterReady: true });
      await stable();
      assert.equal((await snapshot('delayed-fonts-and-content')).active, true);
      await load(wide);
      const result = await evaluate(`(async function () {
        var original = document.fonts;
        var release;
        var scheduled = 0;
        Object.defineProperty(document, 'fonts', { configurable: true, value: { ready: new Promise(function (resolve) { release = resolve; }) } });
        try {
          var waiting = Archify.waitForStableLayout({ maximumFrames: 1, schedule: function () { scheduled += 1; }, pending: function () { return true; } });
          var observed = waiting.then(function () { return 'unexpected success'; }, function (error) { return error.message; });
          await new Promise(function (resolve) { requestAnimationFrame(function () { requestAnimationFrame(resolve); }); });
          var beforeReady = scheduled;
          release();
          return { beforeReady: beforeReady, outcome: await observed, afterReady: scheduled };
        } finally { Object.defineProperty(document, 'fonts', { configurable: true, value: original }); }
      })()`, true);
      assert.equal(result.beforeReady, 0);
      assert.equal(result.afterReady, 1);
      assert.match(result.outcome, /did not reach stable dimensions/);
      await stable();
    });
  } finally {
    if (evidence) fs.writeFileSync(path.join(evidence, 'reader-observations.json'), `${JSON.stringify(records, null, 2)}\n`);
    await browser.close();
    fs.rmSync(scratch, { recursive: true, force: true });
  }
});
```

## test/readme-showcase.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const assetPath = path.join(repoRoot, 'docs', 'assets', 'archify-live-proof.gif');
const receiptPath = path.join(repoRoot, 'docs', 'assets', 'archify-live-proof.json');

function sha256(file) {
  return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
}

function git(cwd, ...args) {
  return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim();
}

function writeStarHistoryCharts(cwd, version) {
  const assets = path.join(cwd, 'assets');
  fs.mkdirSync(assets, { recursive: true });
  fs.writeFileSync(path.join(assets, 'star-history-light.svg'), `<svg><title>light ${version}</title></svg>\n`);
  fs.writeFileSync(path.join(assets, 'star-history-dark.svg'), `<svg><title>dark ${version}</title></svg>\n`);
}

function skipSubBlocks(buffer, start) {
  let offset = start;
  while (offset < buffer.length) {
    const size = buffer[offset];
    offset += 1;
    if (size === 0) return offset;
    offset += size;
  }
  throw new Error('GIF sub-block runs past end of file');
}

function inspectGif(buffer) {
  assert.match(buffer.subarray(0, 6).toString('ascii'), /^GIF8[79]a$/);
  const width = buffer.readUInt16LE(6);
  const height = buffer.readUInt16LE(8);
  const packed = buffer[10];
  let offset = 13;
  if (packed & 0x80) offset += 3 * (2 ** ((packed & 0x07) + 1));
  let frameCount = 0;
  let durationCentiseconds = 0;
  let trailer = false;

  while (offset < buffer.length) {
    const marker = buffer[offset];
    offset += 1;
    if (marker === 0x3b) {
      trailer = true;
      break;
    }
    if (marker === 0x21) {
      const label = buffer[offset];
      offset += 1;
      if (label === 0xf9) {
        const blockSize = buffer[offset];
        offset += 1;
        assert.equal(blockSize, 4, 'unexpected graphic-control block size');
        durationCentiseconds += buffer.readUInt16LE(offset + 1);
        offset += blockSize;
        assert.equal(buffer[offset], 0, 'graphic-control block missing terminator');
        offset += 1;
      } else {
        offset = skipSubBlocks(buffer, offset);
      }
      continue;
    }
    if (marker === 0x2c) {
      frameCount += 1;
      const localPacked = buffer[offset + 8];
      offset += 9;
      if (localPacked & 0x80) offset += 3 * (2 ** ((localPacked & 0x07) + 1));
      offset += 1;
      offset = skipSubBlocks(buffer, offset);
      continue;
    }
    throw new Error(`unexpected GIF marker 0x${marker.toString(16)} at ${offset - 1}`);
  }
  assert.equal(trailer, true, 'GIF trailer missing');
  return { width, height, frameCount, durationSeconds: durationCentiseconds / 100 };
}

test('README motion proof is compact, looping, and backed by current gallery artifacts', () => {
  const builder = fs.readFileSync(path.join(repoRoot, 'scripts', 'build-readme-showcase.mjs'), 'utf8');
  assert.match(builder, /\?embed=1&play=1&theme=dark#view=/);
  const buffer = fs.readFileSync(assetPath);
  const receipt = JSON.parse(fs.readFileSync(receiptPath, 'utf8'));
  const inspected = inspectGif(buffer);

  assert.deepEqual(inspected, { width: 960, height: 540, frameCount: 54, durationSeconds: 5.4 });
  assert.ok(buffer.includes(Buffer.from('NETSCAPE2.0')), 'GIF must loop continuously');
  assert.ok(buffer.byteLength <= 3 * 1024 * 1024, `README GIF is too large: ${buffer.byteLength} bytes`);
  assert.equal(receipt.schemaVersion, 1);
  assert.equal(receipt.generator, 'scripts/build-readme-showcase.mjs');
  assert.equal(receipt.output, 'docs/assets/archify-live-proof.gif');
  assert.equal(receipt.width, inspected.width);
  assert.equal(receipt.height, inspected.height);
  assert.equal(receipt.frameCount, inspected.frameCount);
  assert.equal(receipt.durationSeconds, inspected.durationSeconds);
  assert.equal(receipt.bytes, buffer.byteLength);
  assert.equal(receipt.sha256, sha256(assetPath));
  assert.deepEqual(receipt.scenes.map(scene => scene.id), ['signal-flow', 'blueprint', 'classic']);
  for (const scene of receipt.scenes) {
    const artifact = path.join(repoRoot, scene.artifact);
    assert.ok(fs.existsSync(artifact), `${scene.id}: source artifact missing`);
    assert.equal(scene.artifactSha256, sha256(artifact), `${scene.id}: source artifact drift; rebuild README showcase`);
    assert.match(scene.receipt, /9\/9 checks/);
  }
});

test('all README languages keep the product hero and retain the verified animated proof', () => {
  for (const filename of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const readme = fs.readFileSync(path.join(repoRoot, filename), 'utf8');
    const heroIndex = readme.indexOf('docs/assets/archify-readme-hero.png');
    const titleIndex = readme.indexOf('# Archify');
    const proofIndex = readme.indexOf('docs/assets/archify-live-proof.gif');
    const demosIndex = Math.max(readme.indexOf('## See Archify in action'), readme.indexOf('## 看看 Archify 能做什么'));
    assert.ok(heroIndex >= 0 && heroIndex < titleIndex, `${filename}: product hero is not above the title`);
    assert.ok(proofIndex > demosIndex, `${filename}: animated proof must live in the demo section`);
    assert.match(readme, /docs\/assets\/archify-live-proof\.gif/);
    assert.match(readme, /https:\/\/tt-a1i\.github\.io\/archify\/gallery\.html/);
  }
  assert.equal(
    fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8'),
    fs.readFileSync(path.join(repoRoot, 'README_EN.md'), 'utf8'),
    'README.md and README_EN.md must stay synchronized',
  );
});

test('README installation tables contain a complete DeepSeek Harness row', () => {
  for (const filename of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const readme = fs.readFileSync(path.join(repoRoot, filename), 'utf8');
    const row = readme.split('\n').find((line) => line.startsWith('| **DeepSeek Harness** |'));
    assert.ok(row, `${filename}: DeepSeek Harness must be an installation table row`);
    assert.equal(
      (row.match(/(?<!\\)\|/g) || []).length,
      4,
      `${filename}: DeepSeek Harness must have exactly three table cells`,
    );
    assert.ok(
      row.includes('Node `^22.19.0 \\|\\| >=24.0.0`'),
      `${filename}: Node version pipes must be escaped inside the table row`,
    );
    assert.ok(
      readme.includes(`${row}\n\n`),
      `${filename}: installation table must end after the DeepSeek Harness row`,
    );
  }
});

test('README demos use checked-in captures and live deep links below the existing hero', () => {
  const demos = [
    {
      asset: 'archify-demo-story.png',
      link: 'agent-tool-call.workflow.html?theme=dark&present=1&play=1#view=happy-path',
    },
    {
      asset: 'archify-demo-route.png',
      link: 'cache-miss.sequence.html?theme=dark&present=1#route=web~db',
    },
    {
      asset: 'archify-demo-lens.png',
      link: 'production-deployment.architecture.html?theme=dark&present=1#lens=backend~database',
    },
  ];

  for (const demo of demos) {
    const buffer = fs.readFileSync(path.join(repoRoot, 'docs', 'assets', demo.asset));
    assert.equal(buffer.subarray(1, 4).toString('ascii'), 'PNG', `${demo.asset}: invalid PNG signature`);
    assert.equal(buffer.readUInt32BE(16), 1280, `${demo.asset}: unexpected width`);
    assert.equal(buffer.readUInt32BE(20), 720, `${demo.asset}: unexpected height`);
    assert.ok(buffer.byteLength < 400 * 1024, `${demo.asset}: capture is too large`);
  }

  for (const filename of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const readme = fs.readFileSync(path.join(repoRoot, filename), 'utf8');
    const heroIndex = readme.indexOf('docs/assets/archify-readme-hero.png');
    const proofIndex = readme.indexOf('docs/assets/archify-live-proof.gif');
    const previewIndex = Math.max(readme.indexOf('## Preview'), readme.indexOf('## 预览'));
    const demosIndex = Math.max(readme.indexOf('## See Archify in action'), readme.indexOf('## 看看 Archify 能做什么'));
    const quickStartIndex = Math.max(readme.indexOf('## Quick start'), readme.indexOf('## 快速开始'));
    assert.ok(heroIndex >= 0 && heroIndex < demosIndex, `${filename}: existing hero proof moved`);
    assert.ok(demosIndex < previewIndex && previewIndex < quickStartIndex, `${filename}: demo section is misplaced`);
    assert.ok(demosIndex < proofIndex && proofIndex < previewIndex, `${filename}: animated proof is outside the demo section`);
    for (const demo of demos) {
      assert.match(readme, new RegExp(`docs/assets/${demo.asset.replaceAll('.', '\\.')}`));
      assert.ok(readme.includes(demo.link), `${filename}: missing ${demo.link}`);
    }
  }
});

test('README stays scannable without deleting the visual proof set', () => {
  const commonAssets = [
    'archify-readme-hero.png',
    'archify-live-proof.gif',
    'archify-demo-story.png',
    'archify-demo-route.png',
    'archify-demo-lens.png',
    'mco-runtime-share-card.png',
    'archify-dark.png',
    'archify-light.png',
    'archify-menu.png',
    'archify-workflow.png',
    'archify-sequence.png',
    'archify-dataflow.png',
    'archify-lifecycle.png',
  ];

  for (const filename of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const readme = fs.readFileSync(path.join(repoRoot, filename), 'utf8');
    assert.ok(readme.split('\n').length <= 295, `${filename}: README grew beyond the scannable line budget`);
    const supercode = readme.indexOf('https://supercode.sh/?utm_source=archify');
    const evermind = readme.indexOf('docs/assets/sponsors/evermind-archify-raven.png');
    assert.ok(supercode >= 0 && evermind > supercode, `${filename}: EverMind must follow Supercode`);
    assert.match(readme, filename === 'README_ZH.md' ? /不需要绑定代码库/ : /No repository is required/);
    for (const asset of commonAssets) {
      assert.ok(readme.includes(`docs/assets/${asset}`), `${filename}: visual proof ${asset} was removed`);
    }
  }

  const english = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8');
  const wordCount = english.trim().split(/\s+/).length;
  const intro = english.slice(0, english.indexOf('![License]'));
  const introBullets = intro.match(/^- \*\*/gm) || [];
  // Allow the restored EverMind sponsor description without cutting product documentation.
  assert.ok(wordCount <= 2125, `README.md is too verbose again (${wordCount} words)`);
  assert.ok(introBullets.length <= 8, `README.md has too many top-level capability bullets (${introBullets.length})`);

  const chinese = fs.readFileSync(path.join(repoRoot, 'README_ZH.md'), 'utf8');
  assert.ok(chinese.includes('docs/assets/claude-skills-settings.png'), 'README_ZH.md lost the Claude Skills setup image');
});

test('all README languages end with the self-hosted star history chart', () => {
  const lightChart = 'https://raw.githubusercontent.com/tt-a1i/archify/star-history/assets/star-history-light.svg';
  const darkChart = 'https://raw.githubusercontent.com/tt-a1i/archify/star-history/assets/star-history-dark.svg';
  const workflow = fs.readFileSync(path.join(repoRoot, '.github', 'workflows', 'star-history.yml'), 'utf8');

  for (const filename of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const readme = fs.readFileSync(path.join(repoRoot, filename), 'utf8');
    const sectionIndex = readme.lastIndexOf('## Star History');
    const contributingIndex = Math.max(readme.indexOf('## Contributing'), readme.indexOf('## 参与贡献'));
    assert.ok(sectionIndex > contributingIndex, `${filename}: Star History must follow Contributing`);
    assert.ok(readme.includes(lightChart), `${filename}: missing light star history chart`);
    assert.ok(readme.includes(darkChart), `${filename}: missing dark star history chart`);
    assert.equal(readme.trimEnd().endsWith('</p>'), true, `${filename}: Star History must remain the final section`);
  }

  assert.match(workflow, /permissions:\n  contents: write/);
  assert.match(workflow, /narayann7\/star-history-action@[0-9a-f]{40}/);
  // Upstream PR #6 migrates setup-node to Node 24 without the v1.0.6 chart changes.
  assert.match(workflow, /narayann7\/star-history-action@00dfada13f106e4114ee46728aa415857078e76c\s/);
  assert.match(workflow, /output-dir: assets/);
  assert.match(workflow, /update-readme: ['"]false['"]/);
  assert.match(workflow, /commit: ['"]false['"]/);
  assert.match(workflow, /bash scripts\/publish-star-history\.sh star-history/);
  assert.doesNotMatch(workflow, /branch: star-history/);
  assert.doesNotMatch(workflow, /xpzouying\/star-history/);
});

test('Star History publishing advances the data branch without a force push', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-star-history-'));
  const remote = path.join(fixture, 'remote.git');
  const firstCheckout = path.join(fixture, 'first');
  const secondCheckout = path.join(fixture, 'second');
  const publisher = path.join(repoRoot, 'scripts', 'publish-star-history.sh');

  try {
    git(fixture, 'init', '--bare', remote);
    git(fixture, '--git-dir', remote, 'config', 'receive.denyNonFastForwards', 'true');
    git(fixture, '--git-dir', remote, 'config', 'receive.denyDeletes', 'true');

    fs.mkdirSync(firstCheckout);
    git(firstCheckout, 'init', '-b', 'main');
    git(firstCheckout, 'config', 'user.name', 'Fixture');
    git(firstCheckout, 'config', 'user.email', 'fixture@example.com');
    fs.writeFileSync(path.join(firstCheckout, 'README.md'), 'fixture\n');
    git(firstCheckout, 'add', 'README.md');
    git(firstCheckout, 'commit', '-m', 'seed');
    git(firstCheckout, 'remote', 'add', 'origin', remote);
    git(firstCheckout, 'push', '-u', 'origin', 'main');

    const firstTemp = path.join(fixture, 'run-1');
    fs.mkdirSync(firstTemp);
    writeStarHistoryCharts(firstCheckout, 'v1');
    execFileSync('bash', [publisher, 'star-history'], {
      cwd: firstCheckout,
      env: { ...process.env, RUNNER_TEMP: firstTemp },
    });
    const firstCommit = git(fixture, '--git-dir', remote, 'rev-parse', 'refs/heads/star-history');

    git(fixture, 'clone', '--branch', 'main', remote, secondCheckout);
    const secondTemp = path.join(fixture, 'run-2');
    fs.mkdirSync(secondTemp);
    writeStarHistoryCharts(secondCheckout, 'v2');
    execFileSync('bash', [publisher, 'star-history'], {
      cwd: secondCheckout,
      env: { ...process.env, RUNNER_TEMP: secondTemp },
    });
    const secondCommit = git(fixture, '--git-dir', remote, 'rev-parse', 'refs/heads/star-history');

    assert.notEqual(secondCommit, firstCommit);
    git(fixture, '--git-dir', remote, 'merge-base', '--is-ancestor', firstCommit, secondCommit);
    assert.deepEqual(
      git(fixture, '--git-dir', remote, 'ls-tree', '-r', '--name-only', secondCommit).split('\n'),
      ['assets/star-history-dark.svg', 'assets/star-history-light.svg'],
    );
    assert.match(
      git(fixture, '--git-dir', remote, 'show', `${secondCommit}:assets/star-history-light.svg`),
      /light v2/,
    );
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});
```

## test/real-repository-proof.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { verifyRepositoryEvidence } from '../renderers/shared/repository-evidence.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const sourcePath = path.join(repoRoot, 'docs', 'cases', 'mco-runtime.architecture.json');
const artifactPath = path.join(repoRoot, 'docs', 'cases', 'mco-runtime.architecture.html');
const shareCardPath = path.join(repoRoot, 'docs', 'assets', 'mco-runtime-share-card.png');
const experimentSourcePath = path.join(repoRoot, 'experiments', 'mco-showcase', 'mco-runtime.architecture.json');
const experimentArtifactPath = path.join(repoRoot, 'experiments', 'mco-showcase', 'mco-runtime.html');
const cli = path.join(skillRoot, 'bin', 'archify.mjs');
const pinnedSource = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
const pinnedRepository = pinnedSource.meta.repository;

function evidencePayload(html) {
  const match = html.match(/<script id="archify-source-evidence-data" type="application\/json">([\s\S]*?)<\/script>/);
  assert.ok(match, 'checked-in MCO proof is missing verified repository evidence');
  return JSON.parse(match[1]);
}

function connectionLabelGeometry(html) {
  return Object.fromEntries([...html.matchAll(
    /<g data-detail="context"[^>]*data-edge-id="([^"]+)"[^>]*>[\s\S]*?<text x="([^"]+)" y="([^"]+)"/g,
  )].map((match) => [match[1], { x: Number(match[2]), y: Number(match[3]) }]));
}

function automaticMcoRoot() {
  const candidate = path.resolve(repoRoot, '..', 'mco');
  if (!fs.existsSync(path.join(candidate, '.git'))) return null;
  try {
    verifyRepositoryEvidence('architecture', pinnedSource, candidate);
    return candidate;
  } catch {
    return null;
  }
}

const pinnedMcoRoot = process.env.ARCHIFY_MCO_REPO_ROOT
  ? path.resolve(process.env.ARCHIFY_MCO_REPO_ROOT)
  : automaticMcoRoot();

test('MCO showcase preserves checked-in connection-label geometry', () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-mco-showcase-layout-'));
  try {
    const source = JSON.parse(fs.readFileSync(experimentSourcePath, 'utf8'));
    assert.deepEqual(
      source.meta.repository,
      pinnedRepository,
      'MCO case and experiment must pin the same repository revision',
    );
    delete source.meta.repository;
    for (const component of source.components) delete component.sources;
    const input = path.join(tmp, 'mco-runtime.architecture.json');
    const output = path.join(tmp, 'mco-runtime.html');
    fs.writeFileSync(input, `${JSON.stringify(source, null, 2)}\n`);

    const rendered = spawnSync(process.execPath, [
      cli,
      'render',
      'architecture',
      input,
      output,
      '--quality',
      'showcase',
    ], { encoding: 'utf8' });
    assert.equal(rendered.status, 0, `${rendered.stdout}\n${rendered.stderr}`);

    const renderedHtml = fs.readFileSync(output, 'utf8');
    const checkedInHtml = fs.readFileSync(experimentArtifactPath, 'utf8');
    const renderedLabels = connectionLabelGeometry(renderedHtml);
    assert.ok(Object.keys(renderedLabels).length >= 8, 'expected the authored MCO connection labels');
    assert.deepEqual(
      connectionLabelGeometry(checkedInHtml),
      renderedLabels,
      'checked-in MCO connection-label geometry drifted from its typed source',
    );

  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('checked-in MCO artifacts are byte-reproducible from the pinned repository revision', {
  skip: pinnedMcoRoot
    ? false
    : `Set ARCHIFY_MCO_REPO_ROOT to a matching ${pinnedRepository.url} clone containing revision ${pinnedRepository.revision.slice(0, 7)}.`,
}, () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-mco-byte-reproduction-'));
  try {
    const experimentOutput = path.join(tmp, 'mco-runtime.experiment.html');
    const experiment = spawnSync(process.execPath, [
      cli,
      'render',
      'architecture',
      experimentSourcePath,
      experimentOutput,
      '--quality',
      'showcase',
      '--repo-root',
      pinnedMcoRoot,
    ], { encoding: 'utf8' });
    assert.equal(experiment.status, 0, `${experiment.stdout}\n${experiment.stderr}`);
    assert.equal(
      fs.readFileSync(experimentOutput, 'utf8'),
      fs.readFileSync(experimentArtifactPath, 'utf8'),
      'checked-in MCO showcase drifted from its typed source and verified repository',
    );

    const caseOutput = path.join(tmp, 'mco-runtime.case.html');
    const delivered = spawnSync(process.execPath, [
      cli,
      'deliver',
      'architecture',
      sourcePath,
      caseOutput,
      '--quality',
      'showcase',
      '--repo-root',
      pinnedMcoRoot,
      '--json',
    ], { encoding: 'utf8' });
    assert.equal(delivered.status, 0, `${delivered.stdout}\n${delivered.stderr}`);
    assert.equal(JSON.parse(delivered.stdout).ok, true);
    assert.equal(
      fs.readFileSync(caseOutput, 'utf8'),
      fs.readFileSync(artifactPath, 'utf8'),
      'checked-in MCO case drifted from its typed source and verified repository',
    );
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('MCO public proof is source-backed, valid, and linked from every README', () => {
  const source = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
  assert.equal(source.meta.title, 'MCO Runtime Architecture');
  assert.equal(source.meta.quality_profile, 'showcase');
  assert.equal(source.meta.animation, 'trace');
  assert.deepEqual(source.meta.views.map(view => view.id), [
    'dispatch-path',
    'answer-evidence',
    'durable-sessions',
  ]);
  assert.equal(source.components.length, 13);
  assert.equal(source.connections.length, 12);
  assert.match(source.components.find((component) => component.id === 'router')?.sublabel || '', /\bdoctor\b/);
  assert.match(source.components.find((component) => component.id === 'adapters')?.sublabel || '', /\bdetect\b/);
  assert.match(source.meta.repository.url, /^https:\/\/github\.com\/[^/]+\/[^/]+$/);
  assert.match(source.meta.repository.revision, /^[0-9a-f]{40}$/);
  const references = source.components.reduce((count, component) => count + (component.sources?.length || 0), 0);
  assert.equal(references, 13);
  const cardCopy = JSON.stringify(source.cards);
  assert.ok(cardCopy.includes(`main @ ${source.meta.repository.revision.slice(0, 7)}`));
  assert.ok(cardCopy.includes(new URL(source.meta.repository.url).host + new URL(source.meta.repository.url).pathname));

  const checkedInHtml = fs.readFileSync(artifactPath, 'utf8');
  const evidence = evidencePayload(checkedInHtml);
  assert.equal(evidence.verified, true);
  assert.equal(evidence.repository.url, source.meta.repository.url);
  assert.equal(evidence.repository.revision, source.meta.repository.revision);
  assert.equal(evidence.repository.shortRevision, source.meta.repository.revision.slice(0, 7));
  assert.equal(evidence.referenceCount, references);
  assert.match(checkedInHtml, /Archify\.sourceEvidence\.installBeacons\(\)/);
  execFileSync(process.execPath, [cli, 'check', artifactPath], { encoding: 'utf8' });

  const noRootTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-mco-proof-no-root-'));
  try {
    const output = path.join(noRootTmp, 'mco-runtime.html');
    const result = spawnSync(process.execPath, [
      cli,
      'deliver',
      'architecture',
      sourcePath,
      output,
      '--quality',
      'showcase',
      '--json',
    ], { encoding: 'utf8' });
    assert.equal(result.status, 1);
    assert.match(JSON.parse(result.stdout).error, /Pass --repo-root/);
    assert.equal(fs.existsSync(output), false);
  } finally {
    fs.rmSync(noRootTmp, { recursive: true, force: true });
  }

  const png = fs.readFileSync(shareCardPath);
  assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a');
  assert.equal(png.readUInt32BE(16), 1200);
  assert.equal(png.readUInt32BE(20), 630);
  assert.ok(png.byteLength > 20_000, 'MCO Share Card is unexpectedly small');

  const repositorySlug = new URL(source.meta.repository.url).pathname.replace(/^\/|\/$/g, '');
  const shortRevision = source.meta.repository.revision.slice(0, 7);
  for (const filename of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const readme = fs.readFileSync(path.join(repoRoot, filename), 'utf8');
    assert.match(readme, /docs\/assets\/mco-runtime-share-card\.png/);
    assert.match(readme, /cases\/mco-runtime\.architecture\.html\?theme=dark&present=1#view=dispatch-path/);
    assert.match(readme, /docs\/cases\/mco-runtime\.architecture\.json/);
    assert.ok(readme.includes(`[\`${repositorySlug}\`](${source.meta.repository.url})`), `${filename}: repository link drifted`);
    assert.ok(readme.includes(`\`${shortRevision}\``), `${filename}: repository revision drifted`);
  }
});
```

## test/relationship-direct-explorer.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-relationship-direct-explorer-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ], { encoding: 'utf8' });
  return { result, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one viewer-only Direct Relationship Explorer', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const { result, html } = render(mode, example);
    assert.equal(result.status, 0, result.stderr);
    assert.match(html, /function installRelationshipHitTargets\(\)/, mode);
    assert.match(html, /data-relationship-hit-overlay/, mode);
    assert.match(html, /className \|\| 'relationship-hit-rail'/, mode);
    assert.doesNotMatch(canonicalSvg(html), /relationship-hit-(?:overlay|target|rail)|data-relationship-direct-active/, mode);
  }
});

test('one roving target represents each exact stable edge key and authored direction', () => {
  assert.match(template, /function relationshipHitRecords\(\)/);
  assert.match(template, /var recordsByKey = \{\}/);
  assert.match(template, /existing\.invalid = true/);
  assert.match(template, /filter\(function \(record\) \{ return !record\.invalid/);
  assert.match(template, /data-relationship-key/);
  assert.match(template, /data-relationship-from/);
  assert.match(template, /data-relationship-to/);
  assert.match(template, /target\.setAttribute\('role', 'button'\)/);
  assert.match(template, /relationshipHitOverlay\.setAttribute\('role', 'group'\)/);
  assert.match(template, /target\.setAttribute\('aria-describedby', relationshipHelp\.id\)/);
  assert.match(template, /target\.setAttribute\('tabindex', index === 0 \? '0' : '-1'\)/);
  assert.match(template, /viewerText\('viewer\.passport\.relationship\.inspect'/);
  assert.match(template, /relationshipEdgeShapes\(edge\)/);
  assert.match(template, /shape\.cloneNode\(false\)/);
  assert.match(template, /\.relationship-hit-rail \{[\s\S]*stroke: transparent;[\s\S]*stroke-width: 24/);
});

test('fine-pointer and keyboard intent preview the exact edge before activation', () => {
  assert.match(template, /function directRelationshipBlocked\(\)/);
  assert.match(template, /function scheduleDirectRelationshipPreview\(target\)/);
  assert.match(template, /if \(pinnedRelationshipKey \|\| hoveredRelationship !== target/);
  assert.match(template, /previewRelationship\(target, \{ direct: true \}\)/);
  assert.match(template, /event\.pointerType === 'touch'/);
  assert.match(template, /finePointerQuery && !finePointerQuery\.matches/);
  assert.match(template, /addEventListener\('pointerover'/);
  assert.match(template, /addEventListener\('pointerout'/);
  assert.match(template, /addEventListener\('focusin'/);
  assert.match(template, /addEventListener\('focusout'/);
  assert.match(template, /data-relationship-direct-active/);
  assert.match(template, /\.relationship-hit-target:focus-visible \.relationship-focus-rail/);
});

test('activation opens the existing source passport and pins its exact relationship row', () => {
  assert.match(template, /function inspectRelationship\(key, options\)/);
  assert.match(template, /if \(directPreviewTimer\) window\.clearTimeout\(directPreviewTimer\)/);
  assert.match(template, /if \(pinnedRelationshipKey === key\)/);
  assert.match(template, /set\(record\.from, \{ toggle: false, updateUrl: false \}\)/);
  assert.match(template, /relationshipList\.querySelectorAll\('\[data-relationship-key\]'\)/);
  assert.match(template, /pinnedRelationship = row/);
  assert.match(template, /pinnedRelationshipKey = key/);
  assert.match(template, /data-relationship-pin-active/);
  assert.match(template, /function clearRelationshipPreview\(options\)/);
  assert.match(template, /clearRelationshipPreview\(\{ clearPin: true \}\)/);
  assert.match(template, /copyBtn\.textContent = viewerText\('viewer\.passport\.copyNode'\)/);
  assert.match(template, /previewRelationship\(row\)/);
  assert.match(template, /inspectRelationship: inspectRelationship/);
  assert.match(template, /event\.key !== 'ArrowRight'/);
  assert.match(template, /event\.key !== 'ArrowLeft'/);
  assert.match(template, /event\.key !== 'Home'/);
  assert.match(template, /event\.key !== 'End'/);
  assert.match(template, /event\.key === 'Enter' \|\| event\.key === ' '/);
});

test('direct relationship targets support one-tap touch, yield to stronger states, and stay export-clean', () => {
  assert.match(template, /html\.getAttribute\('data-embed'\) === 'true'/);
  assert.match(template, /svg\.hasAttribute\('data-story-active'\)/);
  assert.match(template, /svg\.hasAttribute\('data-route-active'\)/);
  assert.match(template, /svg\.hasAttribute\('data-lens-active'\)/);
  assert.match(template, /event\.target\.closest\('\[data-relationship-hit-key\]'\)/);
  assert.match(template, /@media \(hover: none\), \(pointer: coarse\)[\s\S]*\.relationship-hit-rail \{ stroke-width: 24/);
  assert.match(template, /@media print \{[\s\S]*\.relationship-hit-overlay/);
  assert.match(template, /clone\.removeAttribute\('data-relationship-direct-active'\)/);
  assert.match(template, /clone\.removeAttribute\('data-relationship-pin-active'\)/);
  assert.match(template, /clone\.querySelectorAll\('\[data-relationship-hit-overlay\]'\)/);
  assert.match(template, /\[data-relationship-hit-overlay\][^']*\[data-relationship-pulse-overlay\]/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/relationship-lens.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-relationship-lens-'));

const CASES = {
  architecture: { example: 'web-app.architecture.json', collection: 'connections' },
  workflow: { example: 'agent-tool-call.workflow.json', collection: 'edges' },
  sequence: { example: 'cache-miss-request.sequence.json', collection: 'messages' },
  dataflow: { example: 'product-analytics.dataflow.json', collection: 'flows' },
  lifecycle: { example: 'agent-run.lifecycle.json', collection: 'transitions' },
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function svg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

function escapeAttr(value) {
  return String(value)
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;');
}

test('all typed renderers expose named, stable relationships without changing geometry', () => {
  for (const [mode, config] of Object.entries(CASES)) {
    const html = render(mode, config.example);
    const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', config.example), 'utf8'));
    const relationships = source[config.collection];
    const diagram = svg(html);
    const keys = new Set(Array.from(diagram.matchAll(/data-edge-key="(\d+)"/g), (match) => match[1]));

    assert.equal(keys.size, relationships.length, `${mode} keeps one stable key per source relationship`);
    relationships.forEach((relationship, index) => {
      const expectedKey = source.schema_version === 2 ? '\\d+' : String(index);
      assert.match(diagram, new RegExp(`data-edge-from="${escapeAttr(relationship.from)}"[^>]+data-edge-to="${escapeAttr(relationship.to)}"[^>]+data-edge-key="${expectedKey}"`), `${mode} relationship ${index}`);
      if (relationship.label) {
        assert.match(diagram, new RegExp(`data-edge-label="${escapeAttr(relationship.label).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`), `${mode} named relationship ${index}`);
      }
    });
    assert.match(diagram, /data-node-id="[^"]+" data-node-label="[^"]+" tabindex="0"/, mode);
  }
});

test('relationship lens groups incoming, outgoing, and self-loop paths and follows neighbors', () => {
  const html = render('architecture', CASES.architecture.example);
  assert.match(html, /id="focus-chip" hidden role="region" aria-labelledby="relationship-lens-title"/);
  assert.match(html, /id="relationship-lens-list" aria-label="Connected relationships"/);
  assert.match(html, /function relationshipsFor\(id, byId\)/);
  assert.match(html, /direction = from === id && to === id \? 'loop' : \(from === id \? 'out' : 'in'\)/);
  assert.match(html, /\{ id: 'out', label: viewerText\('viewer\.passport\.relationship\.group\.out'\) \}/);
  assert.match(html, /\{ id: 'in', label: viewerText\('viewer\.passport\.relationship\.group\.in'\) \}/);
  assert.match(html, /data-relationship-target/);
  assert.match(html, /data-relationship-key/);
  assert.match(html, /data-relationship-from/);
  assert.match(html, /data-relationship-to/);
  assert.match(html, /set\(id, \{ toggle: false \}\)/);
  assert.match(html, /Archify\.view\.reveal\(\[id\], \{ includeNeighbors: true, reason: 'relationship' \}\)/);
  assert.doesNotMatch(svg(html), /relationship-lens|Connected relationships/);
});

test('relationship preview precisely links pointer and keyboard rows to an edge and its endpoints', () => {
  const html = render('sequence', CASES.sequence.example);
  const diagram = svg(html);
  assert.match(html, /function previewRelationship\(button, options\)/);
  assert.match(html, /edge\.getAttribute\('data-edge-key'\) === key/);
  assert.match(html, /data-relationship-preview-source/);
  assert.match(html, /data-relationship-preview-target/);
  assert.match(html, /addEventListener\('pointerover'/);
  assert.match(html, /addEventListener\('pointerout'/);
  assert.match(html, /addEventListener\('focusin'/);
  assert.match(html, /addEventListener\('focusout'/);
  assert.match(html, /pinnedRelationship \|\| focusedRelationship \|\| hoveredRelationship/);
  assert.doesNotMatch(diagram, /data-relationship-preview(?:-active|-node|-source|-target)?=/);
});

test('relationship preview is export-clean and visually geometry-neutral', () => {
  const html = render('dataflow', CASES.dataflow.example);
  assert.match(html, /clone\.removeAttribute\('data-relationship-preview-active'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-relationship-preview\], \[data-relationship-preview-node\], \[data-relationship-preview-source\], \[data-relationship-preview-target\]'\)/);
  assert.match(html, /!clone\.hasAttribute\('data-relationship-preview-active'\)/);
  assert.match(html, /Relationship Preview is temporary exploration state layered on top of/);
  assert.doesNotMatch(html, /data-relationship-preview[^\n{]*\{[^}]*\b(?:x|y|transform)\s*:/);
});

test('relationship lens is keyboard navigable, mobile-pinned, and excluded from embed and print', () => {
  const html = render('workflow', CASES.workflow.example);
  assert.match(html, /event\.key !== 'ArrowDown'/);
  assert.match(html, /event\.key !== 'ArrowUp'/);
  assert.match(html, /event\.key !== 'Home'/);
  assert.match(html, /event\.key !== 'End'/);
  assert.match(html, /buttons\[index\]\.focus\(\)/);
  assert.match(html, /data-wide-diagram="true"\] \.focus-chip/);
  assert.match(html, /\.focus-chip\[data-relationship-previewing="true"\] \.relationship-lens-list/);
  assert.match(html, /\.relationship-lens-row:not\(\[data-preview-active="true"\]\)/);
  assert.match(html, /var mobile = window\.innerWidth <= 720/);
  assert.match(html, /previewingOnMobile = mobile && chip\.getAttribute\('data-relationship-previewing'\) === 'true'/);
  assert.match(html, /nodeCenter < \(visibleTop \+ visibleBottom\) \/ 2 \? pinnedBottom : pinnedTop/);
  assert.match(html, /html\[data-embed="true"\] \.focus-chip/);
  assert.match(html, /\.toolbar, \.diagram-nav, \.focus-chip, \.guided-views/);
  assert.match(html, /chip\.hidden = options\.hideChip === true \|\| normalized\.length !== 1 \|\| selectionMode/);
  assert.match(html, /event\.target\.closest\('\.diagram-nav, \.focus-chip, \.node-finder, \.diagram-guide, \.overview-map, \.route-probe, \.semantic-lens'\)/);
  assert.match(html, /function placeRelationshipLens\(\)/);
  assert.match(html, /visibleTop = Math\.max\(padding, -containerRect\.top \+ padding\)/);
  assert.match(html, /window\.addEventListener\('scroll', requestLensPlacement, \{ passive: true \}\)/);
  assert.match(html, /container\.addEventListener\('scroll', requestLensPlacement, \{ passive: true \}\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/relationship-permalink.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-relationship-permalink-'));

const CASES = {
  architecture: { example: 'web-app.architecture.json', collection: 'connections' },
  workflow: { example: 'agent-tool-call.workflow.json', collection: 'edges' },
  sequence: { example: 'cache-miss-request.sequence.json', collection: 'messages' },
  dataflow: { example: 'product-analytics.dataflow.json', collection: 'flows' },
  lifecycle: { example: 'agent-run.lifecycle.json', collection: 'transitions' },
};

function fixture(mode) {
  return JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', CASES[mode].example), 'utf8'));
}

function run(mode, doc, suffix) {
  const input = path.join(tmp, `${mode}-${suffix}.json`);
  const output = path.join(tmp, `${mode}-${suffix}.html`);
  fs.writeFileSync(input, JSON.stringify(doc));
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, output,
  ], { encoding: 'utf8' });
  return { result, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

function relationshipKey(html, id) {
  return html.match(new RegExp(`data-edge-key="(\\d+)" data-edge-id="${id}"`))?.[1] ?? null;
}

test('all typed renderers preserve optional authored relationship ids beside runtime keys', () => {
  for (const mode of Object.keys(CASES)) {
    const doc = fixture(mode);
    doc[CASES[mode].collection][0].id = 'shareable-relation';
    const { result, html } = run(mode, doc, 'stable-id');
    assert.equal(result.status, 0, `${mode}: ${result.stderr}`);
    assert.match(html, /data-edge-key="\d+" data-edge-id="shareable-relation"/, mode);
    assert.match(canonicalSvg(html), /data-edge-id="shareable-relation"/, mode);
  }
});

test('authored relationship identity and readable-v2 compiler keys survive source-order changes', () => {
  const original = fixture('workflow');
  const reordered = fixture('workflow');
  const moved = reordered.edges.shift();
  reordered.edges.splice(1, 0, moved);

  const first = run('workflow', original, 'original-order');
  const second = run('workflow', reordered, 'reordered');
  assert.equal(first.result.status, 0, first.result.stderr);
  assert.equal(second.result.status, 0, second.result.stderr);
  assert.notEqual(relationshipKey(first.html, 'request-chat'), null);
  assert.equal(
    relationshipKey(second.html, 'request-chat'),
    relationshipKey(first.html, 'request-chat'),
  );
  assert.match(first.html, /'#relation=' \+ encodeURIComponent\(record\.id\)/);
  assert.match(second.html, /'#relation=' \+ encodeURIComponent\(record\.id\)/);
});

test('relationship ids stay optional and duplicate ids fail closed in the shared zero-install path', () => {
  for (const mode of Object.keys(CASES)) {
    const idless = fixture(mode);
    delete idless[CASES[mode].collection][0].id;
    const plain = run(mode, idless, 'idless');
    assert.equal(plain.result.status, 0, `${mode}: ${plain.result.stderr}`);
    const keyZeroTags = Array.from(plain.html.matchAll(/<(?:path|g)\b[^>]*data-edge-key="0"[^>]*>/g), (match) => match[0]);
    assert.ok(keyZeroTags.length > 0, `${mode} emits runtime key zero`);
    assert.ok(keyZeroTags.every((tag) => !tag.includes('data-edge-id=')), `${mode} does not invent a durable id`);

    const duplicate = fixture(mode);
    duplicate[CASES[mode].collection][1].id = duplicate[CASES[mode].collection][0].id;
    const rejected = run(mode, duplicate, 'duplicate');
    assert.notEqual(rejected.result.status, 0, mode);
    assert.match(rejected.result.stderr, /Relationship identity validation failed/);
    assert.match(rejected.result.stderr, /duplicates relationship id/);
  }
});

test('relationship id syntax is schema-checked before viewer output is written', () => {
  const doc = fixture('workflow');
  doc.edges[0].id = 'not a stable id';
  const { result, html } = run('workflow', doc, 'invalid-id');
  assert.notEqual(result.status, 0);
  assert.match(result.stderr, /\/edges\/0\/id/);
  assert.match(result.stderr, /must match pattern/);
  assert.equal(html, '');
});

test('the viewer restores and copies stable relation links without exposing numeric keys', () => {
  const { result, html } = run('workflow', fixture('workflow'), 'viewer');
  assert.equal(result.status, 0, result.stderr);
  assert.match(html, /var edgeId = edge\.getAttribute\('data-edge-id'\) \|\| ''/);
  assert.match(html, /target\.setAttribute\('data-relationship-id', record\.id\)/);
  assert.match(html, /button\.setAttribute\('data-relationship-id', relationship\.id\)/);
  assert.match(html, /copyBtn\.textContent = viewerText\('viewer\.passport\.copyRelation'\)/);
  assert.match(html, /'#relation=' \+ encodeURIComponent\(record\.id\)/);
  assert.match(html, /var relation = params\.get\('relation'\)/);
  assert.match(html, /inspectRelationshipById\(relation, \{ updateUrl: false, toggle: false \}\)/);
  assert.match(html, /if \(html\.getAttribute\('data-embed'\) === 'true'\) return false/);
  assert.match(html, /if \(html\.getAttribute\('data-embed'\) === 'true' \|\|\s*!inspectRelationshipById/);
  assert.match(html, /params\.get\('focus'\) \|\| params\.get\('relation'\)/);
  assert.match(html, /if \(!reveal\(\)\) requestAnimationFrame\(reveal\)/);
  assert.match(html, /inspectRelationshipById: inspectRelationshipById/);
  assert.match(html, /id: record\.id \|\| null, key: record\.key/);
  assert.doesNotMatch(html, /'#relation=' \+ encodeURIComponent\(record\.key\)/);
});

test('runtime overlays drop durable edge ids while canonical SVG keeps authored identity', () => {
  const { result, html } = run('architecture', fixture('architecture'), 'export-boundary');
  assert.equal(result.status, 0, result.stderr);
  assert.match(canonicalSvg(html), /data-edge-id="users-to-cdn"/);
  assert.doesNotMatch(canonicalSvg(html), /data-relationship-hit-overlay|data-relationship-id=/);
  assert.ok((html.match(/clone\.removeAttribute\('data-edge-id'\)/g) || []).length >= 6);
  assert.match(html, /querySelectorAll\('\[data-relationship-hit-overlay\]'\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/relationship-pulse.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-relationship-pulse-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers inherit one exact-edge Directional Flow Pulse', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /function renderRelationshipPulse\(key\)/, mode);
    assert.match(html, /function relationshipTokenKind\(edge\)/, mode);
    assert.match(html, /function relationshipTokenGeometry\(shape, kind, key, options\)/, mode);
    assert.match(html, /data-relationship-pulse-overlay/, mode);
    assert.match(html, /setAttribute\('class', 'relationship-flow-pulse'\)/, mode);
    assert.doesNotMatch(canonicalSvg(html), /relationship-flow-(?:pulse|token)|data-relationship-(?:pulse|token)/, mode);
  }
});

test('pulse clones only the previewed authored geometry and keeps source-to-target direction', () => {
  const html = render('sequence', CASES.sequence);
  assert.match(html, /edge\.getAttribute\('data-edge-key'\) === key/);
  assert.match(html, /function relationshipEdgeShapes\(edge\)/);
  assert.match(html, /shape\.cloneNode\(false\)/);
  assert.match(html, /clone\.removeAttribute\('marker-end'\)/);
  assert.match(html, /clone\.removeAttribute\('data-edge-key'\)/);
  assert.match(html, /clone\.removeAttribute\('filter'\)/);
  assert.match(html, /clone\.setAttribute\('pathLength', '1'\)/);
  assert.match(html, /overlay\.setAttribute\('data-relationship-pulse-key', key\)/);
  assert.match(html, /function relationshipTokenPath\(shape\)/);
  assert.match(html, /tagName === 'path'.+shape\.getAttribute\('d'\)/s);
  assert.match(html, /tagName === 'line'/);
  assert.match(html, /tagName === 'polyline' && shape\.points/);
  assert.match(html, /motion\.setAttribute\('path', pathData\)/);
  assert.match(html, /motion\.setAttribute\('rotate', 'auto'\)/);
  assert.match(html, /svg\.insertBefore\(overlay, firstNode\)/);
  assert.match(html, /stroke-dashoffset: -1/);
});

test('semantic token classification is evidence-based and fail-closed', () => {
  const html = render('lifecycle', CASES.lifecycle);
  assert.match(html, /a-security.+sourceKind === 'security'.+targetKind === 'failure'.+return 'security'/s);
  assert.match(html, /a-dashed.+sourceKind === 'messagebus'.+targetKind === 'messagebus'.+return 'event'/s);
  assert.match(html, /sourceKind === 'database' \|\| targetKind === 'database'.+return 'data'/s);
  assert.match(html, /targetKind === 'waiting' \|\| targetKind === 'success'.+return 'state'/s);
  assert.match(html, /return 'call';/);
  assert.doesNotMatch(html, /relationshipTokenKind[\s\S]{0,1800}data-edge-label/);
});

test('semantic tokens use five distinct inline SVG cues on one finite timing owner', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /data-token-kind', kind/);
  assert.match(html, /kind === 'data'[\s\S]+kind === 'event'[\s\S]+kind === 'security'[\s\S]+kind === 'state'/);
  assert.match(html, /document\.createElementNS\(svgNamespace, 'animateMotion'\)/);
  assert.match(html, /motion\.setAttribute\('dur', options\.duration \|\| '1\.2s'\)/);
  assert.match(html, /animation: archify-relationship-token-life 1\.2s linear 1 both/);
  assert.match(html, /semantic-flow-token-halo/);
  assert.match(html, /Archify\.flowTokens = \{/);
  assert.match(html, /data-relationship-token-kind', tokenKind/);
  assert.match(html, /var tokenAdded = false/);
  assert.doesNotMatch(html, /relationship-flow-token[^}]+infinite/);
});

test('pulse is finite, event-owned, preset-aware, touch-safe, and motion-safe', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /animation: archify-relationship-pulse 1\.2s linear 1 both/);
  assert.match(html, /@keyframes archify-relationship-token-life/);
  assert.doesNotMatch(html, /relationship-flow-pulse[^}]+infinite/);
  assert.match(html, /var activeRelationshipPreview = null/);
  assert.match(html, /if \(next === activeRelationshipPreview\) return/);
  assert.match(html, /event\.pointerType === 'touch'/);
  assert.match(html, /finePointerQuery && !finePointerQuery\.matches/);
  assert.match(html, /Archify\.motionGovernor && Archify\.motionGovernor\.isPaused\(\)/);
  assert.match(html, /document\.hidden/);
  assert.match(html, /addEventListener\('animationcancel', finishPulse/);
  assert.match(html, /reducedMotionQuery\.addEventListener\('change', syncRelationshipMotionPreference\)/);
  assert.match(html, /if \(event\.matches\) removeRelationshipPulse\(\)/);
  assert.match(html, /document\.addEventListener\('visibilitychange'/);
  assert.match(html, /html\[data-embed="true"\] \.relationship-pulse-overlay/);
  assert.match(html, /svg\[data-preset="signal-flow"\] \.relationship-flow-pulse/);
  assert.match(html, /svg\[data-preset="blueprint"\] \.relationship-flow-pulse/);
  assert.match(html, /@media \(prefers-reduced-motion: reduce\)[\s\S]+\.relationship-pulse-overlay \{ display: none !important; \}/);
});

test('pulse has one owner and never enters print or canonical exports', () => {
  const html = render('dataflow', CASES.dataflow);
  assert.match(html, /function removeRelationshipPulse\(\)/);
  assert.match(html, /svg\.querySelectorAll\('\[data-relationship-pulse-overlay\]'\)/);
  assert.match(html, /@media print \{[\s\S]+\.relationship-pulse-overlay \{ display: none !important; \}/);
  assert.match(html, /clone\.querySelectorAll\('\[data-relationship-pulse-overlay\]'\)/);
  assert.match(html, /\[data-relationship-pulse-overlay\],[^']*\[data-relationship-preview\]/);
  assert.doesNotMatch(canonicalSvg(html), /relationship-flow-(?:pulse|token)|data-relationship-(?:pulse|token)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/release-identity.test.mjs

```js
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import assert from 'node:assert/strict';

const here = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(here, '..', '..');
const checker = path.join(repoRoot, 'scripts', 'check-release-identity.mjs');

function writeFile(root, relativePath, content) {
  const target = path.join(root, relativePath);
  fs.mkdirSync(path.dirname(target), { recursive: true });
  fs.writeFileSync(target, content);
}

function runCheck(root) {
  return spawnSync(process.execPath, [checker, '--root', root], {
    encoding: 'utf8',
  });
}

function stableUpdateManifest(version) {
  return JSON.stringify({
    schemaVersion: 1,
    skillId: 'archify',
    channel: 'stable',
    version,
    publishedAt: '2026-07-29T00:00:00Z',
    source: {
      repository: 'https://github.com/tt-a1i/archify',
      ref: `v${version}`,
      treeSha: 'a'.repeat(40),
    },
    artifact: { sha256: 'b'.repeat(64) },
    summary: 'Published stable release.',
    releaseNotes: `https://github.com/tt-a1i/archify/releases/tag/v${version}`,
    severity: 'normal',
  });
}

function writeValidDevelopmentFixture(root, overrides = {}) {
  const version = '2.13.0-dev.0';
  const english = [
    '![Development Version](https://img.shields.io/badge/version-2.13.0--dev.0-blue)',
    '',
    `Current development version: \`v${version}\``,
    '',
    'Raven uses manual ZIP installation: extract archify.zip into `~/.raven/workspace/skills`, which yields `~/.raven/workspace/skills/archify`; Raven is not an agent-switcher target.',
  ].join('\n');
  const chinese = [
    '![开发版本](https://img.shields.io/badge/version-2.13.0--dev.0-blue)',
    '',
    `当前开发版本：\`v${version}\``,
    '',
    'Raven 使用 ZIP 手动安装：将 archify.zip 解压到 `~/.raven/workspace/skills`，解压后会得到 `~/.raven/workspace/skills/archify`；Raven 不属于 Agent 切换器目标。',
  ].join('\n');
  const files = {
    'archify/package.json': JSON.stringify({ version }),
    'archify/package-lock.json': JSON.stringify({ version, packages: { '': { version } } }),
    'archify/skill-release.json': JSON.stringify({
      schemaVersion: 1,
      skillId: 'archify',
      channel: 'development',
      version,
      source: { repository: 'https://github.com/tt-a1i/archify' },
      updateManifestUrl: 'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json',
    }),
    'docs/skill-updates/archify/stable.json': stableUpdateManifest('2.12.0'),
    'archify/SKILL.md': '---\nmetadata:\n  version: "2.13"\n---\n',
    'archify/assets/template.html': '<meta name="generator" content="archify 2.13.0-dev.0">',
    'CHANGELOG.md': [
      '# Changelog',
      '',
      '## [Unreleased]',
      '',
      `> Development identity: \`v${version}\`. Not a stable release.`,
      '',
      '### Added',
      '- Real unreleased work.',
      '',
      '## [2.12.0] — 2026-07-23',
      '',
    ].join('\n'),
    'README.md': english,
    'README_EN.md': english,
    'README_ZH.md': chinese,
    'scripts/start-template.html': 'development · 开发版 · [[ARCHIFY_VERSION]]',
    'scripts/guide-template.html': 'development · 开发版 · [[ARCHIFY_VERSION]]',
    'scripts/gallery-template.html': 'development · 开发版 · [[ARCHIFY_VERSION]]',
    'docs/index.html': `<span>development · v${version} · 开发版 · 9/9 checks</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills，解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>`,
    'docs/start.html': `<span>development · v${version} · 开发版</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills，解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>`,
    'ROADMAP.md': `The current development line is \`v${version}\`; it contains the work under Changelog Unreleased and is not a stable release.`,
  };
  for (const [relativePath, content] of Object.entries({ ...files, ...overrides })) {
    writeFile(root, relativePath, content);
  }
}

function writeValidStableFixture(root, overrides = {}) {
  const version = '2.13.0';
  const english = [
    '![Stable Version](https://img.shields.io/badge/version-2.13.0-blue)',
    '',
    `Current stable version: \`v${version}\``,
    '',
    'Raven uses manual ZIP installation: extract archify.zip into `~/.raven/workspace/skills`, which yields `~/.raven/workspace/skills/archify`; Raven is not an agent-switcher target.',
  ].join('\n');
  const chinese = [
    '![稳定版本](https://img.shields.io/badge/version-2.13.0-blue)',
    '',
    `当前稳定版本：\`v${version}\``,
    '',
    'Raven 使用 ZIP 手动安装：将 archify.zip 解压到 `~/.raven/workspace/skills`，解压后会得到 `~/.raven/workspace/skills/archify`；Raven 不属于 Agent 切换器目标。',
  ].join('\n');
  const files = {
    'archify/package.json': JSON.stringify({ version }),
    'archify/package-lock.json': JSON.stringify({ version, packages: { '': { version } } }),
    'archify/skill-release.json': JSON.stringify({
      schemaVersion: 1,
      skillId: 'archify',
      channel: 'stable',
      version,
      source: { repository: 'https://github.com/tt-a1i/archify' },
      updateManifestUrl: 'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json',
    }),
    'docs/skill-updates/archify/stable.json': stableUpdateManifest(version),
    'archify/SKILL.md': '---\nmetadata:\n  version: "2.13"\n---\n',
    'archify/assets/template.html': '<meta name="generator" content="archify 2.13.0">',
    'CHANGELOG.md': [
      '# Changelog',
      '',
      '## [Unreleased]',
      '',
      '## [2.13.0] — 2026-07-29',
      '- Published work.',
      '',
    ].join('\n'),
    'README.md': english,
    'README_EN.md': english,
    'README_ZH.md': chinese,
    'scripts/start-template.html': 'stable · 稳定版 · [[ARCHIFY_VERSION]]',
    'scripts/guide-template.html': 'stable · 稳定版 · [[ARCHIFY_VERSION]]',
    'scripts/gallery-template.html': 'stable · 稳定版 · [[ARCHIFY_VERSION]]',
    'docs/index.html': `<span>stable · v${version} · 稳定版 · 9/9 checks</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills，解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>`,
    'docs/start.html': `<span>stable · v${version} · 稳定版</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills，解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>`,
    'ROADMAP.md': `The current stable version is \`v${version}\`.`,
  };
  for (const [relativePath, content] of Object.entries({ ...files, ...overrides })) {
    writeFile(root, relativePath, content);
  }
}

test('an empty Unreleased section accepts a coherent stable release identity', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidStableFixture(fixture);

    const result = runCheck(fixture);
    assert.equal(result.status, 0, result.stderr);
    assert.match(result.stdout, /release identity ok: 2\.13\.0/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('stable release preparation allows only the immediate prior public manifest', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    const changelog = [
      '# Changelog',
      '',
      '## [Unreleased]',
      '',
      '## [2.13.0] — 2026-07-29',
      '- Release being prepared.',
      '',
      '## [2.12.0] — 2026-07-23',
      '- Previously published release.',
      '',
    ].join('\n');
    writeValidStableFixture(fixture, {
      'CHANGELOG.md': changelog,
      'docs/skill-updates/archify/stable.json': stableUpdateManifest('2.12.0'),
    });

    const prior = runCheck(fixture);
    assert.equal(prior.status, 0, prior.stderr);

    writeFile(fixture, 'docs/skill-updates/archify/stable.json', stableUpdateManifest('2.11.0'));
    const stale = runCheck(fixture);
    assert.notEqual(stale.status, 0);
    assert.match(stale.stderr, /immediate prior v2\.12\.0/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('the embedded update identity must match the package release exactly', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'archify/skill-release.json': JSON.stringify({
        schemaVersion: 1,
        skillId: 'archify',
        channel: 'stable',
        version: '2.12.0',
        source: { repository: 'https://example.com/untrusted/archify' },
        updateManifestUrl: 'https://example.com/latest.json',
      }),
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /archify\/skill-release\.json must identify archify 2\.13\.0-dev\.0 as development/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('the published update manifest must track the newest stable changelog release', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'docs/skill-updates/archify/stable.json': stableUpdateManifest('2.11.0'),
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /stable\.json must describe the newest published stable v2\.12\.0/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('the published update manifest must use a canonical UTC timestamp', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    const manifest = JSON.parse(stableUpdateManifest('2.12.0'));
    manifest.publishedAt = '2026-07-29T08:00:00+08:00';
    writeValidDevelopmentFixture(fixture, {
      'docs/skill-updates/archify/stable.json': JSON.stringify(manifest),
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /stable\.json must describe the newest published stable v2\.12\.0/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('package identities reject leading-zero core and prerelease identifiers', () => {
  for (const version of ['02.13.0', '2.13.0-dev.01']) {
    const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
    try {
      writeValidDevelopmentFixture(fixture, {
        'archify/package.json': JSON.stringify({ version }),
      });
      const result = runCheck(fixture);
      assert.notEqual(result.status, 0);
      assert.match(result.stderr, /not a supported SemVer identity/, version);
    } finally {
      fs.rmSync(fixture, { recursive: true, force: true });
    }
  }
});

test('the newest stable release is selected by SemVer rather than changelog order', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'CHANGELOG.md': [
        '# Changelog',
        '',
        '## [Unreleased]',
        '',
        '> Development identity: `v2.13.0-dev.0`. Not a stable release.',
        '',
        '### Added',
        '- Real unreleased work.',
        '',
        '## [2.11.0] — 2026-07-16',
        '',
        '## [2.12.0] — 2026-07-23',
        '',
      ].join('\n'),
    });

    const result = runCheck(fixture);
    assert.equal(result.status, 0, result.stderr);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('real Unreleased changes cannot reuse a stable published package identity', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeFile(fixture, 'archify/package.json', JSON.stringify({ version: '2.12.0' }));
    writeFile(fixture, 'CHANGELOG.md', [
      '# Changelog',
      '',
      '## [Unreleased]',
      '',
      '### Added',
      '- Real unreleased work.',
      '',
      '## [2.12.0] — 2026-07-23',
      '',
    ].join('\n'));

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /Unreleased changes require a prerelease package version/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('package, lockfile, Skill metadata, escaped Shields badge, and public docs share one development identity', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeFile(fixture, 'archify/package.json', JSON.stringify({ version: '2.13.0-dev.0' }));
    writeFile(fixture, 'archify/package-lock.json', JSON.stringify({
      version: '2.12.0',
      packages: { '': { version: '2.12.0' } },
    }));
    writeFile(fixture, 'archify/SKILL.md', '---\nmetadata:\n  version: "2.12"\n---\n');
    writeFile(fixture, 'CHANGELOG.md', [
      '# Changelog',
      '',
      '## [Unreleased]',
      '',
      '### Added',
      '- Real unreleased work.',
      '',
      '## [2.12.0] — 2026-07-23',
      '',
    ].join('\n'));
    const staleEnglish = [
      '![Version](https://img.shields.io/badge/version-2.13.0-blue)',
      '',
      'Archify 2.12 includes unreleased capabilities.',
    ].join('\n');
    writeFile(fixture, 'README.md', staleEnglish);
    writeFile(fixture, 'README_EN.md', staleEnglish);
    writeFile(fixture, 'README_ZH.md', '![Version](https://img.shields.io/badge/version-2.13.0-blue)\n\nArchify 2.12 包含未发布能力。\n');
    writeFile(fixture, 'docs/index.html', '<span>Agent Skill · v2.12.0</span>');
    writeFile(fixture, 'docs/start.html', '<span>Archify v2.12.0</span>');

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /package-lock\.json must match 2\.13\.0-dev\.0/);
    assert.match(result.stderr, /SKILL\.md metadata version 2\.12 must map to package 2\.13\.0-dev\.0/);
    assert.match(result.stderr, /README\.md must advertise development identity v2\.13\.0-dev\.0/);
    assert.match(result.stderr, /docs\/index\.html must advertise development identity v2\.13\.0-dev\.0/);
    assert.match(result.stderr, /docs\/start\.html must advertise development identity v2\.13\.0-dev\.0/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('landing proof receipt matches the current nine-check artifact contract', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'docs/index.html': '<span>development · v2.13.0-dev.0 · 开发版 · 8/8 checks</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills，解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>',
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /docs\/index\.html proof receipt must say 9\/9/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('landing rejects every stale N/N contract count even when 9/9 is also present', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'docs/index.html': [
        '<span>development · v2.13.0-dev.0 · 开发版 · 9/9 checks</span>',
        '<span>legacy receipt · 7/7 checks</span>',
        '<p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills，解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>',
      ].join('\n'),
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /every N\/N proof receipt must be exactly 9\/9; found 7\/7/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('Raven stays a truthful manual ZIP install and never becomes a generated agent-switcher command', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'docs/start.html': [
        '<span>development · v2.13.0-dev.0 · 开发版</span>',
        '<button data-agent="raven">Raven</button>',
        '<pre>npx skills add tt-a1i/archify --agent raven</pre>',
      ].join('\n'),
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /Raven must remain a manual ZIP installation outside the agent switcher/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('Raven instructions reject extracting the archive into the final Skill directory', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    const nestedEnglish = [
      '![Development Version](https://img.shields.io/badge/version-2.13.0--dev.0-blue)',
      '',
      'Current development version: `v2.13.0-dev.0`',
      '',
      'Raven is manual ZIP only: extract archify.zip into `~/.raven/workspace/skills/archify`; Raven is not an agent-switcher target.',
    ].join('\n');
    writeValidDevelopmentFixture(fixture, {
      'README.md': nestedEnglish,
      'README_EN.md': nestedEnglish,
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /extract archify\.zip into ~\/\.raven\/workspace\/skills, yielding ~\/\.raven\/workspace\/skills\/archify/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('renderer template generator carries the complete package prerelease identity', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'archify/assets/template.html': '<meta name="generator" content="archify 2.12.0">',
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /archify\/assets\/template\.html generator must be archify 2\.13\.0-dev\.0/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('roadmap current identity follows the package release state', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'ROADMAP.md': 'The current development line is `v2.12.0`; it is not a stable release.',
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /ROADMAP\.md must declare the current development line as v2\.13\.0-dev\.0/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('generated public-page templates keep a development marker and version placeholder', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidDevelopmentFixture(fixture, {
      'scripts/gallery-template.html': 'Proof Lab / 2.12.0',
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /scripts\/gallery-template\.html must use \[\[ARCHIFY_VERSION\]\] with development and 开发版 labels/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('stable public-page templates reject development labels on version-bearing fallbacks', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
  try {
    writeValidStableFixture(fixture, {
      'scripts/guide-template.html': [
        '<span data-i18n="versionLabel">Scenario guide / development / v[[ARCHIFY_VERSION]]</span>',
        "versionLabel:'Scenario guide / stable / v[[ARCHIFY_VERSION]]'",
        "versionLabel:'场景指南 / 稳定版 / v[[ARCHIFY_VERSION]]'",
      ].join('\n'),
    });

    const result = runCheck(fixture);
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /scripts\/guide-template\.html must not label \[\[ARCHIFY_VERSION\]\] as development or 开发版/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});
```

## test/release-package-gates.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { stageCleanSkill } from '../../scripts/stage-clean-skill.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..', '..');
const canonicalZipNodeMajor = 22;
const currentNodeMajor = Number(process.versions.node.split('.')[0]);
const canonicalZipTest = (name, fn) => test(name, {
  skip: currentNodeMajor === canonicalZipNodeMajor
    ? false
    : `canonical ZIP builds require Node ${canonicalZipNodeMajor}`,
}, fn);

function spawnBuildZip(outputPath, options = {}) {
  const script = path.join(repoRoot, 'scripts', 'build-zip.sh');
  const { cwd = repoRoot, ...rest } = options;
  if (process.platform === 'win32') {
    const bashCandidates = [
      process.env.BASH,
      'C:\\Program Files\\Git\\bin\\bash.exe',
      'bash',
    ].filter(Boolean);
    for (const bash of bashCandidates) {
      if (bash.includes('\\') && !fs.existsSync(bash)) continue;
      const result = spawnSync(bash, [script, outputPath], { cwd, encoding: 'utf8', ...rest });
      if (result.status !== 127) return result;
    }
  }
  return spawnSync(script, [outputPath], { cwd, encoding: 'utf8', ...rest });
}

function workflowStep(workflow, name) {
  const marker = `      - name: ${name}`;
  const start = workflow.indexOf(marker);
  assert.notEqual(start, -1, `workflow is missing the "${name}" step`);
  const next = workflow.indexOf('\n      - ', start + marker.length);
  return workflow.slice(start, next === -1 ? workflow.length : next);
}

function workflowJob(workflow, name) {
  const marker = `  ${name}:`;
  const start = workflow.indexOf(marker);
  assert.notEqual(start, -1, `workflow is missing the "${name}" job`);
  const next = workflow.slice(start + marker.length).search(/\n  [a-z][a-z0-9-]*:\n/);
  return workflow.slice(start, next === -1 ? workflow.length : start + marker.length + next);
}

test('release prevents manifest preannouncement and smokes the exact archive before upload', () => {
  const workflow = fs.readFileSync(path.join(repoRoot, '.github', 'workflows', 'release.yml'), 'utf8');
  const tagFetch = workflowStep(workflow, 'Fetch exact tag object');
  const tagGate = workflowStep(workflow, 'Tag must match package.json version');
  const annotatedTagGate = workflowStep(workflow, 'Stable release tag must be annotated');
  const publicationOrder = workflowStep(workflow, 'Stable notifier manifest must remain on the previous release');
  const build = workflowStep(workflow, 'Build skill archive');
  const smoke = workflowStep(workflow, 'Validate the exact release archive without installing dependencies');
  const freshness = workflowStep(workflow, 'Committed zip must match the build (same gate as CI)');
  const upload = workflowStep(workflow, 'Create GitHub Release with the zip attached');
  const followUp = workflowStep(workflow, 'Record stable notifier publication follow-up');

  assert.ok(workflow.indexOf(tagFetch) < workflow.indexOf(tagGate), 'the real tag object must be fetched before release identity checks');
  assert.ok(workflow.indexOf(tagGate) < workflow.indexOf(publicationOrder), 'tag/version gate must precede the publication-order gate');
  assert.ok(workflow.indexOf(tagGate) < workflow.indexOf(annotatedTagGate), 'tag/version gate must precede the annotated-tag gate');
  assert.ok(workflow.indexOf(annotatedTagGate) < workflow.indexOf(publicationOrder), 'annotated-tag gate must precede the publication-order gate');
  assert.ok(workflow.indexOf(publicationOrder) < workflow.indexOf(build), 'manifest preannouncement must fail before the release build');
  assert.ok(workflow.indexOf(build) < workflow.indexOf(smoke), 'release smoke must follow the archive build');
  assert.ok(workflow.indexOf(smoke) < workflow.indexOf(freshness), 'release smoke must inspect the built archive before comparison');
  assert.ok(workflow.indexOf(freshness) < workflow.indexOf(upload), 'freshness must pass before release upload');
  assert.ok(workflow.indexOf(upload) < workflow.indexOf(followUp), 'manifest follow-up must be recorded only after Release creation');

  assert.match(tagFetch, /git fetch --force --no-tags origin/);
  assert.match(tagFetch, /refs\/tags\/\$\{GITHUB_REF_NAME\}:refs\/tags\/\$\{GITHUB_REF_NAME\}/);
  assert.match(tagGate, /require\('\.\/archify\/package\.json'\)\.version/);
  assert.match(tagGate, /GITHUB_REF_NAME#v/);
  assert.match(annotatedTagGate, /steps\.release-kind\.outputs\.prerelease == 'false'/);
  assert.match(annotatedTagGate, /git cat-file -t "refs\/tags\/\$\{GITHUB_REF_NAME\}"/);
  assert.match(annotatedTagGate, /stable releases require an annotated tag/);
  assert.match(publicationOrder, /compareSemver\(published\.version, releasing\) >= 0/);
  assert.match(publicationOrder, /publish the manifest in a follow-up commit/);
  assert.match(build, /run: scripts\/build-zip\.sh \/tmp\/archify-built\.zip/);
  assert.match(smoke, /unzip -q \/tmp\/archify-built\.zip -d "\$package_root"/);
  assert.match(smoke, /node scripts\/package-smoke\.mjs "\$package_root\/archify"/);
  assert.doesNotMatch(smoke, /\bnpm\s+(?:ci|install)\b/);
  assert.match(freshness, /cmp -s \/tmp\/archify-built\.zip archify\.zip/);
  assert.match(upload, /uses: softprops\/action-gh-release@v3\s/);
  assert.match(upload, /files: archify\.zip/);
  assert.match(followUp, /docs\/skill-updates\/archify\/stable\.json/);
});

test('an exact tag fetch restores an annotated object after a SHA-only checkout', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-tag-fetch-'));
  const source = path.join(fixture, 'source');
  const checkout = path.join(fixture, 'checkout');
  const runGit = (cwd, args) => spawnSync('git', args, { cwd, encoding: 'utf8' });

  try {
    fs.mkdirSync(source);
    assert.equal(runGit(source, ['init', '--quiet']).status, 0);
    assert.equal(runGit(source, ['config', 'user.name', 'Archify Test']).status, 0);
    assert.equal(runGit(source, ['config', 'user.email', 'archify@example.invalid']).status, 0);
    fs.writeFileSync(path.join(source, 'release.txt'), 'release\n');
    assert.equal(runGit(source, ['add', 'release.txt']).status, 0);
    assert.equal(runGit(source, ['commit', '--quiet', '-m', 'release fixture']).status, 0);
    assert.equal(runGit(source, ['tag', '-a', 'v1.0.0', '-m', 'Release v1.0.0']).status, 0);
    const commit = runGit(source, ['rev-parse', 'HEAD']).stdout.trim();

    fs.mkdirSync(checkout);
    assert.equal(runGit(checkout, ['init', '--quiet']).status, 0);
    assert.equal(runGit(checkout, ['remote', 'add', 'origin', source]).status, 0);
    assert.equal(runGit(checkout, [
      'fetch', '--no-tags', '--depth=1', 'origin',
      `+${commit}:refs/tags/v1.0.0`,
    ]).status, 0);
    assert.equal(runGit(checkout, ['cat-file', '-t', 'refs/tags/v1.0.0']).stdout.trim(), 'commit');

    assert.equal(runGit(checkout, [
      'fetch', '--force', '--no-tags', 'origin',
      '+refs/tags/v1.0.0:refs/tags/v1.0.0',
    ]).status, 0);
    assert.equal(runGit(checkout, ['cat-file', '-t', 'refs/tags/v1.0.0']).stdout.trim(), 'tag');
    assert.equal(runGit(checkout, ['rev-parse', 'refs/tags/v1.0.0^{}']).stdout.trim(), commit);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('CI binds a public notifier manifest to the Release asset, tagged archive, and tag tree build', () => {
  const workflow = fs.readFileSync(path.join(repoRoot, '.github', 'workflows', 'ci.yml'), 'utf8');
  const job = workflowJob(workflow, 'published-update-manifest');
  assert.match(job, /validateStableUpdateManifest/);
  assert.match(job, /releases\/latest/);
  assert.match(job, /latest_stable_tag" != "v\$\{manifest_version\}"/);
  assert.match(job, /releases\/tags\/v\$\{manifest_version\}/);
  assert.match(job, /select\(\.draft == false and \.prerelease == false\)/);
  assert.match(job, /select\(\.name == "archify\.zip"\)/);
  assert.match(job, /releases\/assets\/\$\{release_asset_id\}/);
  assert.match(job, /Accept: application\/octet-stream/);
  assert.match(job, /refs\/tags\/v\$\{manifest_version\}:refs\/tags\/v\$\{manifest_version\}/);
  assert.match(job, /git show "v\$\{manifest_version\}:archify\.zip" > "\$tagged_archive"/);
  assert.match(job, /cmp -s "\$published_archive" "\$tagged_archive"/);
  assert.match(job, /check-stable-update-manifest\.mjs/);
  assert.match(job, /--archive "\$published_archive"/);
  assert.match(job, /--tag "v\$\{manifest_version\}"/);
  assert.match(job, /--source-ref "v\$\{manifest_version\}"/);
  assert.match(job, /git worktree add --detach "\$tag_checkout" "v\$\{manifest_version\}"/);
  assert.match(job, /"\$tag_checkout\/scripts\/build-zip\.sh" "\$rebuilt_archive"/);
  assert.match(job, /cmp -s "\$rebuilt_archive" "\$tagged_archive"/);
  assert.match(job, /manifest_version" == "2\.15\.0"/);
  assert.match(job, /missing the deterministic archive builder/);
});

test('release docs disclose that mutable Release assets are verified only at deployment time', () => {
  const design = fs.readFileSync(
    path.join(repoRoot, 'docs', 'skill-embedded-optional-update-notifier-design.md'),
    'utf8',
  );
  assert.match(design, /部署时点/);
  assert.match(design, /部署后替换[^。]*不会自动触发复验/);
  assert.match(design, /immutable release/i);
  assert.doesNotMatch(design, /即使 Release 资产后来可被替换，也不能脱离/);
});

test('GitHub Pages deploys docs only after every repository gate succeeds', () => {
  const workflow = fs.readFileSync(path.join(repoRoot, '.github', 'workflows', 'ci.yml'), 'utf8');
  const job = workflowJob(workflow, 'deploy-pages');
  assert.match(job, /if: github\.event_name == 'push' && github\.ref == 'refs\/heads\/main'/);
  assert.match(job, /needs: \[test, webm-artifact, zip-freshness, published-update-manifest, package-smoke\]/);
  assert.match(job, /pages: write/);
  assert.match(job, /id-token: write/);
  assert.match(job, /repos\/\$\{GITHUB_REPOSITORY\}\/git\/ref\/heads\/main/);
  assert.match(job, /current_main" == "\$GITHUB_SHA"/);
  assert.match(job, /Skipping obsolete Pages deployment/);
  assert.match(job, /if: steps\.deployment-head\.outputs\.current == 'true'/);
  assert.match(job, /actions\/configure-pages@v6/);
  // v5 delegates to upload-artifact v7 (Node 24); v4 still embeds Node 20.
  assert.match(job, /actions\/upload-pages-artifact@v5\s/);
  assert.match(job, /path: docs/);
  assert.match(job, /actions\/deploy-pages@v5/);
});

test('release tags with a SemVer prerelease are marked prerelease and never become latest', () => {
  const workflow = fs.readFileSync(path.join(repoRoot, '.github', 'workflows', 'release.yml'), 'utf8');
  const classifier = workflowStep(workflow, 'Classify stable and prerelease tags');
  const upload = workflowStep(workflow, 'Create GitHub Release with the zip attached');

  assert.ok(workflow.indexOf(classifier) < workflow.indexOf(upload), 'release kind must be known before upload');
  assert.match(classifier, /version="\$\{GITHUB_REF_NAME#v\}"/);
  assert.match(classifier, /validateLocalRelease/);
  assert.match(classifier, /update-contract\.mjs/);
  assert.match(classifier, /release\.version !== process\.argv\[1\]/);
  assert.match(classifier, /if \[\[ "\$channel" == "development" \]\]/);
  assert.match(classifier, /echo "prerelease=true" >> "\$GITHUB_OUTPUT"/);
  assert.match(classifier, /echo "make_latest=false" >> "\$GITHUB_OUTPUT"/);
  assert.match(classifier, /echo "prerelease=false" >> "\$GITHUB_OUTPUT"/);
  assert.match(classifier, /echo "make_latest=true" >> "\$GITHUB_OUTPUT"/);
  assert.match(upload, /prerelease: \$\{\{ steps\.release-kind\.outputs\.prerelease \}\}/);
  assert.match(upload, /make_latest: \$\{\{ steps\.release-kind\.outputs\.make_latest \}\}/);
});

test('package smoke rejects every dependency or repository-only artifact', () => {
  const packageSmoke = path.join(repoRoot, 'scripts', 'package-smoke.mjs');
  const forbidden = [
    { relative: 'node_modules', kind: 'directory' },
    { relative: 'package-lock.json', kind: 'file' },
    { relative: path.join('scripts', 'generate-validators.mjs'), kind: 'file' },
    { relative: 'test', kind: 'directory' },
    { relative: '.hive', kind: 'directory' },
    { relative: '.workbuddy', kind: 'directory' },
  ];

  for (const { relative, kind } of forbidden) {
    const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-gate-'));
    try {
      fs.mkdirSync(path.join(fixture, 'bin'), { recursive: true });
      fs.writeFileSync(path.join(fixture, 'bin', 'archify.mjs'), '');
      const target = path.join(fixture, relative);
      if (kind === 'directory') fs.mkdirSync(target, { recursive: true });
      else {
        fs.mkdirSync(path.dirname(target), { recursive: true });
        fs.writeFileSync(target, '');
      }

      const result = spawnSync(process.execPath, [packageSmoke, fixture], { encoding: 'utf8' });
      assert.notEqual(result.status, 0, `${relative} must fail package smoke`);
      assert.match(
        `${result.stdout}\n${result.stderr}`,
        new RegExp(`packaged skill must not contain ${relative.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`),
        `${relative} must be rejected explicitly`,
      );
    } finally {
      fs.rmSync(fixture, { recursive: true, force: true });
    }
  }
});

test('package smoke verifies the embedded notifier identity and local disable switch', () => {
  const source = fs.readFileSync(path.join(repoRoot, 'scripts', 'package-smoke.mjs'), 'utf8');
  assert.match(source, /scripts', 'check-update\.mjs/);
  assert.match(source, /scripts', 'update-contract\.mjs/);
  assert.match(source, /skill-release\.json/);
  assert.match(source, /ARCHIFY_UPDATE_CHECK_DISABLED: '1'/);
  assert.match(source, /reason !== 'disabled'/);
});

test('package smoke rejects a missing or modified distribution license', () => {
  const packageSmoke = path.join(repoRoot, 'scripts', 'package-smoke.mjs');
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-license-gate-'));
  try {
    const staged = path.join(fixture, 'archify');
    stageCleanSkill({ repoRoot, destination: staged });
    const licensePath = path.join(staged, 'LICENSE');

    fs.rmSync(licensePath);
    let result = spawnSync(process.execPath, [packageSmoke, staged], { encoding: 'utf8' });
    assert.notEqual(result.status, 0, 'missing LICENSE must fail package smoke');
    assert.match(`${result.stdout}\n${result.stderr}`, /packaged skill is missing LICENSE/);

    const repositoryLicense = fs.readFileSync(path.join(repoRoot, 'LICENSE'), 'utf8');
    fs.writeFileSync(
      licensePath,
      repositoryLicense.replace(
        'Copyright (c) 2025 Cocoon AI',
        'Copyright (c) 2025 Cocoon AI (original "architecture-diagram-generator")',
      ),
    );
    result = spawnSync(process.execPath, [packageSmoke, staged], { encoding: 'utf8' });
    assert.notEqual(result.status, 0, 'modified upstream notice must fail package smoke');
    assert.match(`${result.stdout}\n${result.stderr}`, /missing the exact Cocoon AI copyright line/);

    fs.writeFileSync(licensePath, 'Copyright (c) 2025 Cocoon AI\n');
    result = spawnSync(process.execPath, [packageSmoke, staged], { encoding: 'utf8' });
    assert.notEqual(result.status, 0, 'truncated LICENSE must fail package smoke');
    assert.match(`${result.stdout}\n${result.stderr}`, /must byte-match the repository LICENSE/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('package smoke rejects missing, modified, or incomplete third-party notices', () => {
  const packageSmoke = path.join(repoRoot, 'scripts', 'package-smoke.mjs');
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-notices-gate-'));
  try {
    const staged = path.join(fixture, 'archify');
    stageCleanSkill({ repoRoot, destination: staged });
    const noticesPath = path.join(staged, 'THIRD_PARTY_NOTICES.md');

    fs.rmSync(noticesPath);
    let result = spawnSync(process.execPath, [packageSmoke, staged], { encoding: 'utf8' });
    assert.notEqual(result.status, 0, 'missing notices must fail package smoke');
    assert.match(`${result.stdout}\n${result.stderr}`, /missing THIRD_PARTY_NOTICES\.md/);

    const repositoryNotices = fs.readFileSync(path.join(repoRoot, 'THIRD_PARTY_NOTICES.md'), 'utf8');
    fs.writeFileSync(noticesPath, repositoryNotices.replace('Simple Icons 16.28.0', 'Simple Icons'));
    result = spawnSync(process.execPath, [packageSmoke, staged], { encoding: 'utf8' });
    assert.notEqual(result.status, 0, 'modified notices must fail package smoke');
    assert.match(`${result.stdout}\n${result.stderr}`, /must byte-match the repository notice/);

    fs.writeFileSync(noticesPath, 'Simple Icons 16.28.0\n');
    result = spawnSync(process.execPath, [packageSmoke, staged], { encoding: 'utf8' });
    assert.notEqual(result.status, 0, 'incomplete notices must fail package smoke');
    assert.match(`${result.stdout}\n${result.stderr}`, /packaged THIRD_PARTY_NOTICES\.md is incomplete/);

    const comparisonRoot = path.join(fixture, 'comparison-root');
    fs.mkdirSync(comparisonRoot);
    fs.copyFileSync(path.join(repoRoot, 'LICENSE'), path.join(comparisonRoot, 'LICENSE'));
    const synchronizedIncomplete = repositoryNotices
      .replace(/## OpenAI mark[\s\S]*?## No additional rights granted/, '## No additional rights granted');
    fs.writeFileSync(path.join(comparisonRoot, 'THIRD_PARTY_NOTICES.md'), synchronizedIncomplete);
    fs.writeFileSync(noticesPath, synchronizedIncomplete);
    result = spawnSync(process.execPath, [packageSmoke, staged], {
      encoding: 'utf8',
      env: {
        ...process.env,
        ARCHIFY_PACKAGE_SMOKE_NOTICE_ROOT: comparisonRoot,
      },
    });
    assert.notEqual(result.status, 0, 'byte-identical incomplete notices must fail package smoke');
    assert.match(
      `${result.stdout}\n${result.stderr}`,
      /repository THIRD_PARTY_NOTICES\.md is incomplete; missing required disclosure: .*OpenAI/,
    );
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('package smoke increments an arbitrary-precision SemVer patch without Number coercion', () => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-bigint-version-'));
  const skillRoot = path.join(scratch, 'archify');
  try {
    stageCleanSkill({ repoRoot, destination: skillRoot });
    const packagePath = path.join(skillRoot, 'package.json');
    const releasePath = path.join(skillRoot, 'skill-release.json');
    const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
    const release = JSON.parse(fs.readFileSync(releasePath, 'utf8'));
    const version = '2.16.9007199254740993';
    packageJson.version = version;
    release.version = version;
    release.channel = 'stable';
    fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
    fs.writeFileSync(releasePath, `${JSON.stringify(release, null, 2)}\n`);

    const smoke = spawnSync(process.execPath, [path.join(repoRoot, 'scripts/package-smoke.mjs'), skillRoot], {
      cwd: repoRoot,
      encoding: 'utf8',
    });
    assert.equal(smoke.status, 0, smoke.stderr || smoke.stdout);
  } finally {
    fs.rmSync(scratch, { recursive: true, force: true });
  }
});

test('archive build refuses to silently omit required release files', () => {
  const buildSource = fs.readFileSync(path.join(repoRoot, 'scripts', 'build-zip.sh'), 'utf8');
  const stageSource = fs.readFileSync(path.join(repoRoot, 'scripts', 'stage-clean-skill.mjs'), 'utf8');
  assert.match(buildSource, /stage-clean-skill\.mjs/);
  assert.match(stageSource, /archify\/LICENSE/);
  assert.match(stageSource, /archify\/THIRD_PARTY_NOTICES\.md/);
  assert.match(stageSource, /archify\/skill-release\.json/);
  assert.match(stageSource, /archify\/scripts\/check-update\.mjs/);
  assert.match(stageSource, /archify\/scripts\/update-contract\.mjs/);
  assert.match(stageSource, /git', \['ls-files', '--stage', '-z'/);
  assert.match(stageSource, /required package input is not tracked by Git/);
  assert.match(stageSource, /required repository input is not tracked by Git/);
});

canonicalZipTest('package smoke rejects every dependency metadata field in a built package', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-built-package-gate-'));
  try {
    const archive = path.join(fixture, 'archify.zip');
    const build = spawnBuildZip(archive);
    assert.equal(build.status, 0, `${build.stdout}\n${build.stderr}`);

    const extracted = path.join(fixture, 'extracted');
    fs.mkdirSync(extracted);
    const unzip = spawnSync('unzip', ['-q', archive, '-d', extracted], { encoding: 'utf8' });
    assert.equal(unzip.status, 0, `${unzip.stdout}\n${unzip.stderr}`);
    const builtPackage = path.join(extracted, 'archify');
    const dependencyFields = {
      dependencies: { runtime: '1.0.0' },
      devDependencies: { build: '1.0.0' },
      optionalDependencies: { optional: '1.0.0' },
      peerDependencies: { peer: '1.0.0' },
      bundledDependencies: ['bundled'],
      bundleDependencies: ['bundle-alias'],
    };

    for (const [field, value] of Object.entries(dependencyFields)) {
      const caseRoot = path.join(fixture, field);
      fs.cpSync(builtPackage, caseRoot, { recursive: true });
      const packagePath = path.join(caseRoot, 'package.json');
      const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
      packageJson[field] = value;
      fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);

      const result = spawnSync(process.execPath, [path.join(repoRoot, 'scripts', 'package-smoke.mjs'), caseRoot], {
        encoding: 'utf8',
      });
      assert.notEqual(result.status, 0, `${field} must fail package smoke`);
      assert.match(`${result.stdout}\n${result.stderr}`, new RegExp(`dependency metadata: ${field}\\b`));
    }
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

canonicalZipTest('built archives contain the embedded notifier runtime', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-notifier-package-gate-'));
  try {
    const archive = path.join(fixture, 'archify.zip');
    const build = spawnSync(path.join(repoRoot, 'scripts', 'build-zip.sh'), [archive], {
      cwd: repoRoot,
      encoding: 'utf8',
    });
    assert.equal(build.status, 0, `${build.stdout}\n${build.stderr}`);

    const listing = spawnSync('unzip', ['-Z1', archive], { encoding: 'utf8' });
    assert.equal(listing.status, 0, `${listing.stdout}\n${listing.stderr}`);
    const entries = new Set(listing.stdout.trim().split('\n'));
    assert.ok(entries.has('archify/skill-release.json'));
    assert.ok(entries.has('archify/scripts/check-update.mjs'));
    assert.ok(entries.has('archify/scripts/update-contract.mjs'));
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

canonicalZipTest('archive build excludes untracked files and external symlinks from the live working tree', () => {
  const marker = `.package-negative-${process.pid}-${Date.now()}`;
  const untracked = path.join(repoRoot, 'archify', `${marker}.txt`);
  const externalRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-external-'));
  const externalTarget = path.join(externalRoot, 'secret.txt');
  const externalLink = path.join(repoRoot, 'archify', `${marker}.link`);
  const outputRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-negative-'));
  const archive = path.join(outputRoot, 'archify.zip');

  try {
    fs.writeFileSync(untracked, 'must not ship\n');
    fs.writeFileSync(externalTarget, 'external content must not ship\n');
    fs.symlinkSync(externalTarget, externalLink, 'file');

    const build = spawnBuildZip(archive);
    assert.equal(build.status, 0, `${build.stdout}\n${build.stderr}`);

    const listing = spawnSync('unzip', ['-Z1', archive], { encoding: 'utf8' });
    assert.equal(listing.status, 0, `${listing.stdout}\n${listing.stderr}`);
    assert.doesNotMatch(listing.stdout, new RegExp(marker), 'untracked files and symlinks must not enter the archive');
  } finally {
    fs.rmSync(untracked, { force: true });
    fs.rmSync(externalLink, { force: true });
    fs.rmSync(externalRoot, { recursive: true, force: true });
    fs.rmSync(outputRoot, { recursive: true, force: true });
  }
});

canonicalZipTest('archive build rejects an unmerged index and preserves an existing archive', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-unmerged-'));
  const scripts = path.join(fixture, 'scripts');
  const skill = path.join(fixture, 'archify');
  const license = path.join(skill, 'LICENSE');
  const archive = path.join(fixture, 'trusted.zip');
  const trusted = Buffer.from('trusted archive bytes');
  const git = (args, options = {}) => spawnSync('git', args, {
    cwd: fixture,
    encoding: 'utf8',
    ...options,
  });

  try {
    fs.mkdirSync(path.join(skill, 'renderers', 'shared'), { recursive: true });
    fs.mkdirSync(path.join(skill, 'scripts'), { recursive: true });
    fs.mkdirSync(scripts);
    fs.copyFileSync(path.join(repoRoot, 'scripts', 'build-zip.sh'), path.join(scripts, 'build-zip.sh'));
    fs.copyFileSync(
      path.join(repoRoot, 'scripts', 'write-deterministic-zip.mjs'),
      path.join(scripts, 'write-deterministic-zip.mjs'),
    );
    fs.copyFileSync(
      path.join(repoRoot, 'scripts', 'stage-clean-skill.mjs'),
      path.join(scripts, 'stage-clean-skill.mjs'),
    );
    fs.copyFileSync(
      path.join(repoRoot, 'scripts', 'third-party-notices-contract.mjs'),
      path.join(scripts, 'third-party-notices-contract.mjs'),
    );
    fs.writeFileSync(path.join(skill, 'renderers', 'shared', 'generated-validators.mjs'), 'export default {};\n');
    fs.writeFileSync(path.join(skill, 'scripts', 'check-update.mjs'), 'export {};\n');
    fs.writeFileSync(path.join(skill, 'scripts', 'update-contract.mjs'), 'export {};\n');
    fs.writeFileSync(path.join(skill, 'skill-release.json'), '{}\n');
    fs.writeFileSync(path.join(skill, 'package.json'), '{"name":"archify"}\n');
    fs.writeFileSync(license, 'base\n');
    assert.equal(git(['init']).status, 0);
    assert.equal(git(['add', '.']).status, 0);

    const base = git(['hash-object', '-w', '--stdin'], { input: 'base\n' });
    const ours = git(['hash-object', '-w', '--stdin'], { input: 'ours\n' });
    const theirs = git(['hash-object', '-w', '--stdin'], { input: 'theirs\n' });
    for (const result of [base, ours, theirs]) assert.equal(result.status, 0, result.stderr);
    const indexInfo = [
      `100644 ${base.stdout.trim()} 1\tarchify/LICENSE`,
      `100644 ${ours.stdout.trim()} 2\tarchify/LICENSE`,
      `100644 ${theirs.stdout.trim()} 3\tarchify/LICENSE`,
      '',
    ].join('\n');
    assert.equal(git(['update-index', '--index-info'], { input: indexInfo }).status, 0);
    fs.writeFileSync(license, '<<<<<<< ours\n=======\n>>>>>>> theirs\n');
    fs.writeFileSync(archive, trusted);

    const build = spawnSync('bash', [path.join(scripts, 'build-zip.sh'), archive], {
      cwd: fixture,
      encoding: 'utf8',
    });
    assert.notEqual(build.status, 0, `${build.stdout}\n${build.stderr}`);
    assert.match(build.stderr, /refusing to package unmerged index entry/);
    assert.ok(fs.readFileSync(archive).equals(trusted), 'a failed build must preserve the trusted archive');
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('archive build rejects non-canonical Node versions before publishing output', {
  skip: currentNodeMajor === canonicalZipNodeMajor
    ? `requires a Node major other than ${canonicalZipNodeMajor}`
    : false,
}, () => {
  const outputRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-node-version-'));
  try {
    const archive = path.join(outputRoot, 'archify.zip');
    const trusted = Buffer.from('existing canonical archive');
    fs.writeFileSync(archive, trusted);
    const build = spawnBuildZip(archive);
    assert.notEqual(build.status, 0, `${build.stdout}\n${build.stderr}`);
    assert.match(build.stderr, /canonical archify\.zip builds require Node 22/);
    assert.ok(fs.readFileSync(archive).equals(trusted), 'version rejection must preserve the canonical archive');
  } finally {
    fs.rmSync(outputRoot, { recursive: true, force: true });
  }
});

canonicalZipTest('archive build is byte-for-byte reproducible across caller time zones without system zip', () => {
  const outputRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-reproducible-'));
  const utcArchive = path.join(outputRoot, 'utc.zip');
  const honoluluArchive = path.join(outputRoot, 'honolulu.zip');

  try {
    for (const [archive, timezone] of [
      [utcArchive, 'UTC'],
      [honoluluArchive, 'Pacific/Honolulu'],
    ]) {
      const build = spawnBuildZip(archive, {
        env: { ...process.env, TZ: timezone },
      });
      assert.equal(build.status, 0, `${build.stdout}\n${build.stderr}`);
    }

    assert.ok(
      fs.readFileSync(utcArchive).equals(fs.readFileSync(honoluluArchive)),
      'identical tracked inputs must produce identical archive bytes',
    );
    assert.ok(
      fs.readFileSync(utcArchive).equals(fs.readFileSync(path.join(repoRoot, 'archify.zip'))),
      'the canonical archive toolchain must reproduce the committed archive bytes',
    );
    assert.deepEqual(
      fs.readdirSync(outputRoot).sort(),
      ['honolulu.zip', 'utc.zip'],
      'successful archive publication must not leave temporary files behind',
    );
  } finally {
    fs.rmSync(outputRoot, { recursive: true, force: true });
  }
});

test('archive build accepts Windows-style absolute output paths', {
  skip: process.platform !== 'win32'
    ? 'Windows drive paths only reach build-zip.sh on win32'
    : currentNodeMajor === canonicalZipNodeMajor
      ? false
      : `canonical ZIP builds require Node ${canonicalZipNodeMajor}`,
}, () => {
  const outputRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-package-windows-path-'));
  const backslashArchive = path.win32.join(outputRoot, 'backslash.zip');
  const slashArchive = path.win32.join(outputRoot, 'slash.zip').replace(/\\/g, '/');
  // The \\.\ device-namespace form is a \\-prefixed absolute path like a UNC
  // share: MSYS passes it to bash unchanged from a native parent and Node
  // resolves it natively. A real network share cannot be assumed in the suite,
  // and the \\?\ extended-length prefix is stripped by MSYS's command-line
  // parsing when bash is started from a native process.
  const deviceArchive = `\\\\.\\${path.win32.join(outputRoot, 'device.zip')}`;

  try {
    for (const archive of [backslashArchive, slashArchive, deviceArchive]) {
      const build = spawnBuildZip(archive);
      assert.equal(build.status, 0, `${build.stdout}\n${build.stderr}`);
      assert.ok(fs.existsSync(archive), `archive must be written to the requested path: ${archive}`);
    }
    const reference = fs.readFileSync(backslashArchive);
    for (const archive of [slashArchive, deviceArchive]) {
      assert.ok(
        reference.equals(fs.readFileSync(archive)),
        `every Windows path form must produce identical archive bytes: ${archive}`,
      );
    }
    assert.deepEqual(
      fs.readdirSync(outputRoot).sort(),
      ['backslash.zip', 'device.zip', 'slash.zip'],
      'successful archive publication must not leave temporary files behind',
    );
  } finally {
    fs.rmSync(outputRoot, { recursive: true, force: true });
  }
});

function centralDirectoryModes(archive) {
  const buffer = fs.readFileSync(archive);
  const end = buffer.length - 22;
  assert.equal(buffer.readUInt32LE(end), 0x06054b50, 'archive must end with an end-of-central-directory record');
  const entryCount = buffer.readUInt16LE(end + 10);
  let offset = buffer.readUInt32LE(end + 16);
  const modes = {};
  for (let index = 0; index < entryCount; index += 1) {
    assert.equal(buffer.readUInt32LE(offset), 0x02014b50, 'central directory entry signature');
    const nameLength = buffer.readUInt16LE(offset + 28);
    const extraLength = buffer.readUInt16LE(offset + 30);
    const commentLength = buffer.readUInt16LE(offset + 32);
    const name = buffer.toString('utf8', offset + 46, offset + 46 + nameLength);
    modes[name] = (buffer.readUInt32LE(offset + 38) >>> 16) & 0o7777;
    offset += 46 + nameLength + extraLength + commentLength;
  }
  return modes;
}

function writeArchive(stagedRoot, archive, modeManifest) {
  const args = [path.join(repoRoot, 'scripts', 'write-deterministic-zip.mjs'), stagedRoot, archive];
  if (modeManifest !== null) args.push('--mode-manifest', modeManifest);
  return spawnSync(process.execPath, args, { encoding: 'utf8' });
}

function stagedFixture(files) {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-zip-modes-'));
  const staged = path.join(fixture, 'archify');
  for (const [relative, { content, mode }] of Object.entries(files)) {
    const target = path.join(staged, ...relative.split('/'));
    fs.mkdirSync(path.dirname(target), { recursive: true });
    fs.writeFileSync(target, content);
    fs.chmodSync(target, mode);
  }
  return { fixture, staged };
}

test('archive writer records Git index modes from the manifest, not filesystem bits', () => {
  const { fixture, staged } = stagedFixture({
    'bin/tool.mjs': { content: '#!/usr/bin/env node\n', mode: 0o644 },
    'docs/notes.txt': { content: 'notes\n', mode: 0o755 },
  });
  try {
    const manifest = path.join(fixture, 'modes.json');
    fs.writeFileSync(manifest, JSON.stringify({ 'bin/tool.mjs': '100755', 'docs/notes.txt': '100644' }));

    const first = path.join(fixture, 'first.zip');
    const build = writeArchive(staged, first, manifest);
    assert.equal(build.status, 0, `${build.stdout}\n${build.stderr}`);
    assert.deepEqual(centralDirectoryModes(first), {
      'archify/bin/tool.mjs': 0o755,
      'archify/docs/notes.txt': 0o644,
    });

    // Flip the on-disk bits; the recorded modes must still decide the bytes.
    fs.chmodSync(path.join(staged, 'bin', 'tool.mjs'), 0o755);
    fs.chmodSync(path.join(staged, 'docs', 'notes.txt'), 0o644);
    const second = path.join(fixture, 'second.zip');
    const rebuild = writeArchive(staged, second, manifest);
    assert.equal(rebuild.status, 0, `${rebuild.stdout}\n${rebuild.stderr}`);
    assert.ok(
      fs.readFileSync(first).equals(fs.readFileSync(second)),
      'archive bytes must not depend on filesystem permission bits',
    );
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('archive writer fails closed when the mode manifest and the staged tree disagree', () => {
  const { fixture, staged } = stagedFixture({
    'bin/tool.mjs': { content: '#!/usr/bin/env node\n', mode: 0o755 },
    'docs/notes.txt': { content: 'notes\n', mode: 0o644 },
  });
  try {
    const archive = path.join(fixture, 'out.zip');
    const cases = [
      [null, /--mode-manifest/, 2],
      [{ 'bin/tool.mjs': '100755' }, /no recorded Git mode: docs\/notes\.txt/, 1],
      [{ 'bin/tool.mjs': '100755', 'docs/notes.txt': '100644', 'extra.txt': '100644' }, /not staged: extra\.txt/, 1],
      [{ 'bin/tool.mjs': '100777', 'docs/notes.txt': '100644' }, /unsupported Git mode "100777"/, 1],
    ];
    for (const [manifestContent, expected, status] of cases) {
      let manifest = null;
      if (manifestContent !== null) {
        manifest = path.join(fixture, 'modes.json');
        fs.writeFileSync(manifest, JSON.stringify(manifestContent));
      }
      const build = writeArchive(staged, archive, manifest);
      assert.equal(build.status, status, `${build.stdout}\n${build.stderr}`);
      assert.match(build.stderr, expected);
      assert.equal(fs.existsSync(archive), false, 'a rejected build must not publish an archive');
      assert.deepEqual(
        fs.readdirSync(fixture).filter((name) => name.endsWith('.tmp')),
        [],
        'a rejected build must not leave temporary files behind',
      );
    }
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});

test('archive build hands the recorded Git index modes from the stager to the writer', () => {
  const buildSource = fs.readFileSync(path.join(repoRoot, 'scripts', 'build-zip.sh'), 'utf8');
  assert.match(buildSource, /stage-clean-skill\.mjs[\s\S]*?--mode-manifest "\$stage\/modes\.json"/);
  assert.match(buildSource, /write-deterministic-zip\.mjs"[^\n]*\n\s*--mode-manifest "\$stage\/modes\.json"/);
});

test('CI tests the declared Node floor plus every maintained current lane', () => {
  const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'archify', 'package.json'), 'utf8'));
  assert.equal(packageJson.engines?.node, '>=18');

  const workflow = fs.readFileSync(path.join(repoRoot, '.github', 'workflows', 'ci.yml'), 'utf8');
  const testJob = workflowJob(workflow, 'test');
  const versions = testJob.match(/node-version:\s*\[([^\]]+)\]/)?.[1]
    .split(',')
    .map((version) => Number(version.trim()));
  assert.ok(versions, 'test job must declare an explicit Node version matrix');
  for (const version of [18, 20, 22, 24]) {
    assert.ok(versions.includes(version), `test matrix must cover Node ${version}`);
  }

  const packageSmokeJob = workflowJob(workflow, 'package-smoke');
  assert.match(packageSmokeJob, /os:\s*\[ubuntu-latest, macos-latest, windows-latest\]/);
  assert.match(packageSmokeJob, /node-version:\s*22/);
});
```

## test/render-output-checks.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-output-checks-'));
const checker = path.join(skillRoot, 'scripts/check-render-output.mjs');

function checkHtml(name, svgBody, profile = 'standard', viewBox = '0 0 240 160') {
  const htmlPath = path.join(tmp, `${name}.html`);
  fs.writeFileSync(htmlPath, `<!doctype html><html><body><svg viewBox="${viewBox}" data-quality-profile="${profile}">${svgBody}</svg></body></html>`);
  try {
    const stdout = execFileSync('node', [checker, htmlPath], { encoding: 'utf8' });
    return { code: 0, result: JSON.parse(stdout) };
  } catch (err) {
    return { code: err.status ?? 1, result: JSON.parse(String(err.stdout || '{}')) };
  }
}

test('render output check: showcase rejects node copy that becomes illegible at 1440px', () => {
  const { code, result } = checkHtml('showcase-desktop-readability', `
    <g data-node-id="tool-runtime">
      <rect x="1398" y="266" width="194" height="70" rx="6" class="c-mask"/>
      <text data-detail-anchor x="1495" y="299" class="t-primary" font-size="11">ToolRuntime</text>
      <text data-detail="context" x="1495" y="315" class="t-muted" font-size="8.1">permissions and recovery</text>
    </g>
  `, 'showcase', '0 0 1994 804');

  assert.notEqual(code, 0);
  const issue = result.composition.issues.find(
    (item) => item.code === 'composition/desktop-readability',
  );
  assert.equal(issue?.severity, 'error');
  assert.equal(issue?.viewportWidth, 1440);
  assert.ok(issue?.projectedFontPx < issue?.minimumProjectedFontPx);
});

test('render output check: compares exact projected size before rounding diagnostics', () => {
  const sourceFontPx = 8.1;
  const viewBoxWidth = 1260;
  assert.ok(sourceFontPx * 930 / viewBoxWidth < 6);

  const { code, result } = checkHtml('showcase-desktop-readability-borderline', `
    <g data-node-id="tool-runtime">
      <rect x="100" y="100" width="194" height="70" rx="6" class="c-mask"/>
      <text data-detail="context" x="197" y="140" class="t-muted" font-size="${sourceFontPx}">permissions and recovery</text>
    </g>
  `, 'showcase', `0 0 ${viewBoxWidth} 804`);

  assert.notEqual(code, 0);
  const issue = result.composition.issues.find(
    (item) => item.code === 'composition/desktop-readability',
  );
  assert.equal(issue?.severity, 'error');
  assert.ok(issue?.projectedFontPx < issue?.minimumProjectedFontPx);
});

test('render output check: includes primary node labels in desktop readability', () => {
  const { code, result } = checkHtml('showcase-primary-desktop-readability', `
    <g data-node-id="compact-node">
      <rect x="100" y="100" width="120" height="48" rx="6" class="c-mask"/>
      <text data-node-label x="160" y="126" class="t-primary" font-size="8">Compact node</text>
    </g>
  `, 'showcase', '0 0 1300 700');

  assert.notEqual(code, 0);
  const issue = result.composition.issues.find(
    (item) => item.code === 'composition/desktop-readability',
  );
  assert.equal(issue?.text, 'Compact node');
  assert.equal(issue?.detail, 'primary');
  assert.equal(issue?.availableDiagramWidth, 930);
  assert.ok(issue?.projectedFontPx < issue?.minimumProjectedFontPx);
});

test('render output check: includes semantic boundary labels in desktop readability', () => {
  const { code, result } = checkHtml('showcase-boundary-desktop-readability', `
    <g data-graph-role="structural-frame-label">
      <rect data-graph-role="structural-frame-label-mask" x="100" y="100" width="180" height="16" class="c-mask"/>
      <text x="104" y="113" class="t-cloud" font-size="8.4" data-boundary-label>Disaster recovery boundary</text>
    </g>
  `, 'showcase', '0 0 1376 728');

  assert.notEqual(code, 0);
  const issue = result.composition.issues.find(
    (item) => item.code === 'composition/desktop-readability',
  );
  assert.equal(issue?.text, 'Disaster recovery boundary');
  assert.equal(issue?.detail, 'boundary');
  assert.ok(issue?.projectedFontPx < issue?.minimumProjectedFontPx);
});

test('render output check: accepts orthogonal arrows away from legend', () => {
  const { code, result } = checkHtml('clean', `
    <path d="M 20 20 L 120 20 L 120 60" class="a-default" stroke-width="1.4" marker-end="url(#arrowhead)"/>
    <!-- Legend -->
    <text x="40" y="120" class="t-primary" font-size="10">Legend</text>
    <rect x="40" y="132" width="14" height="9" class="c-backend"/>
    <text x="60" y="140" class="t-muted" font-size="7">Backend</text>
  `);
  assert.equal(code, 0);
  assert.equal(result.ok, true);
});

test('render output check: rejects two-point diagonal arrows', () => {
  const { code, result } = checkHtml('diagonal', `
    <path d="M 20 20 L 120 80" class="a-default" stroke-width="1.4" marker-end="url(#arrowhead)"/>
    <!-- Legend -->
    <text x="40" y="120" class="t-primary" font-size="10">Legend</text>
  `);
  assert.notEqual(code, 0);
  const check = result.checks.find((item) => item.name === 'orthogonal_arrows');
  assert.equal(check.ok, false);
  assert.match(check.details[0], /path 1/);
});

test('render output check: rejects a diagonal segment inside a polyline', () => {
  const { code, result } = checkHtml('polyline-diagonal', `
    <path d="M 20 20 L 60 35 L 120 35" class="a-default" stroke-width="1.4" marker-end="url(#arrowhead)"/>
    <!-- Legend -->
    <text x="40" y="120" class="t-primary" font-size="10">Legend</text>
  `);
  assert.notEqual(code, 0);
  const check = result.checks.find((item) => item.name === 'orthogonal_arrows');
  assert.equal(check.ok, false);
  assert.match(check.details[0], /path 1/);
  assert.match(check.details[0], /segment 1/);
});

test('render output check: rejects arrows crossing legend text', () => {
  const { code, result } = checkHtml('legend-crossing', `
    <path d="M 20 112 L 180 112" class="a-dashed" stroke-width="1.4" marker-end="url(#arrowhead-dashed)"/>
    <!-- Legend -->
    <text x="40" y="120" class="t-primary" font-size="10">Legend</text>
    <rect x="40" y="132" width="14" height="9" class="c-backend"/>
    <text x="60" y="140" class="t-muted" font-size="7">Backend</text>
  `);
  assert.notEqual(code, 0);
  const check = result.checks.find((item) => item.name === 'legend_clearance');
  assert.equal(check.ok, false);
  assert.match(check.details[0], /Legend/);
});

test('render output check: ignores unmarked sequence lifelines near legend', () => {
  const { code, result } = checkHtml('lifeline-near-legend', `
    <path d="M 60 20 L 60 126" class="a-default" stroke-width="0.8" stroke-dasharray="3,7"/>
    <!-- Legend -->
    <text x="40" y="120" class="t-primary" font-size="10">Legend</text>
    <path d="M 120 136 L 154 136" class="a-default" stroke-width="1.4" stroke-dasharray="3,5" marker-end="url(#arrowhead)"/>
    <text x="163" y="139" class="t-muted" font-size="8">return</text>
  `);
  assert.equal(code, 0);
  assert.equal(result.ok, true);
});

test('render output check: standard records a proper X as a composition warning', () => {
  const { code, result } = checkHtml('standard-crossing', `
    <path data-edge-from="a" data-edge-to="b" d="M 20 60 L 200 60" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-from="c" data-edge-to="d" d="M 103 20 L 103 120" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
  `);
  assert.equal(code, 0);
  assert.equal(result.composition.profile, 'standard');
  assert.deepEqual(result.composition.summary, { errors: 0, warnings: 1 });
  assert.equal(result.composition.metrics.properCrossings, 1);
  assert.equal(result.composition.issues[0].code, 'composition/proper-crossing');
  assert.equal(result.composition.issues[0].severity, 'warning');
});

test('render output check: showcase rejects a proper X with semantic identities', () => {
  const { code, result } = checkHtml('showcase-crossing', `
    <path data-edge-id="left" data-edge-from="a" data-edge-to="b" d="M 20 60 L 200 60" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-id="right" data-edge-from="c" data-edge-to="d" d="M 100 20 L 100 120" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
  `, 'showcase');
  assert.notEqual(code, 0);
  const check = result.checks.find((item) => item.name === 'relationship_crossings');
  assert.equal(check.ok, false);
  assert.match(check.details[0], /\[composition\/proper-crossing\] showcase/);
  assert.match(check.details[0], /relationship id "left"/);
  assert.deepEqual(result.composition.summary, { errors: 1, warnings: 0 });
});

test('render output check: shared endpoints and endpoint touches pass showcase', () => {
  const { code, result } = checkHtml('showcase-exemptions', `
    <path data-edge-from="a" data-edge-to="b" d="M 20 60 L 200 60" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-from="a" data-edge-to="c" d="M 100 20 L 100 90" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
    <path data-edge-from="d" data-edge-to="e" d="M 20 100 L 100 100" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-from="f" data-edge-to="g" d="M 100 100 L 100 140" class="a-default" marker-end="url(#arrowhead)"/>
  `, 'showcase');
  assert.equal(code, 0);
  assert.equal(result.composition.metrics.properCrossings, 0);
  assert.equal(result.composition.metrics.ambiguousCorridors, 0);
});

test('render output check: relationship labels cannot hide another shared-source route', () => {
  for (const profile of ['standard', 'showcase']) {
    const { code, result } = checkHtml(`label-route-${profile}`, `
      <path data-edge-key="0" data-edge-id="approved" data-edge-from="dlq" data-edge-to="replay" data-composition-points="20,60;200,60" d="M 20 60 L 200 60" class="a-default" marker-end="url(#arrowhead)"/>
      <path data-edge-key="1" data-edge-id="sample" data-edge-from="dlq" data-edge-to="ops" data-composition-points="70,55;150,55" d="M 70 55 L 150 55" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
      <g data-detail="context" data-edge-key="0" data-edge-id="approved" data-edge-from="dlq" data-edge-to="replay" data-edge-label="approved replay">
        <rect x="80" y="48" width="60" height="14" rx="3" class="c-mask"/>
        <text x="110" y="58">approved replay</text>
      </g>
    `, profile);

    assert.equal(result.composition.metrics.labelRouteClearanceIssues, 1);
    assert.equal(result.composition.metrics.minLabelRouteClearance, 0);
    const issue = result.composition.issues.find((item) => item.code === 'composition/label-route-clearance');
    assert.deepEqual(issue.labelRelationship, { id: 'approved', from: 'dlq', to: 'replay', label: 'approved replay', collectionIndex: 0, artifactIndex: 1 });
    assert.deepEqual(issue.otherRelationship, { id: 'sample', from: 'dlq', to: 'ops', label: '', collectionIndex: 1, artifactIndex: 2 });
    assert.equal(issue.segmentIndex, 0);
    assert.deepEqual(issue.labelRect, { x: 80, y: 48, width: 60, height: 14 });
    assert.equal(issue.clearance, 0);
    assert.equal(issue.intersectionLength, 60);
    assert.equal(issue.threshold, profile === 'showcase' ? 4 : 2);
    const check = result.checks.find((item) => item.name === 'label_route_clearance');
    if (profile === 'standard') {
      assert.equal(code, 0);
      assert.equal(check.ok, true);
      assert.equal(issue.severity, 'warning');
      assert.deepEqual(result.composition.summary, { errors: 0, warnings: 1 });
    } else {
      assert.notEqual(code, 0);
      assert.equal(check.ok, false);
      assert.match(check.details[0], /approved.*sample/);
      assert.match(check.details[0], /labelAt.*labelDx.*labelDy.*labelSegment/);
      assert.equal(issue.severity, 'error');
      assert.deepEqual(result.composition.summary, { errors: 1, warnings: 0 });
    }
  }
});

test('render output check: repeated endpoint messages keep their own stable owner identity', () => {
  const { code, result } = checkHtml('sequence-repeated-endpoints', `
    <g data-edge-key="0" data-edge-from="client" data-edge-to="api" data-edge-label="first request">
      <path data-composition-edge-from="client" data-composition-edge-to="api" data-composition-points="20,20;200,20" d="M 20 20 L 200 20" class="a-default" marker-end="url(#arrowhead)"/>
      <g data-detail="context"><rect x="70" y="2" width="80" height="16" class="c-mask"/></g>
    </g>
    <g data-edge-key="1" data-edge-from="client" data-edge-to="api" data-edge-label="second request">
      <path data-composition-edge-from="client" data-composition-edge-to="api" data-composition-points="20,60;200,60" d="M 20 60 L 200 60" class="a-default" marker-end="url(#arrowhead)"/>
      <g data-detail="context"><rect x="70" y="72" width="80" height="16" class="c-mask"/></g>
    </g>
    <path data-edge-key="2" data-edge-from="worker" data-edge-to="store" data-composition-points="20,80;200,80" d="M 20 80 L 200 80" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
  `, 'showcase');
  assert.notEqual(code, 0);
  const issues = result.composition.issues.filter((item) => item.code === 'composition/label-route-clearance');
  assert.equal(issues.length, 1);
  assert.equal(issues[0].label, 'second request');
  assert.equal(issues[0].labelRelationship.collectionIndex, 1);
  assert.equal(issues[0].otherRelationship.collectionIndex, 2);
});

test('render output check: duplicate fragments of the owning relationship stay exempt', () => {
  const { code, result } = checkHtml('label-owner-fragments', `
    <path data-edge-key="0" data-edge-id="approved" data-edge-from="dlq" data-edge-to="replay" data-composition-points="20,60;200,60" d="M 20 60 L 200 60" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-key="0" data-edge-id="approved" data-edge-from="dlq" data-edge-to="replay" data-composition-points="20,60;200,60" d="M 20 60 L 200 60" class="a-default" marker-end="url(#arrowhead)"/>
    <g data-detail="context" data-edge-key="0" data-edge-id="approved" data-edge-from="dlq" data-edge-to="replay" data-edge-label="approved replay">
      <rect x="80" y="48" width="60" height="14" rx="3" class="c-mask"/>
      <text x="110" y="58">approved replay</text>
    </g>
  `, 'showcase');
  assert.equal(code, 0);
  assert.equal(result.composition.metrics.labelRouteClearanceIssues, 0);
  assert.equal(result.composition.metrics.minLabelRouteClearance, null);
});

test('render output check: duplicate fragments of another relationship count once', () => {
  const { result } = checkHtml('label-other-fragments', `
    <path data-edge-key="0" data-edge-id="approved" data-edge-from="dlq" data-edge-to="replay" data-composition-points="20,60;200,60" d="M 20 60 L 200 60" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-key="1" data-edge-id="sample" data-edge-from="dlq" data-edge-to="ops" data-composition-points="70,55;150,55" d="M 70 55 L 150 55" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
    <path data-edge-key="1" data-edge-id="sample" data-edge-from="dlq" data-edge-to="ops" data-composition-points="70,55;150,55" d="M 70 55 L 150 55" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
    <g data-detail="context" data-edge-key="0" data-edge-id="approved" data-edge-from="dlq" data-edge-to="replay" data-edge-label="approved replay">
      <rect x="80" y="48" width="60" height="14" rx="3" class="c-mask"/>
      <text x="110" y="58">approved replay</text>
    </g>
  `, 'showcase');
  assert.equal(result.composition.metrics.labelRouteClearanceIssues, 1);
});

test('render output check: label-route thresholds include exact 2px and 4px boundaries', () => {
  const body = (otherY) => `
    <path data-edge-key="0" data-edge-from="a" data-edge-to="b" data-composition-points="20,70;200,70" d="M 20 70 L 200 70" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-key="1" data-edge-from="c" data-edge-to="d" data-composition-points="70,${otherY};150,${otherY}" d="M 70 ${otherY} L 150 ${otherY}" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
    <g data-detail="context" data-edge-key="0" data-edge-from="a" data-edge-to="b" data-edge-label="handoff">
      <rect x="80" y="48" width="60" height="14" rx="3" class="c-mask"/>
      <text x="110" y="58">handoff</text>
    </g>
  `;
  const standardAtTwo = checkHtml('label-standard-two', body(64), 'standard');
  assert.equal(standardAtTwo.code, 0);
  assert.equal(standardAtTwo.result.composition.metrics.labelRouteClearanceIssues, 0);
  assert.equal(standardAtTwo.result.composition.metrics.minLabelRouteClearance, 2);

  const standardBelowTwo = checkHtml('label-standard-one-nine', body(63.9), 'standard');
  assert.equal(standardBelowTwo.code, 0);
  assert.equal(standardBelowTwo.result.composition.metrics.labelRouteClearanceIssues, 1);
  assert.equal(standardBelowTwo.result.composition.summary.warnings, 1);

  const showcaseAtTwo = checkHtml('label-showcase-two', body(64), 'showcase');
  assert.notEqual(showcaseAtTwo.code, 0);
  assert.equal(showcaseAtTwo.result.composition.metrics.labelRouteClearanceIssues, 1);
  assert.equal(showcaseAtTwo.result.composition.issues.find((item) => item.code === 'composition/label-route-clearance').threshold, 4);

  const showcaseAtFour = checkHtml('label-showcase-four', body(66), 'showcase');
  assert.equal(showcaseAtFour.code, 0);
  assert.equal(showcaseAtFour.result.composition.metrics.labelRouteClearanceIssues, 0);
  assert.equal(showcaseAtFour.result.composition.metrics.minLabelRouteClearance, 4);

  const showcaseBelowFour = checkHtml('label-showcase-three-nine', body(65.9), 'showcase');
  assert.notEqual(showcaseBelowFour.code, 0);
  assert.equal(showcaseBelowFour.result.composition.metrics.labelRouteClearanceIssues, 1);
  assert.equal(showcaseBelowFour.result.composition.summary.errors, 1);
});

test('render output check: unrelated shared corridors warn in standard and fail showcase', () => {
  for (const profile of ['standard', 'showcase']) {
    const { code, result } = checkHtml(`corridor-${profile}`, `
      <path data-edge-id="first" data-edge-from="a" data-edge-to="b" data-composition-points="20,60;140,60;140,100" d="M 20 60 L 140 60 L 140 100" class="a-default" marker-end="url(#arrowhead)"/>
      <path data-edge-id="second" data-edge-from="c" data-edge-to="d" data-composition-points="60,60;180,60;180,100" d="M 60 60 L 180 60 L 180 100" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
    `, profile);
    assert.equal(result.composition.metrics.ambiguousCorridors, 1);
    assert.equal(result.composition.issues[0].code, 'composition/ambiguous-corridor');
    assert.equal(result.composition.issues[0].overlapLength, 80);
    assert.deepEqual(result.composition.issues[0].from, [60, 60]);
    assert.deepEqual(result.composition.issues[0].to, [140, 60]);
    const check = result.checks.find((item) => item.name === 'relationship_corridors');
    if (profile === 'standard') {
      assert.equal(code, 0);
      assert.equal(check.ok, true);
      assert.deepEqual(result.composition.summary, { errors: 0, warnings: 1 });
      assert.equal(result.composition.issues[0].severity, 'warning');
    } else {
      assert.notEqual(code, 0);
      assert.equal(check.ok, false);
      assert.match(check.details[0], /\[composition\/ambiguous-corridor\] showcase/);
      assert.match(check.details[0], /relationship id "first".*relationship id "second"/);
      assert.deepEqual(result.composition.summary, { errors: 1, warnings: 0 });
      assert.equal(result.composition.issues[0].severity, 'error');
    }
  }
});

test('render output check: visible quadratic crossing is caught in showcase', () => {
  const { code, result } = checkHtml('showcase-quadratic-crossing', `
    <path data-edge-from="a" data-edge-to="b" d="M 20 90 Q 100 10 180 90" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-from="c" data-edge-to="d" d="M 103 20 L 103 120" class="a-dashed" marker-end="url(#arrowhead-dashed)"/>
  `, 'showcase');
  assert.notEqual(code, 0);
  assert.equal(result.composition.metrics.properCrossings, 1);
  assert.equal(result.composition.status, 'fail');
});

test('render output check: container border runs fail both profiles with frame identity', () => {
  for (const profile of ['standard', 'showcase']) {
    const { code, result } = checkHtml(`border-run-${profile}`, `
      <rect data-composition-frame-kind="stage" data-composition-frame-id="sources" x="40" y="40" width="160" height="80" rx="10"/>
      <path data-edge-id="events" data-edge-from="web" data-edge-to="edge" data-composition-points="60,40;150,40" d="M 60 40 L 150 40" class="a-default" marker-end="url(#arrowhead)"/>
    `, profile);
    assert.notEqual(code, 0);
    const check = result.checks.find((item) => item.name === 'container_border_runs');
    assert.equal(check.ok, false);
    assert.match(check.details[0], /\[composition\/container-border-run\].*relationship id "events"/);
    assert.match(check.details[0], /stage "sources" top border for 90px/);
    assert.equal(result.composition.summary.errors, 1);
    assert.equal(result.composition.metrics.containerBorderRuns, 1);
    assert.equal(result.composition.issues[0].code, 'composition/container-border-run');
  }
});

test('render output check: perpendicular crossings, rounded-corner touches, and tangent Q curves pass', () => {
  const { code, result } = checkHtml('border-run-exemptions', `
    <rect data-composition-frame-kind="group" data-composition-frame-id="safe" x="40" y="40" width="160" height="80" rx="10"/>
    <path data-edge-from="a" data-edge-to="b" d="M 100 10 L 100 80" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-from="c" data-edge-to="d" d="M 40 40 L 49 40" class="a-default" marker-end="url(#arrowhead)"/>
    <path data-edge-from="e" data-edge-to="f" d="M 20 70 Q 40 40 60 40" class="a-default" marker-end="url(#arrowhead)"/>
  `, 'showcase');
  assert.equal(code, 0);
  assert.equal(result.composition.metrics.containerBorderRuns, 0);
});

test('render output check: a fully collinear quadratic primitive is a border run', () => {
  const { code, result } = checkHtml('border-run-collinear-q', `
    <rect data-composition-frame-kind="segment" data-composition-frame-id="retry" x="40" y="40" width="160" height="80" rx="10"/>
    <path data-edge-from="a" data-edge-to="b" d="M 60 40 Q 100 40 140 40" class="a-default" marker-end="url(#arrowhead)"/>
  `);
  assert.notEqual(code, 0);
  assert.equal(result.composition.metrics.containerBorderRuns, 1);
});

test('render output check: composition receipt records neutral normalized route metrics', () => {
  const { code, result } = checkHtml('route-metrics', `
    <path data-edge-from="a" data-edge-to="b" data-composition-points="0,20;10,20;30,20;30,28;50,28;50,50" d="M 0 20 L 30 20 L 30 28 L 50 28 L 50 50" class="a-default" marker-end="url(#arrowhead)"/>
  `);
  assert.equal(code, 0);
  assert.equal(result.composition.metrics.maxBends, 3);
  assert.equal(result.composition.metrics.routesOverSuggestedBends, 1);
  assert.equal(result.composition.metrics.minSegmentPx, 8);
  assert.equal(result.composition.metrics.shortSegmentCount, 1);
  assert.equal(result.composition.metrics.shortInteriorSegmentCount, 1);
  assert.equal(result.composition.metrics.shortEndpointSegmentCount, 0);
  assert.equal(result.composition.metrics.microSegmentCount, 0);
  assert.deepEqual(result.composition.suggestedLimits, { bendsPerRelationship: 2, stretch: 1.35, segmentPx: 16, microSegmentPx: 8 });
});

test('render output check: endpoint stubs from 8px pass while cramped interior turns are profile-aware', () => {
  const clean = checkHtml('endpoint-stubs', `
    <path data-edge-id="lane-hop" data-edge-from="a" data-edge-to="b" data-composition-points="0,20;13,20;13,60;80,60;80,73" d="M 0 20 L 13 20 L 13 60 L 80 60 L 80 73" class="a-default" marker-end="url(#arrowhead)"/>
  `, 'showcase');
  assert.equal(clean.code, 0);
  assert.equal(clean.result.composition.metrics.shortEndpointSegmentCount, 2);
  assert.equal(clean.result.composition.metrics.shortInteriorSegmentCount, 0);

  const standard = checkHtml('short-turn-standard', `
    <path data-edge-id="tight" data-edge-from="a" data-edge-to="b" data-composition-points="0,20;24,20;24,29;80,29" d="M 0 20 L 24 20 L 24 29 L 80 29" class="a-default" marker-end="url(#arrowhead)"/>
  `, 'standard');
  assert.equal(standard.code, 0);
  assert.deepEqual(standard.result.composition.summary, { errors: 0, warnings: 1 });
  assert.equal(standard.result.composition.issues[0].code, 'composition/short-interior-segment');
  assert.equal(standard.result.checks.find((item) => item.name === 'route_rhythm').ok, true);

  const showcase = checkHtml('short-turn-showcase', `
    <path data-edge-id="tight" data-edge-from="a" data-edge-to="b" data-composition-points="0,20;24,20;24,29;80,29" d="M 0 20 L 24 20 L 24 29 L 80 29" class="a-default" marker-end="url(#arrowhead)"/>
  `, 'showcase');
  assert.notEqual(showcase.code, 0);
  assert.deepEqual(showcase.result.composition.summary, { errors: 1, warnings: 0 });
  const rhythm = showcase.result.checks.find((item) => item.name === 'route_rhythm');
  assert.equal(rhythm.ok, false);
  assert.match(rhythm.details[0], /\[composition\/short-interior-segment\] showcase relationship id "tight"/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/repair-receipt.test.mjs

```js
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const cli = path.join(skillRoot, 'bin/archify.mjs');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-repair-receipt-'));

function run(args) {
  return spawnSync(process.execPath, [cli, ...args], {
    cwd: skillRoot,
    encoding: 'utf8',
  });
}

function writeFixture(name, source) {
  const file = path.join(tmp, name);
  fs.writeFileSync(file, JSON.stringify(source, null, 2));
  return file;
}

function receipt(result) {
  assert.doesNotThrow(() => JSON.parse(result.stdout), result.stdout || result.stderr);
  return JSON.parse(result.stdout);
}

test('repair receipt: malformed JSON is one clean machine object without a Node stack', () => {
  const input = path.join(tmp, 'malformed.workflow.json');
  fs.writeFileSync(input, '{broken json');

  const result = run(['validate', 'workflow', input, '--json']);
  assert.equal(result.status, 1);
  assert.equal(result.stderr, '');
  const failure = receipt(result);
  assert.equal(failure.schemaVersion, 1);
  assert.equal(failure.ok, false);
  assert.equal(failure.command, 'validate');
  assert.equal(failure.stage, 'input');
  assert.equal(failure.diagnostics.length, 1);
  assert.deepEqual(failure.diagnostics[0].subject, { input });
  assert.equal(failure.diagnostics[0].code, 'input/json-parse');
  assert.equal(failure.diagnostics[0].severity, 'error');
  assert.match(failure.diagnostics[0].evidence.reason, /JSON/);
  assert.deepEqual(failure.diagnostics[0].supportedFixes, ['repair the JSON syntax and run validation again']);
  assert.doesNotMatch(result.stdout, /\n\s+at\s|file:\/\//);
});

test('repair receipt: all five modes identify schema subjects and supported fixes', () => {
  const cases = {
    architecture: ['web-app.architecture.json', 'components'],
    workflow: ['agent-tool-call.workflow.json', 'nodes'],
    sequence: ['cache-miss-request.sequence.json', 'participants'],
    dataflow: ['product-analytics.dataflow.json', 'nodes'],
    lifecycle: ['agent-run.lifecycle.json', 'states'],
  };

  for (const [type, [example, collection]] of Object.entries(cases)) {
    const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8'));
    source[collection][0].unexpected = true;
    const identity = source[collection][0].id;
    const input = writeFixture(`schema-${type}.json`, source);
    const result = run(['validate', type, input, '--json']);

    assert.equal(result.status, 1, `${type}: ${result.stderr || result.stdout}`);
    assert.equal(result.stderr, '', type);
    const failure = receipt(result);
    const repair = failure.diagnostics.find((entry) => entry.code === 'schema/additionalProperties');
    assert.ok(repair, type);
    assert.deepEqual(repair.subject, {
      diagramType: type,
      path: `/${collection}/0`,
      identity,
    });
    assert.equal(repair.evidence.additionalProperty, 'unexpected');
    assert.deepEqual(repair.supportedFixes, ['remove unsupported property "unexpected"']);
  }
});

test('repair receipt: human validation formats the same rule without a stack', () => {
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8'));
  source.nodes[0].unexpected = true;
  const input = writeFixture('human-schema.workflow.json', source);
  const result = run(['validate', 'workflow', input]);

  assert.equal(result.status, 1);
  assert.equal(result.stdout, '');
  assert.match(result.stderr, /\[schema\/additionalProperties\]/);
  assert.match(result.stderr, /Fix: remove unsupported property "unexpected"/);
  assert.doesNotMatch(result.stderr, /\n\s+at\s|file:\/\//);
});

test('repair receipt: validate and deliver share exact Clean Flow evidence while delivery preserves the trusted artifact', () => {
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  source.connections[0] = {
    ...source.connections[0],
    fromSide: 'right',
    toSide: 'left',
    via: [[100, 140], [220, 140]],
  };
  const input = writeFixture('blocked-route.architecture.json', source);
  const output = path.join(tmp, 'trusted.html');
  const trusted = '<!doctype html><title>trusted prior artifact</title>\n';
  fs.writeFileSync(output, trusted);

  const validated = run(['validate', 'architecture', input, '--quality', 'showcase', '--json']);
  const delivered = run(['deliver', 'architecture', input, output, '--quality', 'showcase', '--json']);
  assert.equal(validated.status, 1, validated.stderr || validated.stdout);
  assert.equal(delivered.status, 1, delivered.stderr || delivered.stdout);
  assert.equal(validated.stderr, '');
  assert.equal(delivered.stderr, '');

  const validateRepair = receipt(validated).diagnostics.find((entry) => entry.code === 'clean-flow/edge-through-node');
  const deliverRepair = receipt(delivered).diagnostics.find((entry) => entry.code === 'clean-flow/edge-through-node');
  assert.ok(validateRepair);
  assert.ok(deliverRepair);
  assert.deepEqual(deliverRepair, validateRepair);
  assert.equal(validateRepair.subject.id, 'users-to-cdn');
  assert.equal(validateRepair.evidence.obstacleId, 'auth');
  assert.equal(validateRepair.evidence.segmentIndex, 0);
  assert.equal(validateRepair.evidence.clearancePx, 2);
  assert.ok(validateRepair.supportedFixes.some((fix) => fix.includes('route/via')));
  assert.equal(fs.readFileSync(output, 'utf8'), trusted);
  assert.deepEqual(fs.readdirSync(tmp).filter((name) => name.startsWith('.archify-delivery-')), []);
});

test('repair receipt: repository evidence failures retain a stable rule and exact repair', () => {
  const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
  source.meta.repository = {
    url: 'https://github.com/example/repository',
    revision: '0123456789abcdef0123456789abcdef01234567',
  };
  source.components[0].sources = [{ path: 'src/index.js', line: 1 }];
  const input = writeFixture('evidence-root-required.architecture.json', source);
  const result = run(['validate', 'architecture', input, '--json']);

  assert.equal(result.status, 1);
  assert.equal(result.stderr, '');
  const repair = receipt(result).diagnostics[0];
  assert.equal(repair.code, 'repository-evidence/root-required');
  assert.deepEqual(repair.subject, { surface: 'repository-evidence', path: '/meta/repository' });
  assert.deepEqual(repair.supportedFixes, ['pass --repo-root with the matching local Git checkout']);
});

test('repair receipt: public validate reports borderline desktop readability with a supported fix', () => {
  const input = writeFixture('borderline-readability.architecture.json', {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: {
      title: 'Borderline desktop readability',
      quality_profile: 'showcase',
      viewBox: [1826, 804],
    },
    components: [{
      id: 'tool-runtime',
      type: 'security',
      label: 'ToolRuntime',
      sublabel: 'OpenAI · Anthropic · provider gateways',
      pos: [100, 100],
      size: [194, 70],
    }],
    connections: [],
  });
  const result = run(['validate', 'architecture', input, '--quality', 'showcase', '--json']);

  assert.equal(result.status, 1, result.stderr || result.stdout);
  assert.equal(result.stderr, '');
  const repair = receipt(result).diagnostics.find(
    (entry) => entry.code === 'composition/desktop-readability',
  );
  assert.ok(repair);
  assert.deepEqual(repair.subject, { check: 'composition' });
  assert.ok(repair.evidence.projectedFontPx < repair.evidence.minimumProjectedFontPx);
  assert.ok(repair.supportedFixes.some((fix) => fix.includes('reduce the viewBox width')));
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/repository-evidence.test.mjs

```js
import assert from 'node:assert/strict';
import { execFileSync, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { startPreview } from '../bin/preview.mjs';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const cli = path.join(skillRoot, 'bin', 'archify.mjs');

function git(repo, ...args) {
  return execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' }).trim();
}

function fixture() {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-evidence-repo-'));
  fs.mkdirSync(path.join(root, 'src'), { recursive: true });
  fs.writeFileSync(path.join(root, 'src', 'router.js'), 'export function route(input) {\n  return input.kind;\n}\n');
  fs.writeFileSync(path.join(root, 'src', 'store.js'), 'export const store = new Map();\n');
  git(root, 'init');
  git(root, 'config', 'user.name', 'Archify Tests');
  git(root, 'config', 'user.email', 'archify@example.test');
  git(root, 'remote', 'add', 'origin', 'git@github.com:example/evidence-repo.git');
  git(root, 'add', '.');
  git(root, 'commit', '-m', 'fixture');
  const revision = git(root, 'rev-parse', 'HEAD');

  const diagram = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', 'web-app.architecture.json'), 'utf8'));
  diagram.meta.repository = {
    url: 'https://github.com/example/evidence-repo',
    revision,
  };
  diagram.components[0].sources = [
    { path: 'src/router.js', line: 1, end_line: 3, label: 'Request router' },
    { path: 'src/store.js', line: 1 },
  ];
  const input = path.join(root, 'diagram.architecture.json');
  fs.writeFileSync(input, JSON.stringify(diagram, null, 2));
  return { root, revision, diagram, input };
}

function run(args) {
  return spawnSync(process.execPath, [cli, ...args], {
    cwd: skillRoot,
    encoding: 'utf8',
  });
}

function evidencePayload(html) {
  const match = html.match(/<script id="archify-source-evidence-data" type="application\/json">([\s\S]*?)<\/script>/);
  assert.ok(match, 'verified evidence payload missing');
  return JSON.parse(match[1]);
}

test('Gitee evidence generates provider-specific revision and line links', () => {
  const data = fixture();
  data.diagram.meta.repository.url = 'https://gitee.com/example/evidence-repo';
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  const output = path.join(data.root, 'gitee.html');
  for (const remote of [
    'https://gitee.com/example/evidence-repo.git/',
    'git@gitee.com:example/evidence-repo.git',
    'ssh://git@gitee.com/example/evidence-repo.git',
  ]) {
    git(data.root, 'remote', 'set-url', 'origin', remote);
    const result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
    assert.equal(result.status, 0, result.stderr || result.stdout);
    const evidence = evidencePayload(fs.readFileSync(output, 'utf8'));
    assert.equal(evidence.repository.href, `https://gitee.com/example/evidence-repo/tree/${data.revision}`);
    assert.equal(evidence.nodes.users[0].href, `https://gitee.com/example/evidence-repo/blob/${data.revision}/src/router.js#L1-3`);
    assert.equal(evidence.nodes.users[1].href, `https://gitee.com/example/evidence-repo/blob/${data.revision}/src/store.js#L1`);
  }
});

test('local-only evidence verifies HTTP self-hosted origins without generating links', () => {
  const data = fixture();
  data.diagram.meta.repository = {
    url: 'http://git.example.internal:3000/Platform/Services/evidence-repo',
    revision: data.revision,
    link_mode: 'local-only',
  };
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  git(data.root, 'remote', 'set-url', 'origin', data.diagram.meta.repository.url);
  // Evidence is read from the pinned commit, not from the current working file.
  fs.writeFileSync(path.join(data.root, 'src/router.js'), 'changed\n');
  const output = path.join(data.root, 'local.html');
  const result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.equal(JSON.parse(result.stdout).evidence.linkMode, 'local-only');
  const evidence = evidencePayload(fs.readFileSync(output, 'utf8'));
  assert.equal(evidence.verified, true);
  assert.equal(evidence.repository.href, undefined);
  assert.equal(evidence.repository.linkMode, 'local-only');
  assert.equal(evidence.nodes.users[0].endLine, 3);
  assert.ok(evidence.nodes.users.every((source) => !Object.hasOwn(source, 'href')));
});

test('local-only supports nested HTTPS and Git SSH identities with bounded port equivalence', () => {
  const data = fixture();
  const output = path.join(data.root, 'portable.html');
  for (const [url, remote] of [
    ['https://git.internal/Platform/Services/repo.git', 'https://git.internal:443/Platform/Services/repo.git'],
    ['ssh://git@git.internal/Platform/Services/repo', 'ssh://git@git.internal:22/Platform/Services/repo'],
    ['ssh://git@git.internal:2222/Platform/repo.git', 'ssh://git@git.internal:2222/Platform/repo.git'],
    ['git@git.internal:Platform/repo', 'git@git.internal:Platform/repo'],
    ['git@git.internal:/Platform/repo', 'ssh://git@git.internal/Platform/repo'],
    ['http://git.internal:3000/Platform/repo.git', 'http://user:SYNTHETIC_TOKEN@git.internal:3000/Platform/repo.git'],
    ['https://git.internal/Platform/repo.git', 'https://user:SYNTHETIC_TOKEN@git.internal/Platform/repo.git'],
    ['git@git.internal:Platform/repo%41', 'git@git.internal:Platform/repo%41'],
    ['ssh://git@git.internal/Platform/repo%41', 'ssh://git@git.internal/Platform/repoA'],
  ]) {
    data.diagram.meta.repository = { url, revision: data.revision, link_mode: 'local-only' };
    fs.writeFileSync(data.input, JSON.stringify(data.diagram));
    git(data.root, 'remote', 'set-url', 'origin', remote);
    const result = run(['render', 'architecture', data.input, output, '--repo-root', data.root]);
    assert.equal(result.status, 0, `${url}: ${result.stderr}`);
    const html = fs.readFileSync(output, 'utf8');
    assert.doesNotMatch(html, /SYNTHETIC_TOKEN/);
    assert.equal(evidencePayload(html).repository.href, undefined);
  }
});

for (const [name, url, origin] of [
  ['relative versus absolute SSH paths', 'ssh://git@git.internal/Team/repo', 'git@git.internal:Team/repo'],
  ['literal percent escapes in SCP paths', 'git@git.internal:Team/repoA', 'git@git.internal:Team/repo%41'],
]) {
  test(`local-only rejects ${name} before replacing a trusted artifact`, () => {
    const data = fixture();
    data.diagram.meta.repository = { url, revision: data.revision, link_mode: 'local-only' };
    fs.writeFileSync(data.input, JSON.stringify(data.diagram));
    git(data.root, 'remote', 'set-url', 'origin', origin);
    const output = path.join(data.root, 'trusted.html');
    fs.writeFileSync(output, 'trusted previous artifact');
    const result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
    assert.equal(result.status, 1, 'different Git paths must not verify as the declared repository');
    assert.ok(JSON.parse(result.stdout).diagnostics.some(({ code }) => code === 'repository-evidence/origin-mismatch'));
    assert.equal(fs.readFileSync(output, 'utf8'), 'trusted previous artifact');
  });
}

test('local-only rejects different hosts, paths, path case, endpoints and guessed prefixes', () => {
  const data = fixture();
  const output = path.join(data.root, 'trusted.html');
  fs.writeFileSync(output, 'trusted previous artifact');
  for (const [url, remote] of [
    ['https://git.internal/Team/repo', 'https://other.internal/Team/repo'],
    ['https://git.internal/Team/repo', 'https://git.internal/Team/other'],
    ['https://git.internal/Team/repo', 'https://git.internal/team/repo'],
    ['https://git.internal/Team/repo', 'http://git.internal/Team/repo'],
    ['https://git.internal/Team/repo', 'https://git.internal:8443/Team/repo'],
    ['ssh://git@git.internal:2222/Team/repo', 'ssh://git@git.internal:2223/Team/repo'],
    ['https://git.internal:2222/Team/repo', 'ssh://git@git.internal:2222/Team/repo'],
    ['https://git.internal/Team/repo', 'git@ssh-alias:Team/repo'],
    ['https://git.internal/Team/repo', 'https://git.internal/scm/Team/repo'],
    ['https://git.internal/Team/repo', 'https://git.internal/Team/ignored/../repo'],
    ['https://git.internal/Team/repo', 'git@git.internal:Team/repo'],
    ['https://git.internal/Team/repo', 'ssh://git@git.internal/Team/repo'],
    ['git@git.internal:Team/repo', 'git@git.internal:Team/repo.git'],
    ['http://git.internal/Team/repo', 'http://git.internal/Team/repo.git'],
  ]) {
    data.diagram.meta.repository = { url, revision: data.revision, link_mode: 'local-only' };
    fs.writeFileSync(data.input, JSON.stringify(data.diagram));
    git(data.root, 'remote', 'set-url', 'origin', remote);
    const result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
    assert.equal(result.status, 1, `${url} must differ from ${remote}`);
    assert.ok(JSON.parse(result.stdout).diagnostics.some(({ code }) => code === 'repository-evidence/origin-mismatch'));
    assert.equal(fs.readFileSync(output, 'utf8'), 'trusted previous artifact');
  }
});

test('local-only preserves root, origin, commit, blob, path and line checks', () => {
  const data = fixture();
  data.diagram.meta.repository.url = 'http://git.internal/team/repo';
  data.diagram.meta.repository.link_mode = 'local-only';
  git(data.root, 'remote', 'set-url', 'origin', 'http://git.internal/team/repo');
  const output = path.join(data.root, 'trusted.html');
  fs.writeFileSync(output, 'trusted previous artifact');
  const original = structuredClone(data.diagram);
  const cases = [
    ['repository-evidence/root-required', () => {}, []],
    ['repository-evidence/revision-unavailable', (diagram) => { diagram.meta.repository.revision = '0'.repeat(40); }],
    ['repository-evidence/file-missing', (diagram) => { diagram.components[0].sources = [{ path: 'src/missing.js' }]; }],
    ['repository-evidence/file-missing', (diagram) => { diagram.components[0].sources = [{ path: 'src' }]; }],
    ['repository-evidence/path-escape', (diagram) => { diagram.components[0].sources = [{ path: '../outside.js' }]; }],
    ['repository-evidence/path-escape', (diagram) => { diagram.components[0].sources = [{ path: '.git/config' }]; }],
    ['repository-evidence/line-out-of-range', (diagram) => { diagram.components[0].sources = [{ path: 'src/router.js', line: 4 }]; }],
    ['repository-evidence/line-range-invalid', (diagram) => { diagram.components[0].sources = [{ path: 'src/router.js', line: 3, end_line: 1 }]; }],
  ];
  for (const [expectedCode, change, roots = ['--repo-root', data.root]] of cases) {
    const diagram = structuredClone(original);
    change(diagram);
    fs.writeFileSync(data.input, JSON.stringify(diagram));
    const result = run(['deliver', 'architecture', data.input, output, ...roots, '--json']);
    assert.equal(result.status, 1);
    assert.ok(JSON.parse(result.stdout).diagnostics.some(({ code }) => code === expectedCode), result.stdout);
    assert.equal(fs.readFileSync(output, 'utf8'), 'trusted previous artifact');
  }
  fs.writeFileSync(data.input, JSON.stringify(original));
  git(data.root, 'remote', 'remove', 'origin');
  const result = run(['validate', 'architecture', data.input, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  assert.match(result.stdout, /must have an origin/);
});

test('unsupported web providers and invalid authored addresses fail without exposing credentials', () => {
  const data = fixture();
  for (const repository of [
    { url: 'https://git.internal/team/repo' },
    { url: 'https://gitee.com/team/repo', provider: 'github' },
    { url: 'https://git.internal/team/repo', provider: 'gitee' },
    { url: 'https://user:SYNTHETIC_TOKEN@gitee.com/team/repo' },
    { url: 'https://gitee.com/team/repo?token=SYNTHETIC_TOKEN' },
    { url: 'https://gitee.com/team/repo#SYNTHETIC_TOKEN' },
    { url: 'https://gitee.com/team/%2e%2e/repo' },
    { url: 'https://gitee.com/team%2Frepo' },
    { url: 'file:///tmp/repo', link_mode: 'local-only' },
    { url: 'javascript:alert(1)', link_mode: 'local-only' },
    { link_mode: 'local-only' },
  ]) {
    data.diagram.meta.repository = { revision: data.revision, ...repository };
    fs.writeFileSync(data.input, JSON.stringify(data.diagram));
    const result = run(['validate', 'architecture', data.input, '--repo-root', data.root, '--json']);
    assert.equal(result.status, 1, JSON.stringify(repository));
    assert.doesNotMatch(result.stdout + result.stderr, /SYNTHETIC_TOKEN/);
  }
});

test('portable origin failures redact HTTP credentials and query tokens', () => {
  const data = fixture();
  data.diagram.meta.repository = { url: 'http://git.internal/Team/repo', revision: data.revision, link_mode: 'local-only' };
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  for (const origin of [
    'http://user:SYNTHETIC_TOKEN@git.internal/Team/other',
    'http://git.internal/Team/repo?token=SYNTHETIC_TOKEN',
    'https://user:SYNTHETIC_TOKEN@git.internal/Team/repo',
  ]) {
    git(data.root, 'remote', 'set-url', 'origin', origin);
    const result = run(['validate', 'architecture', data.input, '--repo-root', data.root, '--json']);
    assert.equal(result.status, 1);
    assert.doesNotMatch(result.stdout + result.stderr, /SYNTHETIC_TOKEN/);
  }
});

test('explicit providers retain GitHub links and encode Gitee source paths', () => {
  const data = fixture();
  fs.writeFileSync(path.join(data.root, 'src', '中文 # router.js'), 'one\ntwo\n');
  git(data.root, 'add', 'src');
  git(data.root, 'commit', '-m', 'encoded source path');
  const revision = git(data.root, 'rev-parse', 'HEAD');
  data.diagram.components[0].sources = [{ path: 'src/中文 # router.js', line: 1, end_line: 2 }];
  for (const provider of ['github', 'gitee']) {
    data.diagram.meta.repository = { url: `https://${provider}.com/example/evidence-repo.git/`, revision, provider };
    git(data.root, 'remote', 'set-url', 'origin', `git@${provider}.com:example/evidence-repo.git`);
    fs.writeFileSync(data.input, JSON.stringify(data.diagram));
    const output = path.join(data.root, `${provider}.html`);
    const result = run(['render', 'architecture', data.input, output, '--repo-root', data.root]);
    assert.equal(result.status, 0, result.stderr);
    const evidence = evidencePayload(fs.readFileSync(output, 'utf8'));
    assert.equal(evidence.nodes.users[0].href, `https://${provider}.com/example/evidence-repo/blob/${revision}/src/${encodeURIComponent('中文 # router.js')}#L1-${provider === 'github' ? 'L' : ''}2`);
  }
});

test('local-only preview and compare publish only revision-verified evidence', { timeout: 20000 }, async () => {
  const data = fixture();
  data.diagram.meta.repository.url = 'http://git.internal/Team/repo';
  data.diagram.meta.repository.link_mode = 'local-only';
  git(data.root, 'remote', 'set-url', 'origin', 'http://git.internal/Team/repo');
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  const head = path.join(data.root, 'head.architecture.json');
  fs.writeFileSync(head, JSON.stringify(data.diagram));
  const compared = path.join(data.root, 'compared.html');
  const result = run(['compare', 'architecture', data.input, head, compared, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.equal(JSON.parse(result.stdout).proofLevel, 'revision-pinned');
  assert.match(fs.readFileSync(compared, 'utf8'), /local-only/);
  const preview = await startPreview({ type: 'architecture', input: data.input, output: path.join(data.root, 'preview-local.html'), repoRoot: data.root, open: false, debounceMs: 30, pollMs: 60 });
  try {
    const state = await waitForState(preview.url, (candidate) => candidate.status === 'verified');
    const before = await (await fetch(new URL('/artifact.html', preview.url))).text();
    assert.equal(evidencePayload(before).repository.href, undefined);
    data.diagram.components[0].sources[0].line = 999;
    delete data.diagram.components[0].sources[0].end_line;
    fs.writeFileSync(data.input, JSON.stringify(data.diagram));
    const failed = await waitForState(preview.url, (candidate) => candidate.status === 'needs-fix');
    assert.equal(failed.revision, state.revision);
    assert.equal(await (await fetch(new URL('/artifact.html', preview.url))).text(), before);
  } finally { await preview.stop(); }
});

test('browser renders local-only sources as searchable text and web sources as links', {
  skip: process.env.ARCHIFY_CHROME ? false : 'Set ARCHIFY_CHROME to run evidence browser checks.',
  timeout: 60000,
}, async () => {
  const data = fixture();
  const browser = new ChromeVisualBrowser(findChrome());
  try {
    for (const mode of ['github', 'gitee', 'local-only']) {
      const local = mode === 'local-only';
      const url = local ? 'http://git.internal/Team/repo' : `https://${mode}.com/example/evidence-repo`;
      data.diagram.meta.repository = { url, revision: data.revision, ...(local ? { link_mode: mode } : {}) };
      git(data.root, 'remote', 'set-url', 'origin', url);
      fs.writeFileSync(data.input, JSON.stringify(data.diagram));
      const artifactPath = path.join(data.root, `${mode}.html`);
      const result = run(['deliver', 'architecture', data.input, artifactPath, '--repo-root', data.root, '--json']);
      assert.equal(result.status, 0, result.stderr || result.stdout);
      for (const theme of ['light', 'dark']) {
        await browser.inspect({ artifactPath, width: 1440, height: 900, theme });
        const sessionId = await browser.sessionPromise;
        const response = await browser.cdp.send('Runtime.evaluate', {
          expression: `(() => {
            document.querySelector('[data-node-id="users"]').dispatchEvent(new MouseEvent('click', { bubbles: true }));
            const panel = document.getElementById('focus-evidence');
            const rows = [...panel.querySelectorAll('.semantic-passport-source')];
            const input = document.getElementById('node-finder-input');
            input.value = 'src/router.js';
            input.dispatchEvent(new Event('input', { bubbles: true }));
            return {
              visible: !panel.hidden,
              links: rows.filter(row => row.tagName === 'A').map(row => row.getAttribute('href')),
              paths: rows.map(row => row.querySelector('small').textContent),
              locations: rows.map(row => row.querySelector('code').textContent),
              repositoryHref: document.getElementById('focus-repository').getAttribute('href'),
              scope: panel.title,
              beacon: !!document.querySelector('[data-node-id="users"] [data-source-evidence-beacon]'),
              invalidLinks: [...panel.querySelectorAll('a[href]')].some(a => /undefined|javascript:/.test(a.getAttribute('href'))),
              search: document.getElementById('node-finder-results').textContent
            };
          })()`, returnByValue: true,
        }, sessionId);
        assert.equal(response.exceptionDetails, undefined);
        const observed = response.result.value;
        assert.equal(observed.visible, true);
        assert.equal(observed.beacon, true);
        assert.equal(observed.invalidLinks, false);
        assert.deepEqual(observed.paths, ['src/router.js', 'src/store.js']);
        assert.match(observed.scope, /local Git/);
        assert.match(observed.search, /Users/);
        assert.equal(observed.links.length, local ? 0 : 2);
        assert.equal(observed.locations[0], local ? 'L1–3' : 'L1–3 ↗');
        assert.equal(observed.repositoryHref, local ? null : `${url}/tree/${data.revision}`);
      }
    }
  } finally { await browser.close(); }
});

test('repository evidence accepts canonical HTTPS and common SSH remotes', () => {
  const data = fixture();
  const output = path.join(data.root, 'remote-form.html');
  for (const remote of [
    'https://github.com/example/evidence-repo.git/',
    'https://x-access-token:not-a-real-token@github.com/example/evidence-repo.git',
    'https://oauth2:not-a-real-token@github.com/example/evidence-repo',
    'git@github.com:example/evidence-repo.git',
    'ssh://git@github.com/example/evidence-repo.git',
  ]) {
    git(data.root, 'remote', 'set-url', 'origin', remote);
    const result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
    assert.equal(result.status, 0, `${remote}: ${result.stderr || result.stdout}`);
  }
});

async function waitForState(url, predicate, timeoutMs = 12000) {
  const started = Date.now();
  let latest;
  while (Date.now() - started < timeoutMs) {
    latest = await (await fetch(new URL('/state', url))).json();
    if (predicate(latest)) return latest;
    await new Promise((resolve) => setTimeout(resolve, 40));
  }
  assert.fail(`preview did not settle; latest state: ${JSON.stringify(latest)}`);
}

test('repository evidence is revision-verified, receipt-backed, searchable, and export-clean', () => {
  const data = fixture();
  const output = path.join(data.root, 'verified.html');
  const result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 0, result.stderr || result.stdout);

  const receipt = JSON.parse(result.stdout);
  assert.deepEqual(receipt.evidence, {
    verified: true,
    repository: 'https://github.com/example/evidence-repo',
    revision: data.revision,
    references: 2,
  });

  const html = fs.readFileSync(output, 'utf8');
  const evidence = evidencePayload(html);
  assert.equal(evidence.verified, true);
  assert.equal(evidence.repository.shortRevision, data.revision.slice(0, 7));
  assert.equal(evidence.nodes.users.length, 2);
  assert.equal(evidence.nodes.users[0].href, `https://github.com/example/evidence-repo/blob/${data.revision}/src/router.js#L1-L3`);
  assert.match(html, /Verified source/);
  assert.match(html, /Archify\.sourceEvidence = \(function \(\)/);
  assert.match(html, /var sourceSearch = sources\.map/);
  assert.match(html, /renderSourceEvidence\(id\)/);
  assert.match(html, /referrerPolicy = 'no-referrer'/);
  assert.match(html, /classList\.add\('source-evidence-beacon'\)/);
  assert.match(html, /text\.textContent = viewerText\('viewer\.passport\.sourceMarker'\) \+ ' ' \+ count/);
  assert.match(html, /Archify\.sourceEvidence\.installBeacons\(\)/);
  assert.match(html, /querySelectorAll\('\[data-source-evidence-beacon\]'\)/);
  assert.match(html, /data-source-evidence-original-label/);

  const svg = html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
  assert.doesNotMatch(svg, /src\/router\.js|github\.com\/example\/evidence-repo|source-evidence/);
});

test('repository evidence is opt-in and never appears in ordinary artifacts', () => {
  const output = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'archify-no-evidence-')), 'plain.html');
  const input = path.join(skillRoot, 'examples', 'web-app.architecture.json');
  const result = run(['render', 'architecture', input, output]);
  assert.equal(result.status, 0, result.stderr);
  const html = fs.readFileSync(output, 'utf8');
  assert.doesNotMatch(html, /id="archify-source-evidence-data"/);
  assert.match(html, /id="focus-evidence" hidden/);
  const svg = html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
  assert.doesNotMatch(svg, /source-evidence-beacon|data-source-evidence-count/);
});


test('origin-mismatch diagnostics redact HTTPS remote userinfo', () => {
  const data = fixture();
  const output = path.join(data.root, 'must-stay.html');
  fs.writeFileSync(output, 'trusted previous artifact');
  git(data.root, 'remote', 'set-url', 'origin', 'https://user:not-a-real-token@github.com/example/other-repo.git');
  const result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  const receipt = JSON.parse(result.stdout);
  const diagnostic = receipt.diagnostics.find((entry) => entry.code === 'repository-evidence/origin-mismatch');
  assert.ok(diagnostic, 'expected origin-mismatch diagnostic');
  assert.doesNotMatch(receipt.error, /not-a-real-token/);
  assert.doesNotMatch(JSON.stringify(receipt.diagnostics), /not-a-real-token/);
  assert.match(diagnostic.evidence.localOrigin, /^https:\/\/REDACTED@github\.com\/example\/other-repo\.git$/);
  assert.equal(fs.readFileSync(output, 'utf8'), 'trusted previous artifact');
});

test('origin-mismatch diagnostics redact HTTP remote userinfo', () => {
  const data = fixture();
  const output = path.join(data.root, 'must-stay.html');
  fs.writeFileSync(output, 'trusted previous artifact');
  git(data.root, 'remote', 'set-url', 'origin', 'http://user:FAKE_SECRET@github.com/example/other');
  const result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  const receipt = JSON.parse(result.stdout);
  const diagnostic = receipt.diagnostics.find((entry) => entry.code === 'repository-evidence/origin-mismatch');
  assert.ok(diagnostic, 'expected origin-mismatch diagnostic');
  assert.doesNotMatch(receipt.error, /FAKE_SECRET/);
  assert.doesNotMatch(JSON.stringify(receipt.diagnostics), /FAKE_SECRET/);
  assert.match(diagnostic.evidence.localOrigin, /^http:\/\/REDACTED@github\.com\/example\/other$/);
  assert.equal(fs.readFileSync(output, 'utf8'), 'trusted previous artifact');
});

test('validate accepts credentialed HTTPS remotes and redacts mismatch diagnostics', () => {
  const data = fixture();
  git(data.root, 'remote', 'set-url', 'origin', 'https://x-access-token:not-a-real-token@github.com/example/evidence-repo.git');
  let result = run(['validate', 'architecture', data.input, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.equal(JSON.parse(result.stdout).ok, true);

  git(data.root, 'remote', 'set-url', 'origin', 'https://user:VALIDATE_SECRET@github.com/example/other-repo.git');
  result = run(['validate', 'architecture', data.input, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  const receipt = JSON.parse(result.stdout);
  const diagnostic = receipt.diagnostics.find((entry) => entry.code === 'repository-evidence/origin-mismatch');
  assert.ok(diagnostic, 'expected origin-mismatch diagnostic');
  assert.doesNotMatch(receipt.error, /VALIDATE_SECRET/);
  assert.doesNotMatch(JSON.stringify(receipt.diagnostics), /VALIDATE_SECRET/);
  assert.doesNotMatch(result.stderr, /VALIDATE_SECRET/);
  assert.match(diagnostic.evidence.localOrigin, /^https:\/\/REDACTED@github\.com\/example\/other-repo\.git$/);
});

test('preview accepts credentialed HTTPS remotes and redacts mismatch diagnostics', { timeout: 20000 }, async () => {
  const data = fixture();
  const output = path.join(data.root, 'preview-credential.html');
  git(data.root, 'remote', 'set-url', 'origin', 'https://oauth2:not-a-real-token@github.com/example/evidence-repo');
  const matched = await startPreview({
    type: 'architecture',
    input: data.input,
    output,
    repoRoot: data.root,
    open: false,
    debounceMs: 30,
    pollMs: 60,
  });
  try {
    const state = await waitForState(matched.url, (candidate) => candidate.status === 'verified');
    assert.equal(state.revision, 1);
    const html = await (await fetch(new URL('/artifact.html', matched.url))).text();
    assert.equal(evidencePayload(html).repository.revision, data.revision);
  } finally {
    await matched.stop();
  }

  git(data.root, 'remote', 'set-url', 'origin', 'https://user:PREVIEW_SECRET@github.com/example/other-repo.git');
  const mismatched = await startPreview({
    type: 'architecture',
    input: data.input,
    output,
    repoRoot: data.root,
    open: false,
    debounceMs: 30,
    pollMs: 60,
  });
  try {
    const state = await waitForState(mismatched.url, (candidate) => candidate.status === 'needs-fix');
    assert.equal(state.failure?.stage, 'render');
    assert.match(state.failure?.message || '', /repository-evidence\/origin-mismatch/);
    assert.doesNotMatch(JSON.stringify(state), /PREVIEW_SECRET/);
  } finally {
    await mismatched.stop();
  }
});

test('evidence fails closed without a root, on wrong origin, missing blobs, or impossible lines', () => {
  const data = fixture();
  const output = path.join(data.root, 'must-stay.html');
  fs.writeFileSync(output, 'trusted previous artifact');

  let result = run(['deliver', 'architecture', data.input, output, '--json']);
  assert.equal(result.status, 1);
  assert.equal(JSON.parse(result.stdout).stage, 'render');
  assert.match(JSON.parse(result.stdout).error, /Pass --repo-root/);
  assert.equal(fs.readFileSync(output, 'utf8'), 'trusted previous artifact');

  git(data.root, 'remote', 'set-url', 'origin', 'https://github.com/example/other-repo.git');
  result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  assert.match(JSON.parse(result.stdout).error, /does not match/);
  git(data.root, 'remote', 'set-url', 'origin', 'git@github.com:example/evidence-repo.git');

  data.diagram.components[0].sources = [{ path: '../outside.js' }];
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  assert.match(JSON.parse(result.stdout).error, /must stay inside the repository/);

  data.diagram.components[0].sources = [{ path: 'src/router.js\n' }];
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  assert.match(JSON.parse(result.stdout).error, /repo-relative POSIX path/);

  data.diagram.components[0].sources = [{ path: 'src/missing.js' }];
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  assert.match(JSON.parse(result.stdout).error, /does not identify a file/);

  data.diagram.components[0].sources = [{ path: 'src/router.js', line: 99 }];
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  assert.match(JSON.parse(result.stdout).error, /requests line 99/);

  data.diagram.components[0].sources = [{ path: 'src/router.js', line: 4 }];
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  result = run(['deliver', 'architecture', data.input, output, '--repo-root', data.root, '--json']);
  assert.equal(result.status, 1);
  assert.match(JSON.parse(result.stdout).error, /has 3 lines/);
  assert.equal(fs.readFileSync(output, 'utf8'), 'trusted previous artifact');
});

test('--repo-root stays bounded to architecture and schema limits evidence shape', () => {
  const data = fixture();
  let result = run(['render', 'workflow', path.join(skillRoot, 'examples', 'agent-tool-call.workflow.json'), '--repo-root', data.root]);
  assert.equal(result.status, 2);
  assert.match(result.stderr, /architecture diagrams only/);

  data.diagram.components[0].sources = [
    { path: 'src/router.js' },
    { path: 'src/router.js' },
    { path: 'src/router.js' },
    { path: 'src/router.js' },
  ];
  fs.writeFileSync(data.input, JSON.stringify(data.diagram));
  result = run(['validate', 'architecture', data.input, '--repo-root', data.root]);
  assert.equal(result.status, 1);
  assert.match(result.stderr, /must NOT have more than 3 items/);
});

test('live preview forwards repo-root and publishes only verified evidence', { timeout: 20000 }, async () => {
  const data = fixture();
  const output = path.join(data.root, 'preview.html');
  const preview = await startPreview({
    type: 'architecture',
    input: data.input,
    output,
    repoRoot: data.root,
    open: false,
    debounceMs: 30,
    pollMs: 60,
  });
  try {
    const state = await waitForState(preview.url, (candidate) => candidate.status === 'verified');
    assert.equal(state.revision, 1);
    const html = await (await fetch(new URL('/artifact.html', preview.url))).text();
    assert.equal(evidencePayload(html).repository.revision, data.revision);
  } finally {
    await preview.stop();
  }
});
```

## test/repository-language-metadata.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');

function linguistGenerated(relativePath) {
  const output = execFileSync(
    'git',
    ['check-attr', 'linguist-generated', '--', relativePath],
    { cwd: repoRoot, encoding: 'utf8' },
  ).trim();
  return output.slice(output.lastIndexOf(':') + 1).trim();
}

test('repository language metadata separates generated artifacts from implementation source', () => {
  for (const generatedPath of [
    'archify/assets/template.html',
    'archify/examples/web-app-rendered.html',
    'examples/web-app.html',
    'docs/cases/mco-runtime.architecture.html',
    'docs/gallery.html',
    'docs/gallery/artifacts/web-app.architecture.html',
    'docs/guide.html',
    'docs/start.html',
    'experiments/mco-showcase/mco-runtime.html',
    'archify/renderers/shared/generated-brand-marks.mjs',
    'archify/renderers/shared/generated-validators.mjs',
  ]) {
    assert.equal(
      linguistGenerated(generatedPath),
      'true',
      `${generatedPath} must be excluded from GitHub language statistics`,
    );
  }

  for (const sourcePath of [
    'viewer/template.source.html',
    'viewer/reader-layout.js',
    'viewer/viewer-chrome-layout.js',
    'viewer/viewer-camera.js',
    'viewer/semantic-radar.js',
    'viewer/motion-governor.js',
    'viewer/node-finder.js',
    'viewer/intent-trace.js',
    'viewer/semantic-lens.js',
    'viewer/route-probe.js',
    'viewer/guided-views.js',
    'viewer/focus.js',
    'viewer/export.js',
    'viewer/export-cleanup.js',
    'scripts/generate-viewer.mjs',
    'scripts/gallery-template.html',
    'scripts/guide-template.html',
    'scripts/start-template.html',
    'docs/index.html',
    'archify/renderers/shared/geometry.mjs',
  ]) {
    assert.equal(
      linguistGenerated(sourcePath),
      'unspecified',
      `${sourcePath} must remain visible as implementation source`,
    );
  }
});
```

## test/route-journey.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-route-journey-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const input = path.join(skillRoot, 'examples', example);
  const output = path.join(tmp, `${mode}.html`);
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    input,
    output,
  ], { encoding: 'utf8' });
  return { result, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit native Route Journey controls outside canonical SVG', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const { result, html } = render(mode, example);
    assert.equal(result.status, 0, result.stderr);
    assert.match(html, /id="route-journey-controls" hidden role="group" aria-label="Route journey controls"/i, mode);
    assert.match(html, /id="route-journey-prev"[^>]+aria-label="Previous route position"/i, mode);
    assert.match(html, /id="route-journey-play"[^>]+aria-label="Play route journey"[^>]+aria-pressed="false"/i, mode);
    assert.match(html, /id="route-journey-next"[^>]+aria-label="Next route position"/i, mode);
    assert.match(html, /id="route-journey-overview"[^>]+aria-label="Show complete route overview"/i, mode);
    assert.match(html, /document\.createElement\(options\.interactive === true \? 'button' : 'span'\)/, mode);
    assert.doesNotMatch(canonicalSvg(html), /data-route-journey|route-journey-(?:flow|overlay)/, mode);
  }
});

test('a position owns its exact ordered incoming edge while the full route remains authored truth', () => {
  assert.match(template, /activeNodeIds = result\.nodes\.slice\(\)/);
  assert.match(template, /activeEdges = result\.edges\.slice\(\)/);
  assert.match(template, /activeEdges\.forEach\(function \(edge, step\) \{[\s\S]*?var destination = step \+ 1/);
  assert.match(template, /destination === journeyIndex \? 'current' : 'future'/);
  assert.match(template, /edge\.setAttribute\('data-route-journey-current', ''\)/);
  assert.match(template, /journeyIndex > 0\) renderJourneyPulse\(activeEdges\[journeyIndex - 1\]\)/);
  assert.match(template, /node\.setAttribute\('data-route-match', ''\)/);
  assert.match(template, /edge\.setAttribute\('data-route-match', ''\)/);
  assert.match(template, /svg\.setAttribute\('data-route-journey', \(journeyIndex \+ 1\) \+ '\/' \+ activeNodeIds\.length\)/);
  assert.doesNotMatch(template, /renderJourneyPulse\([\s\S]{0,120}querySelector\(.*data-edge-from/);
});

test('route chips provide one roving tab stop, native activation, and manual ownership', () => {
  assert.match(template, /item\.setAttribute\('data-route-journey-index', String\(index\)\)/);
  assert.match(template, /item\.setAttribute\('tabindex', index === 0 \? '0' : '-1'\)/);
  assert.match(template, /item\.setAttribute\('aria-label', viewerText\('viewer\.route\.position'/);
  assert.match(template, /path\.addEventListener\('focusin'[\s\S]*?pauseJourney\(\{ preserveElapsed: true \}\)/);
  assert.match(template, /path\.addEventListener\('keydown'[\s\S]*?event\.key === 'ArrowRight'/);
  assert.match(template, /else if \(event\.key === 'Home'\) next = 0/);
  assert.match(template, /else if \(event\.key === 'End'\) next = activeNodeIds\.length - 1/);
  assert.match(template, /event\.key === 'Enter' \|\| event\.key === ' '/);
  assert.match(template, /selectJourneyIndex\(Number\(button\.getAttribute\('data-route-journey-index'\)\)\)/);
  assert.match(template, /button\.setAttribute\('aria-current', 'step'\)/);
});

test('playback is explicit, finite, resumable, and never leaks position into the route URL', () => {
  assert.match(template, /var JOURNEY_DWELL_MS = 1100/);
  assert.match(template, /journeyGeneration \+= 1/);
  assert.match(template, /generation !== journeyGeneration \|\| !journeyPlaying/);
  assert.match(template, /JOURNEY_DWELL_MS - journeyElapsedMs/);
  assert.match(template, /preserveElapsed: options\.complete !== true/);
  assert.match(template, /journeyIndex >= activeNodeIds\.length - 1[\s\S]*?pauseJourney\(\{ complete: true/);
  assert.match(template, /applyJourneyState\(journeyIndex \+ 1[\s\S]*?journeyElapsedMs = 0;[\s\S]*?scheduleJourney\(\)/);
  assert.doesNotMatch(template, /journeyTimer\s*=\s*(?:window\.)?setInterval/);
  assert.match(template, /function playJourney\(\)[\s\S]*?if \(journeyIndex < 0\) applyJourneyState\(0/);
  assert.match(template, /function syncFromHash\(\)[\s\S]*?choose\(parts\[1\], \{ updateUrl: false \}\)/);
  assert.match(template, /'#route=' \+ encodeURIComponent\(startId\) \+ '~' \+ encodeURIComponent\(endId\)/);
  assert.doesNotMatch(template, /#route=[^'\n]*journey/);
});

test('motion, camera, layered Escape, mobile, print, and embed boundaries stay explicit', () => {
  assert.match(template, /Archify\.motionGovernor\.capable === true[\s\S]*?!Archify\.motionGovernor\.isPaused\(\)/);
  assert.match(template, /Archify\.motionGovernor\.claim\('route'/);
  assert.match(template, /reason: 'route-journey',[\s\S]*?maxScale: 1\.65,[\s\S]*?padding: 64,[\s\S]*?duration: 360/);
  assert.match(template, /Archify\.routeProbe\.pauseJourney\(\{ preserveElapsed: true, reason: reason \|\| 'manual' \}\)/);
  assert.match(template, /event\.target\.closest\('\.diagram-nav, \.focus-chip, \.node-finder, \.diagram-guide, \.overview-map, \.route-probe, \.semantic-lens'\)/);
  assert.match(template, /reason: 'guide'/);
  assert.match(template, /window\.addEventListener\('beforeprint'[\s\S]*?pauseJourney/);
  assert.match(template, /function escapeRoute\(options\)[\s\S]*?return 'paused'[\s\S]*?return 'overview'[\s\S]*?return 'cleared'/);
  assert.match(template, /Archify\.routeProbe\.escape\(\{ restoreFocus: true \}\)/);
  assert.match(template, /\.route-probe\[data-route-dock="top"\] \{\s*top: 1rem;\s*bottom: auto;/);
  assert.match(template, /function updateDocking\(\) \{\s*if \(panel\.hidden\)/);
  assert.doesNotMatch(template, /if \(panel\.hidden \|\| window\.innerWidth > 720\)/);
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*?\.route-probe-node \{ min-height: 2\.75rem !important; \}/);
  assert.match(template, /\.route-journey-controls button \{ min-height: 2\.75rem; \}/);
  assert.match(template, /@media print[\s\S]*?\.route-probe-overlay, \.route-journey-overlay \{ display: none !important; \}/);
  assert.match(template, /svg\[data-route-active\] \[data-node-id\],[\s\S]*?filter: none !important/);
  assert.match(template, /html\[data-embed="true"\][\s\S]*?\.route-probe/);
  assert.match(template, /html\[data-motion="still"\] \.route-journey-flow/);
  assert.match(template, /@media \(prefers-reduced-motion: reduce\)[\s\S]*?\.route-journey-overlay/);
});

test('standalone export strips every journey attribute and transient pulse', () => {
  const cleanup = template.match(/function cleanExportClone\(clone\) \{[\s\S]*?\n      \}/)?.[0] || '';
  assert.match(template, /var canonicalStateClean = cleanExportClone\(clone\);/);
  assert.match(cleanup, /clone\.removeAttribute\('data-route-journey'\)/);
  assert.match(cleanup, /clone\.querySelectorAll\('\[data-route-journey-overlay\]'\)/);
  assert.match(cleanup, /el\.removeAttribute\('data-route-journey-state'\)/);
  assert.match(cleanup, /el\.removeAttribute\('data-route-journey-current'\)/);
  assert.match(cleanup, /!clone\.hasAttribute\('data-route-journey'\)/);
  assert.match(cleanup, /return !clone[\s\S]*?\[data-route-journey-overlay\][\s\S]*?\[data-route-journey-current\]/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/route-probe-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';
import { createViewerClick } from './helpers/viewer-click.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;

test('Route Probe preserves directed paths, Journey and export contracts', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser Route Probe checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-route-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_ROUTE_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const records = [];
  t.after(() => {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n');
  });
  const cases = {
    architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    files[mode] = path.join(scratch, mode + '.html');
    execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      path.join(skillRoot, 'examples', example), files[mode]]);
  }
  const trace = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', cases.architecture), 'utf8'));
  trace.meta.animation = 'trace';
  const traceInput = path.join(scratch, 'trace.json');
  fs.writeFileSync(traceInput, JSON.stringify(trace)); files.trace = path.join(scratch, 'trace.html');
  execFileSync(process.execPath, [path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'), traceInput, files.trace]);
  const browser = new ChromeVisualBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  await send('Emulation.setFocusEmulationEnabled', { enabled: true });
  async function run(expression) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  const click = await createViewerClick({ send, run, timeout: 12000 });
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.routeErrors=[];window.routeEnds=[];addEventListener('animationend',e=>{if(e.target.matches('.route-journey-flow'))routeEnds.push({name:e.animationName,trusted:e.isTrusted});},true);addEventListener('error',e=>routeErrors.push(e.message));
    addEventListener('unhandledrejection',e=>routeErrors.push(String(e.reason)));
    try {localStorage.removeItem('archify-motion');} catch (_) {}
    window.routeWait=predicate=>new Promise((resolve,reject)=>{
      const start=performance.now();function sample(){if(predicate())return resolve();
      if(performance.now()-start>12000)return reject(new Error('Route observation timed out'));requestAnimationFrame(sample);}requestAnimationFrame(sample);
    });
  ` });
  async function load(mode = 'architecture', { theme = 'dark', reduced = false, suffix = '' } = {}) {
    await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 0, y: 0 });
    await send('Emulation.setDeviceMetricsOverride', { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
    await send('Emulation.setEmulatedMedia', { media: '', features: [{ name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' }] });
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    await send('Page.navigate', { url: pathToFileURL(files[mode]).href + `?theme=${theme}` + suffix });
    await loaded; await run('document.fonts.ready'); await run('Archify.viewerChromeLayout.whenStable()');
  }
  async function key(key, code, windowsVirtualKeyCode) {
    await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode, text: key === 'Enter' ? '\r' : key === ' ' ? ' ' : undefined });
    await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode });
  }
  const node = id => `.diagram-container > svg [data-node-id="${id}"]`;
  async function snapshot(scenario) {
    const state = await run(`(()=>{
      const svg=document.querySelector('.diagram-container > svg'),p=Archify.routeProbe,panel=document.getElementById('route-probe');
      return {active:p.active(),result:p.result(),panel:panel.dataset.state,hidden:panel.hidden,hash:location.hash,
        nodes:[...svg.querySelectorAll('[data-node-id][data-route-match]')].map(n=>n.dataset.nodeId),
        candidates:[...svg.querySelectorAll('[data-route-candidate]')].map(n=>n.dataset.nodeId),
        edges:[...svg.querySelectorAll('[data-edge-from][data-route-match]')].map(n=>({key:n.dataset.edgeKey||null,from:n.dataset.edgeFrom,to:n.dataset.edgeTo,step:n.dataset.routeStep})),
        journey:[...svg.querySelectorAll('[data-node-id][data-route-journey-state]')].map(n=>({id:n.dataset.nodeId,state:n.dataset.routeJourneyState})),
        currentEdges:[...svg.querySelectorAll('[data-edge-from][data-route-journey-current]')].map(n=>n.dataset.edgeKey||null),
        overlays:svg.querySelectorAll('[data-route-probe-overlay]').length,pulses:svg.querySelectorAll('[data-route-journey-overlay]').length,
        chips:[...panel.querySelectorAll('[data-route-journey-index]')].map(n=>({id:n.dataset.routeNodeId,tab:n.tabIndex,current:n.getAttribute('aria-current')})),
        controls:[...document.querySelectorAll('#route-journey-controls button')].map(n=>({id:n.id,disabled:n.disabled,pressed:n.getAttribute('aria-pressed')})),
        status:document.getElementById('route-probe-status').textContent,focus:Archify.focus.active(),owner:Archify.motionGovernor.owner(),
        errors:routeErrors,external:performance.getEntriesByType('resource').map(e=>e.name).filter(n=>/^https?:/.test(n))};
    })()`);
    assert.deepEqual(state.errors, [], scenario); assert.deepEqual(state.external, [], scenario);
    records.push({ scenario, ...state }); return state;
  }
  async function route(source = 'users', target = 'db') {
    assert.equal(await run(`Archify.routeProbe.begin({source:${JSON.stringify(source)}});Archify.routeProbe.choose(${JSON.stringify(target)})`), true);
  }
  async function hash(value) {
    await run(`new Promise(resolve=>{addEventListener('hashchange',()=>requestAnimationFrame(resolve),{once:true});location.hash=${JSON.stringify(value)};})`);
  }
  // Synchronous clock fixture isolates callbacks, including cancelled callbacks.
  // Real playback and animation completion are exercised separately below.
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.routeClock=body=>{
      const schedule=window.setTimeout,cancel=window.clearTimeout,clock=Date.now;let now=1000,serial=0;const jobs=[];
      window.setTimeout=(fn,delay)=>{const job={id:++serial,fn,delay,cancelled:false};jobs.push(job);return job.id;};
      window.clearTimeout=id=>{const job=jobs.find(j=>j.id===id);if(job)job.cancelled=true;};Date.now=()=>now;
      try{return body({jobs,advance:ms=>now+=ms,last:delay=>jobs.filter(j=>j.delay===delay).at(-1),fire:job=>job.fn()});}
      finally{window.setTimeout=schedule;window.clearTimeout=cancel;Date.now=clock;}
    };
  ` });

  await t.test('five modes initialize and trusted endpoint input preserves capture and error recovery', async () => {
    for (const mode of Object.keys(cases)) {
      await load(mode); const s = await snapshot(mode + '-initial'); assert.equal(s.active, null); assert.equal(s.hidden, true); assert.equal(s.result, null);
      assert.deepEqual(await run('Object.keys(Archify.routeProbe).sort()'), ['active', 'begin', 'choose', 'clear', 'copyLink', 'escape', 'exportSnapshot', 'finderClosed', 'finderContext', 'finderOpening', 'isJourneyPlaying', 'openFinder', 'pauseJourney', 'playJourney', 'result', 'selectJourneyIndex', 'showOverview', 'syncMotion', 'toggle']);
    }
    await load(); await click('#btn-route-probe'); assert.equal((await snapshot('source')).active, 'source');
    await click(node('users')); let s = await snapshot('target'); assert.equal(s.active, 'target'); assert.equal(s.focus, null);
    assert.deepEqual(s.candidates, ['cdn', 'lb', 'api', 'cache', 'db', 's3', 'queue', 'worker']);
    assert.equal(await run(`Archify.routeProbe.choose('unknown')`), false);
    await click(node('users')); s = await snapshot('same-node'); assert.equal(s.active, 'target'); assert.equal(s.panel, 'error');
    await run(`document.querySelector(${JSON.stringify(node('auth'))}).focus()`); await key('Enter', 'Enter', 13);
    s = await snapshot('unreachable'); assert.equal(s.active, 'target'); assert.equal(s.panel, 'error'); assert.equal(s.focus, null);
    await run(`document.querySelector(${JSON.stringify(node('db'))}).focus()`); await key(' ', 'Space', 32);
    s = await snapshot('result'); assert.deepEqual(s.result.nodes, ['users', 'cdn', 'lb', 'api', 'db']); assert.equal(s.result.hops, 4);
    assert.equal(s.result.journey, -1); assert.equal(s.result.playing, false); assert.equal(s.focus, null);
    assert.deepEqual(s.edges.map(e => [e.from, e.to]), [['users', 'cdn'], ['cdn', 'lb'], ['lb', 'api'], ['api', 'db']]);
    assert.equal(await run(`Archify.routeProbe.choose('cache')`), false);
    assert.equal(await run(`(()=>{const r=Archify.routeProbe.result();r.nodes.length=0;return Archify.routeProbe.result().nodes.length;})()`), 5);
    await load(); await run(`Archify.focus.set('api',{toggle:false});Archify.routeProbe.begin()`);
    assert.equal((await snapshot('focus-seeded')).active, 'target');
    await load(); await run(`Archify.focus.setMany(['api','db']);Archify.routeProbe.begin({focusNode:true})`);
    assert.equal((await snapshot('multi-focus-source')).active, 'source');
    assert.equal(await run('document.activeElement.dataset.nodeId'), 'users');
    const filtered = await run(`(()=>{const p=Archify.routeProbe,svg=document.querySelector('.diagram-container > svg'),container=svg.parentElement,n=svg.querySelector('[data-node-id="users"]');
      const event=key=>new KeyboardEvent('keydown',{key,bubbles:true,cancelable:true});const unrelated=event('ArrowRight');n.dispatchEvent(unrelated);container.setAttribute('data-just-panned','true');const panned=event('Enter');let capture;svg.addEventListener('keydown',()=>{capture={mode:p.active(),prevented:panned.defaultPrevented};},{capture:true,once:true});n.dispatchEvent(panned);container.removeAttribute('data-just-panned');return {capture,mode:p.active(),unrelated:unrelated.defaultPrevented,panned:panned.defaultPrevented,focus:Archify.focus.active()};})()`);
    // Route lets the panned key through; the existing Focus handler then consumes it.
    assert.deepEqual(filtered, { capture: { mode: 'source', prevented: false }, mode: null, unrelated: false, panned: true, focus: 'users' });
    await load('architecture', { suffix: '&embed=1#route=users~db' });
    assert.equal(await run('Archify.routeProbe.begin()'), false); assert.equal((await snapshot('embed-hash')).active, null);
  });

  await t.test('SVG graph fixtures preserve directed BFS order, parallel edges and strict export snapshots', async () => {
    await load('trace');
    // Isolate noncanonical graph inputs; original renderers remain unchanged.
    await run(`window.routeGraph=()=>{
      Archify.routeProbe.clear({preserveView:true});const svg=document.querySelector('.diagram-container > svg');
      svg.innerHTML='<g data-edge-from="a" data-edge-to="b" data-edge-key="ab" data-edge-id="first" transform="translate(3 4)"><path id="author-path" d="M0 0 L10 10" class="author" style="opacity:.7" marker-end="url(#arrow)"/><line x1="0" y1="0" x2="5" y2="5"/></g><path data-edge-from="a" data-edge-to="b" data-edge-key="parallel" d="M2 2 L20 20"/><path data-edge-from="a" data-edge-to="c" data-edge-key="ac" d="M0 0 L10 10"/><polyline data-edge-from="b" data-edge-to="d" data-edge-key="bd" points="0,0 10,10"/><path data-edge-from="c" data-edge-to="d" data-edge-key="cd" d="M0 0 L10 10"/><path data-edge-from="b" data-edge-to="a" data-edge-key="cycle" d="M0 0 L10 10"/><path data-edge-from="a" data-edge-to="a" data-edge-key="loop" d="M0 0 L10 10"/><path data-edge-from="a" data-edge-to="missing" data-edge-key="dangling" d="M0 0 L10 10"/>'+['a','b','c','d','solo'].map((id,i)=>'<g data-node-id="'+id+'" data-node-label="'+id.toUpperCase()+'" data-node-kind="backend" transform="translate('+i*100+' 100)"><rect width="60" height="40"/></g>').join('');
    }`);
    await run('routeGraph()'); await route('a', 'd');
    let s = await snapshot('graph-order'); assert.deepEqual(s.result.nodes, ['a', 'b', 'd']); assert.deepEqual(s.edges.map(e => e.key), ['ab', 'bd']);
    const shape = await run(`(()=>{const overlay=document.querySelector('[data-route-probe-overlay]'),shapes=[...overlay.querySelectorAll('.route-probe-flow')];return {count:shapes.length,transform:overlay.firstElementChild.getAttribute('transform'),d:shapes[0].getAttribute('d'),points:shapes[2].getAttribute('points'),steps:shapes.map(n=>n.style.getPropertyValue('--route-step')),normalized:shapes.every(n=>n.getAttribute('pathLength')==='1'),stripped:!overlay.querySelector('[id],[marker-end],[data-edge-key]'),original:document.getElementById('author-path').getAttribute('d'),beforeNode:overlay.nextElementSibling.hasAttribute('data-node-id')};})()`);
    assert.deepEqual(shape, { count: 3, transform: 'translate(3 4)', d: 'M0 0 L10 10', points: '0,0 10,10', steps: ['0', '0', '1'], normalized: true, stripped: true, original: 'M0 0 L10 10', beforeNode: true });
    const valid = await run(`(()=>{const p=Archify.routeProbe,s=p.exportSnapshot();s.nodeIds.length=0;s.edges[0].key='mutated';s.source.label='mutated';return p.exportSnapshot();})()`);
    assert.deepEqual(valid.nodeIds, ['a', 'b', 'd']); assert.equal(valid.edges[0].key, 'ab'); assert.equal(valid.source.label, 'A'); records.push({ scenario: 'geometry', shape, snapshot: valid });
    await run(`(()=>{routeGraph();const svg=document.querySelector('.diagram-container > svg');svg.insertBefore(svg.querySelector('[data-edge-key="ac"]'),svg.firstChild);})()`); await route('a', 'd');
    assert.deepEqual((await snapshot('reordered-tie')).result.nodes, ['a', 'c', 'd']);
    await run(`Archify.routeProbe.begin({source:'d'})`); assert.equal(await run(`Archify.routeProbe.choose('a')`), false);
    s = await snapshot('directed-unreachable'); assert.deepEqual(s.candidates, []); assert.equal(s.active, 'target');
    for (const [name, mutation] of [
      ['duplicate-node', `svg.appendChild(svg.querySelector('[data-node-id="a"]').cloneNode(true))`],
      ['detached-edge', `edge.remove()`], ['missing-key', `edge.removeAttribute('data-edge-key')`],
      ['duplicate-key', `svg.querySelector('[data-edge-key="bd"]').setAttribute('data-edge-key','ab')`],
      ['inconsistent-fragment', `const n=document.createElementNS(svg.namespaceURI,'text');n.setAttribute('data-edge-key','ab');n.setAttribute('data-edge-from','a');n.setAttribute('data-edge-to','c');svg.appendChild(n)`],
      ['multiple-drawable', `svg.appendChild(edge.cloneNode(true))`],
      ['zero-drawable', `edge.querySelectorAll('path,line').forEach(n=>n.remove())`],
      ['different-drawable', `const n=edge.cloneNode(true);edge.querySelectorAll('path,line').forEach(n=>n.remove());svg.appendChild(n)`],
    ]) {
      await run('routeGraph()'); await route('a', 'd');
      const rejected = await run(`(()=>{const svg=document.querySelector('.diagram-container > svg'),edge=svg.querySelector('[data-edge-key="ab"]');${mutation};return {result:Archify.routeProbe.result()!==null,snapshot:Archify.routeProbe.exportSnapshot()};})()`);
      assert.deepEqual(rejected, { result: true, snapshot: null }, name); records.push({ scenario: 'snapshot-' + name, ...rejected });
    }
    await run(`(()=>{routeGraph();const svg=document.querySelector('.diagram-container > svg');svg.querySelectorAll('[data-edge-from]').forEach(e=>{e.querySelectorAll('path,line,polyline').forEach(n=>n.remove());if(e.matches('path,line,polyline'))e.remove();});})()`);
    await route('a', 'b'); assert.equal(await run(`document.querySelector('[data-route-probe-overlay]').children.length`), 0);
    await run('Archify.routeProbe.selectJourneyIndex(1)'); assert.equal((await snapshot('no-geometry')).pulses, 0);
    await run(`Archify.routeProbe.clear();document.querySelector('.diagram-container > svg').innerHTML='';Archify.routeProbe.begin({focusNode:true})`);
    assert.deepEqual(await run('Archify.routeProbe.finderContext().allowedIds'), []); assert.equal(await run(`Archify.routeProbe.choose('missing')`), false); await snapshot('empty-graph');
  });

  await t.test('native Journey controls, finite playback, pause and layered Escape preserve path state', async () => {
    await load('trace'); await route();
    await run(`document.querySelector('[data-route-journey-index="0"]').focus()`); await key('ArrowLeft', 'ArrowLeft', 37);
    assert.equal(await run('document.activeElement.dataset.routeJourneyIndex'), '0');
    await key('End', 'End', 35); await key('ArrowRight', 'ArrowRight', 39); assert.equal(await run('document.activeElement.dataset.routeJourneyIndex'), '4');
    await key('Home', 'Home', 36); await key('ArrowRight', 'ArrowRight', 39); await key('Enter', 'Enter', 13);
    let s = await snapshot('journey-position'); assert.equal(s.result.journey, 1); assert.deepEqual(s.currentEdges, [s.edges[0].key]);
    assert.deepEqual(s.journey.map(n=>n.state), ['past','current','future','future','future']); assert.equal(s.chips.filter(n=>n.tab===0).length, 1);
    await run(`routeWait(()=>!document.querySelector('.diagram-container').hasAttribute('data-camera-transaction'))`);
    await click('#route-journey-next'); assert.equal((await snapshot('journey-next')).result.journey, 2);
    await click('#route-journey-prev'); assert.equal((await snapshot('journey-prev')).result.journey, 1);
    await click('#route-journey-overview'); assert.equal((await snapshot('overview')).result.journey, -1);
    await click('#route-journey-play'); assert.equal(await run('Archify.routeProbe.isJourneyPlaying()'), true);
    await run(`routeWait(()=>Archify.routeProbe.result().journey>=1)`); await snapshot('real-play-step');
    await key('Escape', 'Escape', 27); s = await snapshot('escape-paused'); assert.equal(s.result.playing, false); assert.ok(s.result.journey >= 1);
    await key('Escape', 'Escape', 27); assert.equal((await snapshot('escape-overview')).result.journey, -1);
    await key('Escape', 'Escape', 27); assert.equal((await snapshot('escape-cleared')).active, null);
    await route('api', 'db'); await click('#route-journey-play');
    assert.equal(await run('Archify.routeProbe.isJourneyPlaying()'), true, 'native Play click starts the journey');
    await run(`routeWait(()=>!Archify.routeProbe.isJourneyPlaying())`);
    s = await snapshot('natural-completion'); assert.equal(s.result.journey, 1); assert.equal(s.hash, '#route=api~db');
    assert.match(await run(`document.getElementById('route-journey-play').getAttribute('aria-label')`), /Replay/i);
    await click('#route-journey-play'); s = await snapshot('replay'); assert.equal(s.result.journey, 0); assert.equal(s.result.playing, true);
    await run(`document.querySelector('[data-route-journey-index="1"]').focus()`); assert.equal(await run('Archify.routeProbe.isJourneyPlaying()'), false);
    await key(' ', 'Space', 32); assert.equal((await snapshot('native-space-position')).result.journey, 1);
  });

  await t.test('controlled clocks preserve elapsed dwell, fresh steps, stale generations and pulse cleanup', async () => {
    await load('trace'); await route();
    const timing = await run(`routeClock(({jobs,last,advance,fire})=>{
      const p=Archify.routeProbe;const started=p.playJourney(),first=last(1100);advance(400);p.pauseJourney();const paused=p.result();p.playJourney();const resumed=jobs.at(-1).delay;fire(jobs.at(-1));const advanced=p.result();const fresh=jobs.at(-1).delay;
      p.clear({preserveView:true});const afterClear=p.result();fire(first);const stale=p.result();
      p.begin({source:'api'});p.choose('db');p.playJourney();const replaced=last(1100);p.begin({source:'users'});p.choose('cache');const replacement=p.result();fire(replaced);const afterReplacement=p.result();p.clear({preserveView:true});
      const cancelled=['overview','manual'].map(action=>{p.begin({source:'users'});p.choose('db');p.playJourney();const job=last(1100);if(action==='overview')p.showOverview({reveal:false});else p.selectJourneyIndex(2);const before=p.result();fire(job);const after=p.result();p.clear({preserveView:true});return {action,before,after};});
      return {cancelled,started,paused,advanced,resumed,fresh,afterClear,stale,replacement,afterReplacement};
    })`);
    assert.equal(timing.started, true); assert.equal(timing.paused.playing, false); assert.equal(timing.paused.journey, 0);
    assert.equal(timing.resumed, 700); assert.equal(timing.advanced.journey, 1); assert.equal(timing.fresh, 1100);
    for (const row of timing.cancelled) assert.deepEqual(row.after, row.before, row.action);
    assert.equal(timing.afterClear, null); assert.equal(timing.stale, null); assert.deepEqual(timing.afterReplacement, timing.replacement); records.push({ scenario: 'clock-fixture', ...timing });
    await route();
    const pulses = await run(`routeClock(({last,fire})=>{
      const p=Archify.routeProbe;p.selectJourneyIndex(1);const old=document.querySelector('[data-route-journey-overlay]'),fallback=last(860);p.selectJourneyIndex(2);const next=document.querySelector('[data-route-journey-overlay]');fire(fallback);const retained=next.isConnected;p.clear({preserveView:true});fire(last(860));return {oldGone:!old.isConnected,retained,afterClear:document.querySelectorAll('[data-route-journey-overlay]').length};
    })`);
    assert.deepEqual(pulses, { oldGone: true, retained: true, afterClear: 0 });
    await route();
    const fallback = await run(`routeClock(({last,fire})=>{Archify.routeProbe.selectJourneyIndex(1);const before=!!document.querySelector('[data-route-journey-overlay]');fire(last(860));return {before,after:!!document.querySelector('[data-route-journey-overlay]'),result:Archify.routeProbe.result()};})`);
    assert.equal(fallback.before, true); assert.equal(fallback.after, false); assert.equal(fallback.result.journey, 1);
    records.push({ scenario: 'pulse-fixture', pulses, fallback });
    await run('Archify.routeProbe.selectJourneyIndex(2)');
    await run(`routeWait(()=>routeEnds.some(e=>e.trusted&&e.name==='archify-route-journey-flow'))`);
    await run(`routeWait(()=>!document.querySelector('[data-route-journey-overlay]'))`);
    assert.equal((await snapshot('real-animation-complete')).result.journey, 2);
    await run(`Archify.routeProbe.selectJourneyIndex(3);window.foreignToken=Archify.motionGovernor.claim('story',()=>{})`);
    assert.equal((await snapshot('owner-replacement')).pulses, 0);
    await run('Archify.motionGovernor.release(foreignToken)'); await snapshot('owner-released');
  });

  await t.test('actual Finder, Focus, Lens, Guide and Camera handoffs retain cleanup options', async () => {
    await load('trace'); await click('#btn-route-probe'); await click('#route-probe-find');
    await run(`routeWait(()=>document.activeElement.id==='node-finder-input')`);
    assert.equal(await run('Archify.finder.context()'), 'route-source');
    assert.equal(await run(`document.getElementById('route-probe').dataset.finderOpen`), 'true');
    await key('Escape', 'Escape', 27); assert.equal(await run('document.activeElement.id'), 'route-probe-find');
    await run(`Archify.routeProbe.choose('users')`);
    const context = await run('Archify.routeProbe.finderContext()'); assert.ok(context.allowedIds.includes('db')); assert.ok(!context.allowedIds.includes('auth')); assert.match(context.badges.db, /4/);
    assert.equal(await run('Archify.routeProbe.openFinder()'), true);
    await run(`routeWait(()=>document.activeElement.id==='node-finder-input')`);
    await run(`document.getElementById('node-finder-input').value='PostgreSQL';document.getElementById('node-finder-input').dispatchEvent(new Event('input',{bubbles:true}))`);
    await key('Enter', 'Enter', 13); assert.equal((await snapshot('finder-result')).active, 'result');
    for (const [name, action] of [
      ['focus', `Archify.focus.set('api',{toggle:false})`], ['lens', `Archify.semanticLens.select('backend')`],
    ]) {
      await load('trace'); await route(); await run('Archify.routeProbe.playJourney()'); await run(action);
      const s = await snapshot(name + '-takeover'); assert.equal(s.active, null); assert.equal(s.overlays, 0); assert.equal(s.pulses, 0);
    }
    for (const [name, action] of [
      ['camera', `Archify.view.zoomIn()`], ['guide', `Archify.guide.open()`], ['still', `Archify.motionGovernor.pause()`],
      ['print-fixture', `dispatchEvent(new Event('beforeprint'))`],
      ['hidden-fixture', `Object.defineProperty(document,'hidden',{configurable:true,value:true});document.dispatchEvent(new Event('visibilitychange'))`],
    ]) {
      await load('trace'); await route(); await run('Archify.routeProbe.playJourney()'); await run(action);
      const s = await snapshot(name + '-pause'); assert.equal(s.active, 'result'); assert.equal(s.result.playing, false); assert.equal(s.result.journey, 0);
      if (name === 'hidden-fixture') { await run(`delete document.hidden;document.dispatchEvent(new Event('visibilitychange'))`); assert.equal(await run('Archify.routeProbe.isJourneyPlaying()'), false); }
      if (name === 'still') { await run('Archify.motionGovernor.resume()'); assert.equal(await run('Archify.routeProbe.isJourneyPlaying()'), false); }
    }
    await load(); await route(); assert.equal(await run('Archify.routeProbe.playJourney()'), false);
    await run('Archify.view.zoomIn()'); const view = await run('Archify.view.state()');
    assert.equal(await run('Archify.routeProbe.clear({preserveView:true,updateUrl:false})===undefined'), true);
    assert.deepEqual(await run('Archify.view.state()'), view); assert.equal((await snapshot('clear-preserved')).hash, '#route=users~db');
    await run('Archify.routeProbe.clear()'); assert.deepEqual(await run('Archify.view.state()'), view);
    await route(); await run('Archify.view.zoomIn();Archify.routeProbe.clear({restoreFocus:true})');
    assert.equal(await run('Archify.view.state().scale'), 1); assert.equal(await run('document.activeElement.id'), 'btn-route-probe'); await snapshot('clear-reset');
  });

  await t.test('hash restoration and controlled clipboard preserve invalid states, query and delayed feedback', async () => {
    await load('architecture', { suffix: '&keep=yes#route=users~db' });
    let s = await snapshot('initial-hash'); assert.equal(s.active, 'result'); assert.equal(s.result.journey, -1);
    for (const value of ['#route=users', '#route=unknown~db', '#route=users~db~api']) {
      await hash(value); assert.deepEqual((await snapshot(value)).result.nodes, ['users','cdn','lb','api','db']);
    }
    await hash('#route=users~users'); s = await snapshot('hash-same'); assert.equal(s.active, 'target'); assert.equal(s.panel, 'error'); assert.equal(s.hash, '#route=users~users');
    await hash('#route=db~users'); s = await snapshot('hash-unreachable'); assert.equal(s.active, 'target'); assert.equal(s.panel, 'error');
    await hash('#route='); assert.equal((await snapshot('hash-empty')).active, null);
    await hash('#route=api~db'); await hash('#unrelated=yes'); assert.equal((await snapshot('hash-missing')).active, null);
    assert.equal(await run('Archify.routeProbe.copyLink()'), false);
    await run(`Archify.routeProbe.begin({source:'api'});Archify.routeProbe.choose('db',{updateUrl:false})`); assert.equal(await run('location.hash'), '#unrelated=yes');
    assert.equal(await run('location.search'), '?theme=dark&keep=yes');
    for (const mode of ['success','reject','absent','failure','throw']) {
      const copied = await run(`(async()=>{
        const descriptor=Object.getOwnPropertyDescriptor(navigator,'clipboard'),exec=document.execCommand;let captured,commands=0;
        const expected=location.href.replace(/#.*$/,'')+'#route=api~db';
        Object.defineProperty(navigator,'clipboard',{configurable:true,value:${mode === 'success' ? "{writeText:v=>{captured=v;return Promise.resolve();}}" : mode === 'reject' ? "{writeText:()=>Promise.reject(new Error('fixture'))}" : 'undefined'}});
        document.execCommand=command=>{commands++;captured=document.activeElement.value;if(${JSON.stringify(mode)}==='throw')throw new Error('fixture');return ${JSON.stringify(mode)}!=='failure';};
        try {const value=await Archify.routeProbe.copyLink(),button=document.getElementById('route-probe-copy');return {value,commands,correct:captured===expected,fields:document.querySelectorAll('textarea[readonly]').length,text:button.textContent,aria:button.getAttribute('aria-label')};}
        finally {document.execCommand=exec;if(descriptor)Object.defineProperty(navigator,'clipboard',descriptor);else delete navigator.clipboard;}
      })()`);
      assert.equal(copied.value, !['failure','throw'].includes(mode)); assert.equal(copied.commands, mode === 'success' ? 0 : 1); assert.equal(copied.correct, true); assert.equal(copied.fields, 0);
      assert.match(copied.text, copied.value ? /Copied/ : /Copy failed/i); assert.match(copied.aria, copied.value ? /copied/i : /Could not copy/i);
      // A pending feedback callback is not cancelled by clearing the route.
      if (mode === 'throw') await run('Archify.routeProbe.clear()');
      await run(`routeWait(()=>document.getElementById('route-probe-copy').getAttribute('aria-label')==='Copy link to traced route')`);
      records.push({ scenario: 'copy-' + mode, ...copied });
    }
    await snapshot('feedback-after-clear');
  });

  await t.test('docking rectangle and timer fixtures preserve top/bottom ties, resize and scroll', async () => {
    await load(); await route();
    await run(`routeWait(()=>!document.querySelector('.diagram-container').hasAttribute('data-camera-transaction'))`);
    const docked = await run(`(()=>{
      const panel=document.getElementById('route-probe'),container=document.querySelector('.diagram-container'),nav=container.querySelector('.diagram-nav'),nodes=[...container.querySelectorAll('[data-node-id]')],elements=[panel,container,nav,...nodes];
      const saved=elements.map(e=>Object.getOwnPropertyDescriptor(e,'getBoundingClientRect')),props=['offsetWidth','offsetHeight'].map(n=>Object.getOwnPropertyDescriptor(panel,n));let top=10,size=100;
      const rect=(x,y,w,h)=>({left:x,right:x+w,top:y,bottom:y+h,width:w,height:h});
      panel.getBoundingClientRect=()=>rect(0,10,200,100);container.getBoundingClientRect=()=>rect(0,0,1000,1000);nav.getBoundingClientRect=()=>rect(600,900,300,40);
      nodes.forEach(n=>n.getBoundingClientRect=()=>rect(0,top,200,100));Object.defineProperty(panel,'offsetWidth',{configurable:true,get:()=>size?200:0});Object.defineProperty(panel,'offsetHeight',{configurable:true,get:()=>size});
      try {const sides=[];for(const value of [10,790,400]){top=value;container.dispatchEvent(new Event('scroll'));sides.push(panel.dataset.routeDock);}size=0;container.dispatchEvent(new Event('scroll'));const noSize=panel.dataset.routeDock;
        const scheduled=routeClock(({jobs})=>{dispatchEvent(new Event('resize'));return jobs.map(j=>j.delay);});Archify.routeProbe.clear({preserveView:true});container.dispatchEvent(new Event('scroll'));return {sides,noSize,scheduled,hidden:panel.getAttribute('data-route-dock')};}
      finally{elements.forEach((e,i)=>{if(saved[i])Object.defineProperty(e,'getBoundingClientRect',saved[i]);else delete e.getBoundingClientRect;});['offsetWidth','offsetHeight'].forEach((n,i)=>{if(props[i])Object.defineProperty(panel,n,props[i]);else delete panel[n];});}
    })()`);
    assert.deepEqual(docked.sides, ['bottom','top','top']); assert.equal(docked.noSize, 'top'); assert.equal(docked.hidden, null);
    assert.ok(docked.scheduled.includes(120)); assert.ok(docked.scheduled.includes(560)); records.push({ scenario: 'docking-fixture', ...docked });
    await route();
    for (const width of [720, 390, 1440]) {
      await send('Emulation.setDeviceMetricsOverride', { width, height: 900, deviceScaleFactor: 1, mobile: false });
      await run('Archify.viewerChromeLayout.whenStable()');
      await run(`routeWait(()=>['top','bottom'].includes(document.getElementById('route-probe').dataset.routeDock))`);
      const state = await run(`(()=>{const p=document.getElementById('route-probe'),container=document.querySelector('.diagram-container');container.scrollLeft=40;return {width:innerWidth,dock:p.dataset.routeDock,hidden:p.hidden};})()`);
      assert.equal(state.hidden, false); records.push({ scenario: 'viewport-' + width, ...state });
    }
  });

  await t.test('theme snapshots, Still rendering and real exports retain authored route semantics', async () => {
    for (const theme of ['dark','light']) {
      for (const position of [-1, 2]) {
        await load('trace', { theme, reduced: true }); await route();
        if (position >= 0) await run(`Archify.routeProbe.selectJourneyIndex(${position})`);
        await run(`routeWait(()=>!document.querySelector('.diagram-container').hasAttribute('data-camera-transaction'))`);
        assert.equal(await run('Archify.routeProbe.playJourney()'), false);
        const style = await run(`({flow:getComputedStyle(document.querySelector('.route-probe-flow')).animationName,pulses:document.querySelectorAll('[data-route-journey-overlay]').length})`);
        assert.deepEqual(style, { flow: 'none', pulses: 0 }); await snapshot(theme + '-' + position);
        if (evidence) {
          await run(`Promise.all(document.getAnimations().filter(a=>Number.isFinite(a.effect.getTiming().iterations)).map(a=>a.finished.catch(()=>{})))`);
          await run('new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))');
          const shot=await send('Page.captureScreenshot',{format:'png'});fs.writeFileSync(path.join(evidence,theme+(position<0?'-overview':'-position')+'.png'),Buffer.from(shot.data,'base64'));
        }
        const exported = await run(`(async()=>{
          const original=URL.createObjectURL;let blob;URL.createObjectURL=function(v){if(v.type.startsWith('image/svg+xml'))blob=v;return original.call(URL,v);};
          try {await Archify.exportMenu.run('svg');}finally{URL.createObjectURL=original;}
          const root=new DOMParser().parseFromString(await blob.text(),'image/svg+xml').documentElement;
          return {clean:!root.hasAttribute('data-route-active')&&!root.hasAttribute('data-route-journey')&&!root.querySelector('[data-route-match],[data-route-step],[data-route-journey-current],[data-route-probe-overlay],[data-route-journey-overlay]'),viewBox:root.getAttribute('viewBox')===document.querySelector('.diagram-container > svg').getAttribute('viewBox')};
        })()`);
        assert.deepEqual(exported, { clean: true, viewBox: true });
      }
    }
    await load('trace'); await route(); await run('Archify.motionGovernor.pause()');
    assert.equal(await run(`getComputedStyle(document.querySelector('.route-probe-flow')).animationName`), 'none');
    assert.equal(await run('Archify.routeProbe.playJourney()'), false);
    await run('Archify.exportMenu.open()');
    const share = await run(`(()=>{const n=document.querySelector('[data-action="route-share-card"]');return {hidden:n.hidden,disabled:n.disabled};})()`);
    assert.deepEqual(share, { hidden: false, disabled: false });
    await run('Archify.routeProbe.clear({preserveView:true})');
    assert.equal(await run(`document.querySelector('[data-action="route-share-card"]').hidden`), true);
  });
});
```

## test/route-probe.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-route-probe-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers inherit one viewer-only Route Probe', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="route-probe" hidden role="region" aria-labelledby="route-probe-title"/, mode);
    assert.match(html, /id="btn-route-probe"[^>]+aria-label="Trace a directed route"[^>]+aria-pressed="false"[^>]+aria-controls="route-probe"/, mode);
    assert.match(html, /Archify\.routeProbe = \(function \(\)/, mode);
    assert.match(html, /Route Probe — shortest directed path over compiled semantics/, mode);
    assert.equal((html.match(/<svg\b/g) || []).length, 1, `${mode} keeps one static canonical SVG`);
    assert.doesNotMatch(canonicalSvg(html), /data-route-|route-probe-flow/, mode);
  }
});

test('Route Probe uses deterministic authored-direction BFS and exposes reachability first', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /function outgoingByNode\(\)/);
  assert.match(html, /var from = edge\.getAttribute\('data-edge-from'\)/);
  assert.match(html, /var to = edge\.getAttribute\('data-edge-to'\)/);
  assert.match(html, /if \(!byId\[from\] \|\| !byId\[to\] \|\| from === to\) return/);
  assert.match(html, /outgoing\[from\]\.push\(\{ edge: edge, to: to \}\)/);
  assert.match(html, /function reachableFrom\(source\)/);
  assert.match(html, /data-route-candidate/);
  assert.match(html, /function shortestDirectedPath\(source, target\)/);
  assert.match(html, /var queue = \[source\]/);
  assert.match(html, /previous\[link\.to\] = \{ from: queue\[cursor\], edge: link\.edge \}/);
  assert.match(html, /routeEdges\.unshift\(step\.edge\)/);
  assert.match(html, /nodeIds\.unshift\(step\.from\)/);
});

test('Route Probe turns a two-node question into a readable route receipt and stable link', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /svg\.setAttribute\('data-route-picking', 'target'\)/);
  assert.match(html, /svg\.setAttribute\('data-route-active', startId \+ '~' \+ endId\)/);
  assert.match(html, /node\.setAttribute\('data-route-step', String\(step\)\)/);
  assert.match(html, /edge\.setAttribute\('data-route-match', ''\)/);
  assert.match(html, /clone\.setAttribute\('pathLength', '1'\)/);
  assert.match(html, /clone\.style\.setProperty\('--route-step', String\(step\)\)/);
  assert.match(html, /#route=' \+ encodeURIComponent\(startId\) \+ '~' \+ encodeURIComponent\(endId\)/);
  assert.match(html, /new URLSearchParams\(location\.hash\.replace/);
  assert.match(html, /Archify\.view\.reveal\(result\.nodes, \{ includeNeighbors: false, reason: 'route' \}\)/);
  assert.match(html, /shortest authored route/);
});

test('Route Probe hands large-diagram endpoint selection to a reachability-aware Finder', () => {
  const html = render('dataflow', CASES.dataflow);
  assert.match(html, /id="route-probe-find"[^>]+aria-label="Find a route start"[^>]+data-node-finder-trigger/);
  assert.match(html, /function hopDistancesFrom\(source\)/);
  assert.match(html, /kind: 'route-source'/);
  assert.match(html, /outgoing\[id\] && outgoing\[id\]\.length/);
  assert.match(html, /kind: 'route-target'/);
  assert.match(html, /Object\.keys\(distances\)\.filter/);
  assert.match(html, /targetBadges\[id\] = viewerCount\('viewer\.route\.hop', distances\[id\]\)/);
  assert.match(html, /Archify\.finder\.open\(\{ context: context \}\)/);
  assert.match(html, /findBtn\.textContent = viewerText\('viewer\.route\.destination\.find'\)/);
  assert.match(html, /panel\.setAttribute\('data-finder-open', 'true'\)/);
  assert.match(html, /\.route-probe\[data-finder-open="true"\]/);
});

test('Route Probe keeps pointer, keyboard, motion, embed, and export boundaries explicit', () => {
  const html = render('sequence', CASES.sequence);
  assert.match(html, /svg\.addEventListener\('click', interceptSelection, true\)/);
  assert.match(html, /svg\.addEventListener\('keydown', interceptSelection, true\)/);
  assert.match(html, /event\.key !== 'Enter' && event\.key !== ' '/);
  assert.match(html, /e\.key === 'r' \|\| e\.key === 'R'/);
  assert.match(html, /e\.key === 'Escape' && Archify\.routeProbe\.active\(\)/);
  assert.match(html, /html\[data-embed="true"\] \.route-probe/);
  assert.match(html, /html\.getAttribute\('data-embed'\) === 'true'/);
  assert.match(html, /\.route-probe\[data-route-dock="top"\]/);
  assert.match(html, /function overlapArea\(a, b\)/);
  assert.match(html, /score\(topCandidate\) <= score\(bottomCandidate\)/);
  assert.match(html, /container\.addEventListener\('scroll', updateDocking, \{ passive: true \}\)/);
  assert.match(html, /@keyframes archify-route-probe-flow/);
  assert.match(html, /@media \(prefers-reduced-motion: reduce\)[\s\S]+\.route-probe-flow \{[\s\S]+animation: none !important/);
  assert.match(html, /clone\.removeAttribute\('data-route-picking'\)/);
  assert.match(html, /clone\.removeAttribute\('data-route-active'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-route-probe-overlay\]'\)/);
  assert.match(html, /!clone\.hasAttribute\('data-route-active'\)/);
  assert.doesNotMatch(canonicalSvg(html), /data-route-|route-probe-flow/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/route-share-card.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-route-share-card-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one resolved-only Route Share Card Export item', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /data-action="route-share-card"[^>]*hidden disabled[^>]*>[\s\S]*?Route Share Card[\s\S]*?1200(?:&times;|×)630 PNG/, mode);
    assert.match(html, /function syncRouteShareItem\(\)/, mode);
    assert.match(html, /routeShareItem\.hidden = !snapshot;/, mode);
    assert.match(html, /routeShareItem\.disabled = !snapshot;/, mode);
    assert.match(html, /\.toolbar \.export-menu button\[hidden\] \{ display: none; \}/, mode);
    assert.doesNotMatch(html, /id="route-probe-share"|class="route-probe-share"/, mode);
    assert.doesNotMatch(canonicalSvg(html), /data-share-route(?:-|=)/, mode);
  }
});

test('Route Share Card menu lifecycle excludes hidden state from keyboard navigation', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /function open\(focusLast\)[\s\S]*?syncRouteShareItem\(\);/);
  assert.match(html, /items\(\)\.filter\(function \(i\) \{ return !i\.hidden && !i\.disabled; \}\)/);
  assert.match(html, /function clear\(options\)[\s\S]*?Archify\.exportMenu\.syncRouteShare\(\)/);
  assert.match(html, /function showResult\(result, options\)[\s\S]*?Archify\.exportMenu\.syncRouteShare\(\)/);
  assert.match(html, /html\[data-embed="true"\] \.toolbar/);
  assert.match(html, /@media print[\s\S]*?\.toolbar/);
});

test('Route Share Card snapshots copy exact resolved node and relationship identity without rerouting', () => {
  const html = render('architecture', CASES.architecture);
  const snapshotBlock = html.match(/function exportSnapshot\(\) \{[\s\S]*?\n      \}/)?.[0] || '';
  const geometryBlock = html.match(/function hasDrawableGeometry\(element\) \{[\s\S]*?\n    \}/)?.[0] || '';
  assert.match(snapshotBlock, /nodeIds: activeNodeIds\.slice\(\)/);
  assert.match(snapshotBlock, /hops: activeEdges\.length/);
  assert.match(snapshotBlock, /edges: activeEdges\.map\(function \(edge\) \{/);
  assert.match(snapshotBlock, /key: edge\.getAttribute\('data-edge-key'\)/);
  assert.match(snapshotBlock, /seenNodeIds = Object\.create\(null\)/);
  assert.match(snapshotBlock, /seenEdgeKeys = Object\.create\(null\)/);
  assert.match(snapshotBlock, /fragment\.getAttribute\('data-edge-from'\) === activeNodeIds\[index\]/);
  assert.match(snapshotBlock, /drawableFragments = fragments\.filter\(hasDrawableGeometry\)/);
  assert.match(snapshotBlock, /drawableFragments\.length !== 1/);
  assert.match(snapshotBlock, /drawableFragments\[0\] !== edge/);
  assert.match(geometryBlock, /geometry\.getTotalLength/);
  assert.match(geometryBlock, /Number\.isFinite\(length\) && length > 0/);
  assert.match(geometryBlock, /nan\|infinity/i);
  assert.match(html, /exportSnapshot: exportSnapshot/);
  assert.doesNotMatch(snapshotBlock, /shortestDirectedPath|outgoingByNode|reachableFrom|labelAt|nearest/i);
});

test('Route Share Card snapshot fails closed when the resolved DOM becomes stale or conflicting', () => {
  const html = render('workflow', CASES.workflow);
  const snapshotBlock = html.match(/function exportSnapshot\(\) \{[\s\S]*?\n      \}/)?.[0] || '';
  assert.match(snapshotBlock, /activeNodeIds\.length < 2/);
  assert.match(snapshotBlock, /activeEdges\.length !== activeNodeIds\.length - 1/);
  assert.match(snapshotBlock, /allNodes\.filter\(function \(node\)/);
  assert.match(snapshotBlock, /!edge \|\| !svg\.contains\(edge\)/);
  assert.match(snapshotBlock, /!edgeKey \|\| seenEdgeKeys\[edgeKey\]/);
  assert.match(snapshotBlock, /!fragments\.every/);
  assert.match(snapshotBlock, /return null/);
});

test('Route variant decorates only a finite canonical clone with dedicated static attributes', () => {
  const html = render('architecture', CASES.architecture);
  const applyBlock = html.match(/function applyRouteSnapshot\(clone, snapshot\) \{[\s\S]*?\n      \}/)?.[0] || '';
  assert.match(applyBlock, /snapshot\.edges\.length !== snapshot\.nodeIds\.length - 1/);
  assert.match(applyBlock, /snapshot\.hops !== snapshot\.edges\.length/);
  assert.match(applyBlock, /matchedNodes\.length !== 1/);
  assert.match(applyBlock, /matchedEdges\.every/);
  assert.match(applyBlock, /drawableMatches = matchedEdges\.filter\(hasDrawableGeometry\)/);
  assert.match(applyBlock, /drawableMatches\.length !== 1/);
  assert.match(applyBlock, /data-share-route-match/);
  assert.match(applyBlock, /data-share-route-start/);
  assert.match(applyBlock, /data-share-route-middle/);
  assert.match(applyBlock, /data-share-route-end/);
  assert.match(applyBlock, /clone\.removeAttribute\('data-animation'\)/);
  assert.doesNotMatch(applyBlock, /setAttribute\('data-route-(?:match|step|start|end|active|journey)/);
  assert.match(html, /canonicalStateClean && finiteSvgDimensions && applyRouteSnapshot\(clone, opts\.routeSnapshot\)/);
  assert.match(html, /Number\.isFinite\(vb\.width\)[\s\S]*?vb\.width > 0 && vb\.height > 0/);
  assert.ok(html.indexOf('var canonicalStateClean =') < html.indexOf('applyRouteSnapshot(clone, opts.routeSnapshot)'), 'canonical cleanup must precede route decoration');
});

test('clone-only Route styling retains context and distinguishes start, middle, and end without motion', () => {
  const html = render('dataflow', CASES.dataflow);
  assert.match(html, /svg\[data-share-route\] \[data-node-id\], svg\[data-share-route\] \[data-edge-from\] \{ opacity: 0\.18; \}/);
  assert.match(html, /svg\[data-share-route\] \[data-share-route-match\] \{ opacity: 1; \}/);
  assert.match(html, /data-share-route-start[\s\S]*?stroke-dasharray: 5 3/);
  assert.match(html, /data-share-route-middle[\s\S]*?stroke-width: 2\.2/);
  assert.match(html, /data-share-route-end[\s\S]*?stroke-width: 3\.4/);
  assert.doesNotMatch(html.match(/if \(opts\.routeSnapshot\) \{[\s\S]*?\n        \}/)?.[0] || '', /display:\s*none|animation:|filter:|transform:/);
});

test('Route Share Card reuses one 1200x630 variant seam and publishes a truthful receipt', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /function rasterizeShareCard\(options\)/);
  assert.match(html, /options\.variant !== 'route'/);
  assert.match(html, /var snapshot = Archify\.routeProbe && Archify\.routeProbe\.exportSnapshot\(\)/);
  assert.match(html, /renderShareCard\(\{ routeSnapshot: snapshot \}\)/);
  assert.doesNotMatch(html, /function rasterizeRouteShareCard|routeShareCard:/);
  assert.match(html, /var title = titleNode \? titleNode\.textContent : document\.title;/);
  assert.match(html, /viewerCount\('viewer\.export\.card\.routeSummary', routeSnapshot\.hops/);
  assert.match(html, /source: routeSnapshot\.source\.label/);
  assert.match(html, /target: routeSnapshot\.target\.label/);
  assert.match(html, /recordExportReceipt\('share-card', blob, false, \{ width: SHARE_CARD_WIDTH, height: SHARE_CARD_HEIGHT \}, 'route', true\)/);
  assert.match(html, /diagramFilename\(\) \+ '-route-share-card\.png'/);
  assert.match(html, /data-last-export-variant/);
  assert.match(html, /data-last-export-route-state-clean/);
  assert.match(html, /clearExportReceipt\(\);[\s\S]*?var snapshot = Archify\.routeProbe/);
  assert.match(html, /function runExport\(format\)[\s\S]*?clearExportReceipt\(\);/);
  assert.match(html, /var ctx = canvas2dOrThrow\(canvas, viewerText\('viewer\.export\.shareCard'\)\)/);
});

test('skill and READMEs describe the optional Export variant and show one real card without changing the hero', () => {
  const viewer = fs.readFileSync(path.join(skillRoot, 'references', 'viewer-runtime.md'), 'utf8');
  assert.match(viewer, /Export → Route Share Card/);
  assert.match(viewer, /format=share-card/);
  assert.match(viewer, /variant=route/);
  assert.match(viewer, /data-share-route-\*/);
  assert.match(viewer, /download-only/i);

  for (const readme of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const text = fs.readFileSync(path.join(repoRoot, readme), 'utf8');
    assert.match(text, /Export → Route Share Card/, readme);
    assert.match(text, /docs\/assets\/archify-route-share-card\.png/, readme);
  }

  const png = fs.readFileSync(path.join(repoRoot, 'docs/assets/archify-route-share-card.png'));
  assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a');
  assert.equal(png.readUInt32BE(16), 1200);
  assert.equal(png.readUInt32BE(20), 630);

});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/semantic-camera.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-semantic-camera-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function svg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers ship the same geometry-neutral semantic camera', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /function frameDesktop\(ids, options\)/, mode);
    assert.match(html, /function semanticIds\(ids, includeNeighbors\)/, mode);
    assert.match(html, /if \(seeds\[from\] \|\| seeds\[to\]\) \{ wanted\[from\] = true; wanted\[to\] = true; \}/, mode);
    assert.match(html, /contentScale = Math\.min\(svgWidth \/ viewBox\.width, svgHeight \/ viewBox\.height\)/, mode);
    assert.match(html, /targetScale = Math\.max\(1, Math\.min\(maxScale, targetScale\)\)/, mode);
    assert.match(html, /visibleTop = Math\.max\(0, -containerRect\.top\)/, mode);
    assert.match(html, /visibleBottom - visibleTop >= 240/, mode);
    assert.match(html, /data-camera-mode/, mode);
    assert.match(html, /data-camera-indicator/, mode);
    assert.match(html, /var resolvedLevel = semantic \? viewerText\('viewer\.nav\.level\.auto'\) : levelLabel/, mode);
    assert.match(html, /is-camera-moving/, mode);
    assert.match(html, /cubic-bezier\(0\.22, 1, 0\.36, 1\)/, mode);
    assert.doesNotMatch(svg(html), /data-camera-mode|is-camera-moving|AUTO /, mode);
  }
});

test('semantic camera follows reader intent but yields to manual navigation', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /beginHandoff\(previousIndex, index, previous, view, outgoingBeatIndex, options\.playback === true \? 'playback' : 'guided'\)/);
  assert.match(html, /reveal\(\[id\], \{ includeNeighbors: true, reason: 'focus' \}\)/);
  assert.match(html, /reveal\(\[id\], \{ includeNeighbors: true, reason: 'relationship' \}\)/);
  assert.match(html, /reveal\(\[id\], \{ includeNeighbors: true, reason: 'finder' \}\)/);
  assert.match(html, /function interruptCamera\(reason\)/);
  assert.match(html, /Archify\.guidedViews\.pause\(\)/);
  assert.match(html, /container\.addEventListener\('pointerdown',[\s\S]+interruptCamera\(\)/);
  assert.match(html, /\.overview-map, \.route-probe, \.semantic-lens/);
  assert.match(html, /window\.innerWidth <= 720 && container\.hasAttribute\('data-wide-diagram'\) && Date\.now\(\) > autoScrollUntil/);
  assert.match(html, /reset\(\{ automatic: true \}\)/);
  assert.match(html, /routeReceipt\.hasAttribute\('data-route-journey'\)/);
  assert.match(html, /receiptBottom \+ 24/);
});

test('semantic camera keeps mobile on its contained scroll model and respects reduced motion', () => {
  const html = render('sequence', CASES.sequence);
  assert.match(html, /if \(window\.innerWidth > 720\) return frameDesktop\(ids, options\)/);
  assert.match(html, /if \(!container\.hasAttribute\('data-wide-diagram'\)\) \{[\s\S]+cameraReceipt\(\{ scale: 1, x: 0, y: 0, mode: 'semantic' \}/);
  assert.match(html, /state\.scale = 1;[\s\S]+state\.x = 0;[\s\S]+state\.y = 0;[\s\S]+state\.mode = 'semantic';[\s\S]+apply\(\)/);
  assert.match(html, /autoScrollUntil = Date\.now\(\) \+ \(instant \? 50 : 470\)/);
  assert.match(html, /behavior: instant \? 'auto' : 'smooth'/);
  assert.match(html, /svg \[data-node-id\], svg \[data-edge-from\], svg \[data-detail\], svg \[data-detail-anchor\], svg \[data-legend-hit\], svg \{ transition: none !important; \}/);
});

test('semantic camera remains outside canonical SVG export state', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /clone\.style\.removeProperty\('transform'\)/);
  assert.match(html, /clone\.removeAttribute\('data-view-scale'\)/);
  assert.match(html, /!clone\.style\.getPropertyValue\('transform'\)/);
  assert.doesNotMatch(svg(html), /style="[^"]*transform|data-view-scale|data-camera-mode/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/semantic-flow.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-semantic-flow-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers inherit one selection-triggered Semantic Flow signal', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /function renderFlowOverlay\(entries\)/, mode);
    assert.match(html, /var MAX_LENS_FLOW_EDGES = 24/, mode);
    assert.match(html, /setAttribute\('class', 'semantic-lens-flow'\)/, mode);
    assert.match(html, /data-semantic-lens-overlay/, mode);
    assert.doesNotMatch(canonicalSvg(html), /semantic-lens-flow|semantic-lens-overlay|data-lens-flow/, mode);
  }
});

test('Semantic Flow clones only exact matched authored geometry and preserves direction', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /var matchedFlow = \[\]/);
  assert.match(html, /direction = fromKind === selectedKinds\[0\] \? 'forward' : 'reverse'/);
  assert.match(html, /fromKind === selectedKinds\[0\] && toKind !== selectedKinds\[0\] \? 'out'/);
  assert.match(html, /toKind === selectedKinds\[0\] && fromKind !== selectedKinds\[0\] \? 'in'/);
  assert.match(html, /matchedFlow\.push\(\{ edge: edge, direction: direction \}\)/);
  assert.match(html, /clone\.removeAttribute\('marker-end'\)/);
  assert.match(html, /clone\.removeAttribute\('data-edge-key'\)/);
  assert.match(html, /clone\.setAttribute\('pathLength', '1'\)/);
  assert.match(html, /wrapper\.setAttribute\('transform', entry\.edge\.members\[0\]\.getAttribute\('transform'\)\)/);
  assert.match(html, /svg\.setAttribute\('data-lens-flow-count', String\(entries\.length\)\)/);
  assert.match(html, /svg\.insertBefore\(overlay, firstNode\)/);
});

test('Semantic Flow has preset identities and motion-safe density boundaries', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /\.semantic-lens-flow\[data-direction="out"\],[\s\S]+var\(--frontend-stroke\)/);
  assert.match(html, /\.semantic-lens-flow\[data-direction="in"\],[\s\S]+var\(--database-stroke\)/);
  assert.match(html, /\.semantic-lens-flow\[data-direction="within"\][\s\S]+var\(--messagebus-stroke\)/);
  assert.match(html, /svg\[data-preset="signal-flow"\] \.semantic-lens-flow/);
  assert.match(html, /svg\[data-preset="blueprint"\] \.semantic-lens-flow/);
  assert.match(html, /@keyframes archify-semantic-lens-flow/);
  assert.match(html, /animation: archify-semantic-lens-flow 1\.35s linear 1 both/);
  assert.match(html, /entries\.length > MAX_LENS_FLOW_EDGES/);
  assert.match(html, /data-lens-flow-density', 'quiet'/);
  assert.match(html, /html\[data-embed="true"\] \.semantic-lens-overlay/);
  assert.match(html, /@media print \{[\s\S]+\.semantic-lens-overlay \{ display: none !important; \}/);
  assert.match(html, /@media \(prefers-reduced-motion: reduce\)[\s\S]+\.semantic-lens-flow \{[\s\S]+animation: none !important/);
});

test('Semantic Flow cleanup and exports remain canonical', () => {
  const html = render('dataflow', CASES.dataflow);
  assert.match(html, /function removeFlowOverlay\(\)/);
  assert.match(html, /svg\.querySelectorAll\('\[data-semantic-lens-overlay\]'\)/);
  assert.match(html, /svg\.removeAttribute\('data-lens-flow-count'\)/);
  assert.match(html, /svg\.removeAttribute\('data-lens-flow-density'\)/);
  assert.match(html, /clone\.removeAttribute\('data-lens-flow-count'\)/);
  assert.match(html, /clone\.removeAttribute\('data-lens-flow-density'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-semantic-lens-overlay\]'\)/);
  assert.match(html, /\[data-semantic-lens-overlay\],[^']*\[data-lens-match\]/);
  assert.doesNotMatch(canonicalSvg(html), /semantic-lens-flow|semantic-lens-overlay|data-lens-flow/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/semantic-legend-gateway.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-semantic-legend-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, mutate) {
  const output = path.join(tmp, `${mode}.html`);
  let input = path.join(skillRoot, 'examples', CASES[mode]);
  if (mutate) {
    const document = JSON.parse(fs.readFileSync(input, 'utf8'));
    mutate(document);
    input = path.join(tmp, `${mode}.json`);
    fs.writeFileSync(input, JSON.stringify(document));
  }
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    input,
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

function values(svg, attribute) {
  const pattern = new RegExp(`${attribute}="([^"]+)"`, 'g');
  return [...svg.matchAll(pattern)].map((match) => match[1]);
}

test('only legends with an exact node-kind meaning publish bridge entries', () => {
  const architecture = canonicalSvg(render('architecture'));
  const architectureKinds = new Set(values(architecture, 'data-node-kind'));
  assert.deepEqual(new Set(values(architecture, 'data-legend-kind')), architectureKinds);
  assert.equal((architecture.match(/data-legend-bridge=""/g) || []).length, 1);

  const workflow = canonicalSvg(render('workflow', (document) => {
    // The production fixture intentionally fills its legend band with authored
    // routes, so legacy implicit-auto correctly hides that legend. Keep this
    // semantic bridge contract focused by rendering the same typed nodes with
    // an explicit full legend and no relationship geometry in the band.
    document.meta.legend = { mode: 'all' };
    delete document.meta.viewBox;
    document.edges = [];
    delete document.mainPath;
  }));
  assert.deepEqual(values(workflow, 'data-legend-kind'), [
    'frontend', 'backend', 'security', 'messagebus', 'database', 'cloud', 'external',
  ]);

  const lifecycle = canonicalSvg(render('lifecycle'));
  assert.deepEqual(values(lifecycle, 'data-legend-kind'), [
    'start', 'active', 'waiting', 'decision', 'success', 'failure',
  ]);

  const sequence = canonicalSvg(render('sequence'));
  assert.deepEqual(values(sequence, 'data-legend-semantic-kind'), [
    'emphasis', 'return', 'security', 'dashed', 'default',
  ]);
  assert.doesNotMatch(sequence, /data-legend-bridge|data-legend-kind=/);
  assert.match(sequence, />Legend</);

  const dataflow = canonicalSvg(render('dataflow'));
  assert.deepEqual(values(dataflow, 'data-legend-semantic-kind'), [
    'emphasis', 'security', 'dashed', 'database', 'default',
  ]);
  assert.deepEqual(values(dataflow, 'data-legend-kind'), ['database']);
  assert.equal((dataflow.match(/data-legend-bridge=""/g) || []).length, 1);
  assert.ok(values(dataflow, 'data-node-kind').includes('database'));
  assert.match(dataflow, />Legend</);
});

test('runtime decoration derives counts from compiled node facts and stays viewer-only', () => {
  const html = render('architecture');
  const svg = canonicalSvg(html);
  assert.match(html, /collectKinds\(\)\.forEach\(function \(kind\) \{ facts\[kind\.id\] = kind; \}\)/);
  assert.match(html, /var count = fact \? fact\.nodes\.length : 0/);
  assert.match(html, /data-legend-bridge-runtime/);
  assert.match(html, /data-legend-count-badge/);
  assert.match(html, /entry\.setAttribute\('role', 'button'\)/);
  assert.match(html, /legendBridge\.setAttribute\('role', legendEntries\.length >= 3 \? 'toolbar' : 'group'\)/);
  assert.match(html, /var visibleLabel = entry\.getAttribute\('data-legend-label'\) \|\| fact\.label/);
  assert.match(html, /entry\.setAttribute\('aria-label', viewerCount\('viewer\.lens\.legend\.inspect', count/);
  assert.match(html, /if \(!legendBridge \|\| html\.getAttribute\('data-embed'\) === 'true'\) return false/);
  assert.doesNotMatch(svg, /data-legend-bridge-runtime|data-legend-count=|role="toolbar"/);
  assert.doesNotMatch(svg, /data-legend-kind="[^"]+"[^>]+(?:role=|aria-pressed=)/);
});

test('preview is soft, input-aware, and yields to stronger exploration owners', () => {
  const html = render('workflow');
  const preview = html.slice(
    html.indexOf('function previewLegendKind'),
    html.indexOf('function syncLegendPreview'),
  );
  assert.match(html, /window\.matchMedia\('\(hover: hover\) and \(pointer: fine\)'\)/);
  assert.match(html, /event\.pointerType === 'touch' \|\| \(finePointerQuery && !finePointerQuery\.matches\)/);
  assert.match(html, /legendBridge\.addEventListener\('focusin'/);
  assert.match(html, /legendBridge\.addEventListener\('focusout'/);
  assert.match(preview, /data-legend-preview-match/);
  assert.match(preview, /data-legend-preview-peer/);
  assert.doesNotMatch(preview, /renderFlowOverlay|data-semantic-lens-overlay/);
  assert.match(html, /selectedKinds\.length > 0 \|\| !panel\.hidden \|\| html\.getAttribute\('data-present'\) === 'true'/);
  assert.match(html, /data-focus-active.*data-intent-trace-active/s);
  assert.match(html, /data-route-picking.*data-story-active.*data-relationship-preview-active/s);
  assert.match(html, /svg\[data-legend-preview-active\] \[data-node-id\]/);
});

test('activation delegates to Semantic Lens and supports roving keyboard navigation', () => {
  const html = render('lifecycle');
  const activation = html.slice(
    html.indexOf('function activateLegendEntry'),
    html.indexOf('function removeFlowOverlay'),
  );
  assert.match(activation, /select\(entry\.getAttribute\('data-legend-kind'\)\)/);
  assert.match(activation, /open\(\{ opener: entry \}\)/);
  assert.match(html, /event\.key === 'Enter' \|\| event\.key === ' '/);
  assert.match(html, /event\.key === 'ArrowRight'/);
  assert.match(html, /event\.key === 'ArrowLeft'/);
  assert.match(html, /event\.key === 'Home'/);
  assert.match(html, /event\.key === 'End'/);
  assert.match(html, /lensOpener\.focus\(\)/);
  assert.match(html, /entry\.setAttribute\('aria-pressed', selected \? 'true' : 'false'\)/);
});

test('bridge state is print-safe, reduced-motion-safe, and absent from canonical export', () => {
  const html = render('architecture');
  assert.match(html, /\[data-legend-bridge-runtime\] \{ display: none !important; \}/);
  assert.match(html, /html:not\(\[data-embed="true"\]\) \.diagram-container \{\s*padding: 0\.75rem 0\.75rem 4\.25rem;/);
  assert.match(html, /data-legend-hit[^}]*transition/s);
  assert.match(html, /prefers-reduced-motion: reduce[\s\S]*\[data-legend-hit\]/);
  assert.match(html, /clone\.removeAttribute\('data-legend-preview-active'\)/);
  assert.match(html, /clone\.querySelectorAll\('\[data-legend-bridge-runtime\]'\)/);
  assert.match(html, /el\.removeAttribute\('data-legend-kind'\)/);
  assert.match(html, /el\.removeAttribute\('data-legend-label'\)/);
  assert.match(html, /el\.removeAttribute\('data-legend-bridge'\)/);
  assert.match(html, /\[data-legend-preview-match\], \[data-legend-preview-selected\], \[data-legend-preview-peer\]/);
  assert.match(html, /\[data-legend-bridge\], \[data-legend-kind\], \[data-legend-bridge-runtime\]/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/semantic-lens-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { findChrome } from '../bin/visual-check.mjs';
import { desktopBrowser, desktopPointerCheck } from './helpers/desktop-browser.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;

test('Semantic Lens preserves selection, legend preview and panel contracts', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser Semantic Lens checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-lens-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_LENS_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const records = [];
  t.after(() => {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n');
  });
  const cases = {
    architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    files[mode] = path.join(scratch, mode + '.html');
    execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      path.join(skillRoot, 'examples', example), files[mode]]);
  }
  const trace = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', cases.architecture), 'utf8'));
  trace.meta.animation = 'trace';
  const traceInput = path.join(scratch, 'trace.json');
  fs.writeFileSync(traceInput, JSON.stringify(trace)); files.trace = path.join(scratch, 'trace.html');
  execFileSync(process.execPath, [path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'), traceInput, files.trace]);
  // Initialization fixtures alter only inputs immediately before Lens captures DOM/media.
  const original = fs.readFileSync(files.architecture, 'utf8');
  assert.ok(original.includes('    Archify.semanticLens = (function () {'), 'Lens fixture anchor');
  for (const [name, source] of Object.entries({
    absent: `document.querySelector('[data-legend-bridge]').remove();`,
    small: `document.querySelectorAll('[data-legend-kind]').forEach((e,i)=>{if(i>1)e.remove();});`,
    zero: `document.querySelector('[data-legend-kind]').setAttribute('data-legend-kind','missing');`,
    coarse: `window.lensMatchMedia=window.matchMedia;window.matchMedia=q=>q==='(hover: hover) and (pointer: fine)'?{matches:false}:lensMatchMedia(q);`,
  })) {
    files[name] = path.join(scratch, name + '.html');
    fs.writeFileSync(files[name], original.replace('    Archify.semanticLens = (function () {', source + '\n    Archify.semanticLens = (function () {'));
  }
  const browser = desktopBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  const checkPointer = await desktopPointerCheck(browser, session);
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  await send('Emulation.setFocusEmulationEnabled', { enabled: true });
  async function run(expression) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.lensErrors=[];window.lensEnds=[];addEventListener('animationend',e=>{if(e.target.matches('.semantic-lens-flow'))lensEnds.push({name:e.animationName,trusted:e.isTrusted});},true);addEventListener('error',e=>lensErrors.push(e.message));
    addEventListener('unhandledrejection',e=>lensErrors.push(String(e.reason)));
    try {localStorage.removeItem('archify-motion');} catch (_) {}
    window.lensWait=predicate=>new Promise((resolve,reject)=>{
      const start=performance.now();function sample(){if(predicate())return resolve();
      if(performance.now()-start>12000)return reject(new Error('Lens observation timed out'));requestAnimationFrame(sample);}requestAnimationFrame(sample);
    });
  ` });
  async function load(mode = 'architecture', { theme = 'dark', reduced = false, suffix = '' } = {}) {
    await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 0, y: 0 });
    await send('Emulation.setDeviceMetricsOverride', { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
    await send('Emulation.setEmulatedMedia', { media: '', features: [{ name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' }] });
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    await send('Page.navigate', { url: pathToFileURL(files[mode]).href + `?theme=${theme}` + suffix });
    await loaded;
    await checkPointer();
    await run('document.fonts.ready'); await run('Archify.viewerChromeLayout.whenStable()');
  }
  async function point(selector) {
    return run(`(()=>{const r=document.querySelector(${JSON.stringify(selector)}).getBoundingClientRect();return {x:r.x+r.width/2,y:r.y+r.height/2};})()`);
  }
  async function move(selector) { await send('Input.dispatchMouseEvent', { type: 'mouseMoved', ...(selector ? await point(selector) : { x: 0, y: 0 }) }); }
  async function click(selector) {
    const p = await point(selector);
    await send('Input.dispatchMouseEvent', { type: 'mousePressed', ...p, button: 'left', clickCount: 1 });
    await send('Input.dispatchMouseEvent', { type: 'mouseReleased', ...p, button: 'left', clickCount: 1 });
  }
  async function key(key, code, windowsVirtualKeyCode) {
    await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode, text: key === 'Enter' ? '\r' : key === ' ' ? ' ' : undefined });
    await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode });
  }
  const legend = kind => `[data-legend-kind="${kind}"]`;
  async function snapshot(scenario) {
    const state = await run(`(()=>{
      const svg=document.querySelector('.diagram-container > svg'),p=Archify.semanticLens;
      const ids=a=>[...svg.querySelectorAll('[data-node-id]['+a+']')].map(n=>n.getAttribute('data-node-id'));
      return {active:p.active(),open:p.isOpen(),kinds:p.kinds(),hash:location.hash,
        selected:ids('data-lens-selected'),peers:ids('data-lens-peer'),preview:svg.getAttribute('data-legend-preview-active'),
        previewSelected:ids('data-legend-preview-selected'),previewPeers:ids('data-legend-preview-peer'),
        edges:[...svg.querySelectorAll('[data-edge-from][data-lens-match]')].map(n=>[n.getAttribute('data-edge-from'),n.getAttribute('data-edge-to'),n.getAttribute('data-edge-key')]),
        count:svg.getAttribute('data-lens-flow-count'),density:svg.getAttribute('data-lens-flow-density'),
        overlays:svg.querySelectorAll('[data-semantic-lens-overlay]').length,
        directions:[...svg.querySelectorAll('.semantic-lens-flow')].map(n=>n.getAttribute('data-direction')),
        status:document.getElementById('semantic-lens-status').textContent,
        buttons:[...document.querySelectorAll('#semantic-lens-kinds button')].map(n=>({kind:n.dataset.kind,disabled:n.disabled,pressed:n.getAttribute('aria-pressed'),label:n.getAttribute('aria-label')})),
        owner:Archify.motionGovernor.owner(),errors:lensErrors,external:performance.getEntriesByType('resource').map(e=>e.name).filter(n=>/^https?:/.test(n))};
    })()`);
    assert.deepEqual(state.errors, [], scenario); assert.deepEqual(state.external, [], scenario);
    records.push({ scenario, ...state }); return state;
  }
  async function hash(value) {
    await run(`new Promise(resolve=>{addEventListener('hashchange',()=>resolve(),{once:true});location.hash=${JSON.stringify(value)};})`);
  }

  await t.test('five modes and optional legend initialization preserve counts, roles and embed boundaries', async () => {
    for (const mode of Object.keys(cases)) {
      await load(mode); const state = await snapshot(mode + '-initial');
      assert.equal(state.active, null); assert.equal(state.open, false);
      assert.deepEqual(await run('Object.keys(Archify.semanticLens).sort()'), ['active', 'clear', 'clearPreview', 'close', 'copyLink', 'isOpen', 'kinds', 'open', 'select', 'toggle']);
      assert.deepEqual(state.buttons.map(n => n.kind), state.kinds.map(n => n.id));
      assert.ok(state.kinds.every((k, i, a) => i === 0 || a[i - 1].count >= k.count));
      assert.equal(await run(`(()=>{const svg=document.querySelector('.diagram-container > svg');return Archify.semanticLens.kinds().reduce((n,k)=>n+k.count,0)===new Set([...svg.querySelectorAll('[data-node-id][data-node-kind]')].map(n=>n.dataset.nodeId).filter(Boolean)).size;})()`), true);
    }
    for (const mode of ['absent', 'small', 'zero']) {
      await load(mode);
      const facts = await run(`(()=>{const b=document.querySelector('[data-legend-bridge]'),z=document.querySelector('[data-legend-zero]');return {role:b?.getAttribute('role')||null,buttons:b?.querySelectorAll('[role="button"]').length||0,zero:z?{count:z.dataset.legendCount,role:z.getAttribute('role'),hit:!!z.querySelector('[data-legend-hit]'),badge:!!z.querySelector('[data-legend-count-badge]')}:null};})()`);
      if (mode === 'absent') assert.equal(facts.role, null);
      if (mode === 'small') { assert.equal(facts.role, 'group'); assert.equal(facts.buttons, 2); }
      if (mode === 'zero') assert.deepEqual(facts.zero, { count: '0', role: null, hit: true, badge: true });
      assert.equal(await run(`Archify.semanticLens.select('backend')`), true); await snapshot(mode);
    }
    await load('architecture', { suffix: '&embed=1#lens=backend' });
    assert.equal(await run('Archify.semanticLens.open()'), false);
    assert.equal(await run(`document.querySelectorAll('[data-legend-bridge-runtime]').length`), 0);
    assert.deepEqual((await snapshot('embed-hash')).active, ['backend']);
    assert.equal((await snapshot('embed-flow')).overlays, 0);
  });

  await t.test('trusted buttons, selection transitions, return values and three cleanup operations stay distinct', async () => {
    await load(); await click('#btn-semantic-lens');
    await run(`lensWait(()=>document.activeElement.matches('#semantic-lens-kinds button'))`);
    await click('#semantic-lens-kinds [data-kind="backend"]');
    let s = await snapshot('one-kind'); assert.deepEqual(s.active, ['backend']); assert.equal(s.open, true);
    assert.deepEqual(s.selected, ['api', 'worker']); assert.ok(s.peers.length > 0); assert.ok(s.edges.length > 0);
    await click('#semantic-lens-kinds [data-kind="database"]');
    s = await snapshot('two-kinds'); assert.deepEqual(s.active, ['backend', 'database']);
    assert.ok(s.buttons.filter(n => !s.active.includes(n.kind)).every(n => n.disabled));
    assert.equal(await run('Archify.semanticLens.clearPreview()===undefined'), true);
    s = await snapshot('selection-after-clear-preview'); assert.deepEqual(s.active, ['backend', 'database']); assert.equal(s.open, true); assert.equal(s.hash, '#lens=backend~database');
    assert.equal(await run(`Archify.semanticLens.select('cloud')`), false);
    assert.equal(await run(`Archify.semanticLens.select('unknown')`), false);
    assert.equal(await run(`(()=>{const a=Archify.semanticLens.active();a.push('fake');return Archify.semanticLens.active().length;})()`), 2);
    const close = await run(`(()=>{const p=Archify.semanticLens,h=location.hash,o=document.querySelector('[data-semantic-lens-overlay]');return {value:p.close(),hash:h===location.hash,overlay:o===document.querySelector('[data-semantic-lens-overlay]'),focus:document.activeElement.id};})()`);
    assert.deepEqual(close, { value: false, hash: true, overlay: true, focus: 'btn-semantic-lens' });
    assert.equal((await snapshot('closed-selected')).open, false);
    assert.equal(await run(`Archify.semanticLens.select('database')`), true);
    await run('Archify.view.zoomIn()'); const zoom = await run('Archify.view.state()');
    assert.equal(await run(`Archify.semanticLens.select('backend')`), false);
    assert.deepEqual(await run('Archify.view.state()'), zoom);
    await run(`Archify.semanticLens.select('backend');Archify.semanticLens.open()`);
    await run(`lensWait(()=>document.activeElement.matches('#semantic-lens-kinds button'))`);
    await run('Archify.view.zoomIn()'); const beforeClear = await run('Archify.view.state()');
    assert.equal(await run(`Archify.semanticLens.clear({preserveView:true,updateUrl:false})`), false);
    s = await snapshot('clear-preserve'); assert.equal(s.active, null); assert.equal(s.open, true); assert.equal(s.hash, '#lens=backend');
    assert.deepEqual(await run('Archify.view.state()'), beforeClear);
    await run(`Archify.semanticLens.clear({closePanel:true})`);
    s = await snapshot('clear-default'); assert.equal(s.open, false); assert.equal(s.hash, ''); assert.equal(s.overlays, 0);
    assert.equal(await run('Archify.view.state().scale'), 1);
    await move(legend('backend')); assert.equal((await snapshot('preview-only')).preview, 'backend');
    assert.equal(await run(`Archify.semanticLens.clear({preserveView:true});document.querySelector('.diagram-container > svg').getAttribute('data-legend-preview-active')`), 'backend');
    assert.equal(await run('Archify.semanticLens.clearPreview()===undefined'), true);
    assert.equal((await snapshot('preview-cleared')).preview, null);
    // Retained hover reference is reused by the original focusout sync path.
    await run(`document.querySelector(${JSON.stringify(legend('database'))}).focus();document.activeElement.blur()`);
    assert.equal((await snapshot('retained-hover')).preview, 'backend');
    assert.equal(await run(`Archify.semanticLens.select('unknown')`), false);
    assert.equal((await snapshot('invalid-clears-preview')).preview, null);
  });

  await t.test('native legend focus, pointer transitions, keyboard activation and opener restoration', async () => {
    await load(); await move(legend('backend'));
    let s = await snapshot('legend-hover'); assert.equal(s.preview, 'backend'); assert.equal(s.active, null); assert.equal(s.overlays, 0); assert.equal(s.hash, '');
    await run(`document.querySelector(${JSON.stringify(legend('database'))}).focus()`);
    await move(legend('cloud')); assert.equal((await snapshot('focus-preferred')).preview, 'database');
    await run('document.activeElement.blur()'); assert.equal((await snapshot('hover-fallback')).preview, 'cloud');
    const internal = await run(`(()=>{const e=document.querySelector(${JSON.stringify(legend('cloud'))});e.dispatchEvent(new PointerEvent('pointerout',{bubbles:true,pointerType:'mouse',relatedTarget:e.querySelector('text')}));return document.querySelector('.diagram-container > svg').getAttribute('data-legend-preview-active');})()`);
    assert.equal(internal, 'cloud');
    await move(); await run(`document.querySelector('[data-legend-kind][role="button"]').focus()`);
    const entries = await run(`Array.from(document.querySelectorAll('[data-legend-kind][role="button"]'),n=>n.dataset.legendKind)`);
    await key('End', 'End', 35); assert.equal(await run('document.activeElement.dataset.legendKind'), entries.at(-1));
    await key('ArrowRight', 'ArrowRight', 39); assert.equal(await run('document.activeElement.dataset.legendKind'), entries[0]);
    await key('ArrowLeft', 'ArrowLeft', 37); assert.equal(await run('document.activeElement.dataset.legendKind'), entries.at(-1));
    await key('Home', 'Home', 36); assert.equal(await run('document.activeElement.dataset.legendKind'), entries[0]);
    assert.equal(await run(`document.querySelectorAll('[data-legend-kind][tabindex="0"]').length`), 1);
    await key('Enter', 'Enter', 13);
    await run(`lensWait(()=>document.activeElement.matches('#semantic-lens-kinds button'))`);
    assert.deepEqual((await snapshot('legend-enter')).active, [entries[0]]);
    await key('Escape', 'Escape', 27); assert.equal(await run('document.activeElement.dataset.legendKind'), entries[0]);
    await key(' ', 'Space', 32); await run(`lensWait(()=>document.activeElement.matches('#semantic-lens-kinds button'))`);
    s = await snapshot('legend-space-toggle'); assert.equal(s.active, null); assert.equal(s.open, true);
    await key('Enter', 'Enter', 13); assert.ok((await snapshot('panel-native-enter')).active);
    // Selection rebuilds buttons and removes the focused target; refocus the new button.
    await run(`document.querySelector('#semantic-lens-kinds [aria-pressed="true"]').focus()`);
    await key(' ', 'Space', 32);
    assert.equal((await snapshot('panel-native-space')).active, null);
    await run(`Archify.semanticLens.clear({closePanel:true,preserveView:true});Archify.semanticLens.select('backend');Archify.semanticLens.select('database');`);
    await click(legend('cloud'));
    s = await snapshot('third-legend-opens'); assert.deepEqual(s.active, ['backend', 'database']); assert.equal(s.open, true);
    await run(`lensWait(()=>document.activeElement.matches('#semantic-lens-kinds button'))`); await key('Escape', 'Escape', 27);
    assert.equal(await run('document.activeElement.dataset.legendKind'), 'cloud');
    for (const mode of ['architecture', 'coarse']) {
      await load(mode);
      await run(`document.querySelector(${JSON.stringify(legend('backend'))}).dispatchEvent(new PointerEvent('pointerover',{bubbles:true,pointerType:${JSON.stringify(mode === 'coarse' ? 'mouse' : 'touch')}}))`);
      assert.equal((await snapshot(mode + '-filtered-pointer')).preview, null);
    }
  });

  await t.test('minimal SVG fixtures preserve node indexing, grouped directions, first-member geometry and 24/25 threshold', async () => {
    await load();
    const data = await run(`(()=>{
      const svg=document.querySelector('.diagram-container > svg'),p=Archify.semanticLens;
      svg.innerHTML='<g data-edge-from="a" data-edge-to="b" data-edge-key="ab" transform="translate(3 4)"><path id="source-path" d="M0 0 L10 10" class="author" style="opacity:.7" marker-end="url(#arrow)"/><line x1="1" y1="2" x2="3" y2="4"/></g><path data-edge-from="a" data-edge-to="b" data-edge-key="ab" d="M1 1 L2 2"/><polyline data-edge-from="b" data-edge-to="a" points="1,2 3,4"/><path data-edge-from="b" data-edge-to="a" d="M3 3 L5 5"/><path data-edge-from="a" data-edge-to="a" d="M0 0 C1 2 3 4 0 0"/><g data-node-id="a" data-node-kind="backend"/><g data-node-id="b" data-node-kind="database"/><g data-node-id="a" data-node-kind="ignored"/><g data-node-id="missing-kind"/><g data-node-id="" data-node-kind="ignored"/><g data-node-id="solo" data-node-kind=""/>';
      const before=svg.querySelector('#source-path').outerHTML;p.select('backend');
      const overlay=svg.querySelector('[data-semantic-lens-overlay]'),shapes=[...overlay.querySelectorAll('.semantic-lens-flow')];
      const result={kinds:p.kinds(),single:{count:svg.dataset.lensFlowCount,matched:svg.querySelectorAll('[data-edge-from][data-lens-match]').length,directions:shapes.map(n=>n.dataset.direction),status:document.getElementById('semantic-lens-status').textContent},
        geometry:{transform:overlay.firstElementChild.getAttribute('transform'),d:shapes[0].getAttribute('d'),points:shapes[2].getAttribute('points'),delays:shapes.map(n=>n.style.getPropertyValue('--lens-flow-delay')),stripped:!overlay.querySelector('[id],[marker-end],[data-edge-from]'),normalized:shapes.every(n=>n.getAttribute('pathLength')==='1'),unchanged:before===svg.querySelector('#source-path').outerHTML,beforeNode:overlay.nextElementSibling.hasAttribute('data-node-id')}};
      p.select('database');result.double={count:svg.dataset.lensFlowCount,directions:[...svg.querySelectorAll('.semantic-lens-flow')].map(n=>n.dataset.direction),peers:svg.querySelectorAll('[data-lens-peer]').length,status:document.getElementById('semantic-lens-status').textContent};
      p.clear({preserveView:true});p.select('database');p.select('backend');result.reverse=[...svg.querySelectorAll('.semantic-lens-flow')].map(n=>n.dataset.direction);
      p.clear({preserveView:true});p.select('neutral');p.select('backend');result.zero={count:svg.dataset.lensFlowCount,overlays:svg.querySelectorAll('[data-semantic-lens-overlay]').length};return result;
    })()`);
    assert.deepEqual(data.kinds.map(n => n.id).sort(), ['backend', 'database', 'neutral']);
    assert.ok(data.kinds.every(n => n.count === 1));
    assert.equal(data.single.count, '3'); assert.equal(data.single.matched, 5);
    assert.deepEqual(data.single.directions, ['out', 'out', 'in', 'within']); assert.match(data.single.status, /3 touching relationships/);
    assert.deepEqual(data.geometry, { transform: 'translate(3 4)', d: 'M0 0 L10 10', points: '1,2 3,4', delays: ['0.00s', '0.00s', '0.08s', '0.16s'], stripped: true, normalized: true, unchanged: true, beforeNode: true });
    assert.equal(data.double.count, '2'); assert.equal(data.double.peers, 0);
    assert.deepEqual(data.double.directions, ['forward', 'forward', 'reverse']);
    assert.deepEqual(data.reverse, ['reverse', 'reverse', 'forward']); assert.deepEqual(data.zero, { count: '0', overlays: 0 });
    records.push({ scenario: 'geometry-fixture', ...data });
    for (const count of [24, 25, 24, 0]) {
      await run(`(()=>{const svg=document.querySelector('.diagram-container > svg');Archify.semanticLens.clear({preserveView:true});svg.innerHTML=Array.from({length:${count}},(_,i)=>'<path data-edge-from="a" data-edge-to="b" data-edge-key="e'+i+'" d="M0 0 L10 10"/>').join('')+'<g data-node-id="a" data-node-kind="backend"/><g data-node-id="b" data-node-kind="database"/>';Archify.semanticLens.select('backend');})()`);
      const s = await snapshot('threshold-' + count); assert.equal(s.count, String(count)); assert.equal(s.edges.length, count);
      assert.equal(s.overlays, count > 0 && count <= 24 ? 1 : 0); assert.equal(s.density, count > 24 ? 'quiet' : null);
    }
    await run(`(()=>{const svg=document.querySelector('.diagram-container > svg');Archify.semanticLens.clear({preserveView:true});svg.innerHTML='<g data-edge-from="a" data-edge-to="b"><text>No shape</text></g><g data-node-id="a" data-node-kind="backend"/><g data-node-id="b" data-node-kind="database"/>';Archify.semanticLens.select('backend');})()`);
    const s = await snapshot('no-shape'); assert.equal(s.count, '1'); assert.equal(s.overlays, 0);
  });

  await t.test('initial URL, hashchange and controlled clipboard boundaries retain normalization and feedback', async () => {
    await load('architecture', { suffix: '&keep=yes#lens=database~database~unknown~backend~cloud' });
    let s = await snapshot('initial-hash'); assert.deepEqual(s.active, ['database', 'backend']); assert.equal(s.open, false);
    assert.equal(s.hash, '#lens=database~database~unknown~backend~cloud');
    await hash('#lens=unknown'); assert.deepEqual((await snapshot('invalid-hash')).active, ['database', 'backend']);
    await hash('#lens='); assert.equal((await snapshot('empty-hash')).active, null);
    await hash('#lens=backend'); await hash('#unrelated=1'); assert.equal((await snapshot('missing-hash')).active, null);
    assert.equal(await run('Archify.semanticLens.copyLink()'), false);
    await run(`Archify.semanticLens.select('backend',{updateUrl:false})`);
    assert.equal(await run('location.hash'), '#unrelated=1');
    await run(`Archify.semanticLens.select('database')`); assert.equal(await run('location.search'), '?theme=dark&keep=yes');
    // Never touch the host clipboard: replace both the preferred API and fallback.
    for (const mode of ['success', 'reject', 'absent', 'failure', 'throw']) {
      const copied = await run(`(async()=>{
        const descriptor=Object.getOwnPropertyDescriptor(navigator,'clipboard'),exec=document.execCommand;let captured,commands=0;
        const expected=location.href.replace(/#.*$/,'')+'#lens=backend~database';
        Object.defineProperty(navigator,'clipboard',{configurable:true,value:${mode === 'success' ? "{writeText:v=>{captured=v;return Promise.resolve();}}" : mode === 'reject' ? "{writeText:()=>Promise.reject(new Error('fixture'))}" : 'undefined'}});
        document.execCommand=command=>{commands++;captured=document.activeElement.value;if(${JSON.stringify(mode)}==='throw')throw new Error('fixture');return ${JSON.stringify(mode)}!=='failure';};
        try {const value=await Archify.semanticLens.copyLink();return {value,commands,correct:captured===expected,fields:document.querySelectorAll('textarea[readonly]').length,text:document.getElementById('semantic-lens-copy').textContent};}
        finally {document.execCommand=exec;if(descriptor)Object.defineProperty(navigator,'clipboard',descriptor);else delete navigator.clipboard;}
      })()`);
      assert.equal(copied.value, !['failure', 'throw'].includes(mode)); assert.equal(copied.commands, mode === 'success' ? 0 : 1);
      assert.equal(copied.correct, true); assert.equal(copied.fields, 0);
      assert.match(copied.text, copied.value ? /Copied/ : /Copy failed/i);
      await run(`lensWait(()=>document.getElementById('semantic-lens-copy').textContent==='Copy link')`);
      records.push({ scenario: 'copy-' + mode, ...copied });
    }
    await snapshot('copy-feedback-restored');
  });

  await t.test('actual capability handoffs and blocked previews preserve existing ownership', async () => {
    for (const [name, action] of [
      ['focus', `Archify.focus.set('api',{toggle:false})`],
      ['route', `Archify.routeProbe.begin({source:'users'})`],
      ['guided', `Archify.guidedViews.activate('request-path');await lensWait(()=>!Archify.guidedViews.handoff())`],
      ['intent', `Archify.intentTrace.show('api',{announce:true})`],
    ]) {
      await load('trace'); await run(`(async()=>{${action}})()`); await run(`Archify.semanticLens.select('backend')`);
      const result = await run(`({focus:Archify.focus.active(),route:Archify.routeProbe.active(),intent:Archify.intentTrace.active(),guided:Archify.guidedViews.active()})`);
      assert.deepEqual(result, { focus: null, route: null, intent: null, guided: null });
      await run(`lensWait(()=>Archify.motionGovernor.owner()==='lens')`); await snapshot(name + '-to-lens');
    }
    await run('Archify.semanticLens.open();Archify.finder.open()');
    assert.equal(await run('Archify.semanticLens.isOpen()'), false); assert.equal(await run('Archify.finder.isOpen()'), true);
    await run('Archify.semanticLens.open();Archify.guide.open()');
    assert.equal(await run('Archify.semanticLens.isOpen()'), false); await snapshot('lens-to-guide');
    await load();
    const blockers = await run(`(()=>{
      const svg=document.querySelector('.diagram-container > svg'),html=document.documentElement,e=document.querySelector(${JSON.stringify(legend('backend'))});
      return [[html,'data-present'],[svg,'data-focus-active'],[svg,'data-intent-trace-active'],[svg,'data-route-picking'],[svg,'data-route-active'],[svg,'data-story-active'],[svg,'data-relationship-preview-active']].map(([el,a])=>{
        el.setAttribute(a,'true');e.dispatchEvent(new PointerEvent('pointerover',{bubbles:true,pointerType:'mouse'}));const preview=svg.getAttribute('data-legend-preview-active');e.dispatchEvent(new PointerEvent('pointerout',{bubbles:true,pointerType:'mouse'}));el.removeAttribute(a);return {attribute:a,preview};});
    })()`);
    assert.ok(blockers.every(row => row.preview === null)); records.push({ scenario: 'blocker-fixture', blockers });
  });

  await t.test('panel events, pending frames and controlled docking geometry preserve responsive branches', async () => {
    await load(); await key('l', 'KeyL', 76);
    await run(`lensWait(()=>document.activeElement.matches('#semantic-lens-kinds button'))`);
    await click('#semantic-lens-kinds [data-kind="backend"]'); assert.equal(await run('Archify.semanticLens.isOpen()'), true);
    await click('h1'); assert.equal(await run('Archify.semanticLens.isOpen()'), false);
    await click('#btn-semantic-lens'); await click('#btn-semantic-lens'); assert.equal(await run('Archify.semanticLens.isOpen()'), false);
    const quick = await run(`new Promise(resolve=>{const p=Archify.semanticLens;const values=[p.open(),p.open(),p.close({restoreFocus:false})];requestAnimationFrame(()=>resolve({values,open:p.isOpen(),dock:document.getElementById('semantic-lens').getAttribute('data-dock-side')}));})`);
    assert.deepEqual(quick, { values: [true, true, false], open: false, dock: null });
    // Only measured rectangle inputs are overridden; public open/select/resize drive docking.
    const docking = await run(`(async()=>{
      const panel=document.getElementById('semantic-lens'),svg=document.querySelector('.diagram-container > svg'),container=svg.parentElement,nav=container.querySelector('.diagram-nav'),legend=svg.querySelector('[data-legend]'),node=svg.querySelector('[data-node-id="api"]');
      const rect=(left,top,width,height)=>({left,top,width,height,right:left+width,bottom:top+height});
      const elements=[panel,container,nav,legend,node].filter(Boolean),saved=elements.map(e=>Object.getOwnPropertyDescriptor(e,'getBoundingClientRect'));
      let position=700;panel.getBoundingClientRect=()=>rect(700,100,200,200);container.getBoundingClientRect=()=>rect(0,0,1000,800);
      [nav,legend].filter(Boolean).forEach(e=>e.getBoundingClientRect=()=>rect(0,600,100,20));node.getBoundingClientRect=()=>rect(position,100,200,200);
      try {Archify.semanticLens.open();await new Promise(requestAnimationFrame);const sides=[];for(const x of [700,16,400]){position=x;dispatchEvent(new Event('resize'));sides.push(panel.getAttribute('data-dock-side'));}return sides;}
      finally {elements.forEach((e,i)=>{if(saved[i])Object.defineProperty(e,'getBoundingClientRect',saved[i]);else delete e.getBoundingClientRect;});}
    })()`);
    assert.deepEqual(docking, ['left', 'right', 'right']); records.push({ scenario: 'docking-fixture', sides: docking });
    for (const width of [720, 721, 1440]) {
      await send('Emulation.setDeviceMetricsOverride', { width, height: 900, deviceScaleFactor: 1, mobile: false });
      await run(`lensWait(()=>innerWidth===${width}).then(()=>Archify.viewerChromeLayout.whenStable())`);
      const dock = await run(`document.getElementById('semantic-lens').getAttribute('data-dock-side')`);
      if (width === 720) assert.equal(dock, null); else assert.ok(['left', 'right'].includes(dock));
      assert.equal(await run(`Array.from(document.querySelectorAll('[data-legend-hit]'),n=>Number(n.getAttribute('width'))).every(w=>w>=24)`), true);
      records.push({ scenario: 'width-' + width, dock });
    }
  });

  await t.test('themes, reduced motion, Still and real SVG export retain selection and preview rendering', async () => {
    for (const theme of ['dark', 'light']) {
      for (const state of ['selection', 'preview']) {
        await load('trace', { theme, reduced: true });
        if (state === 'selection') await run(`Archify.semanticLens.select('backend')`); else await move(legend('backend'));
        await run(`lensWait(()=>Archify.motionGovernor.owner()===${JSON.stringify(state === 'selection' ? 'lens' : 'legend')})`);
        const s = await snapshot(theme + '-' + state); assert.equal(state === 'selection' ? s.active[0] : s.preview, 'backend');
        if (state === 'selection') {
          const style = await run(`(()=>{const n=document.querySelector('.semantic-lens-flow'),c=getComputedStyle(n);return {animation:c.animationName,pointer:getComputedStyle(n.parentElement.parentElement).pointerEvents};})()`);
          assert.deepEqual(style, { animation: 'none', pointer: 'none' });
        }
        if (evidence) {
          await run(`Promise.all(document.getAnimations().filter(a=>Number.isFinite(a.effect.getTiming().iterations)).map(a=>a.finished.catch(()=>{})))`);
          const shot = await send('Page.captureScreenshot', { format: 'png' });
          fs.writeFileSync(path.join(evidence, theme + '-' + state + '.png'), Buffer.from(shot.data, 'base64'));
        }
        const exported = await run(`(async()=>{
          const original=URL.createObjectURL;let blob;URL.createObjectURL=function(v){if(v.type.startsWith('image/svg+xml'))blob=v;return original.call(URL,v);};
          try {await Archify.exportMenu.run('svg');}finally{URL.createObjectURL=original;}
          const root=new DOMParser().parseFromString(await blob.text(),'image/svg+xml').documentElement;
          return {clean:!root.hasAttribute('data-lens-active')&&!root.hasAttribute('data-legend-preview-active')&&!root.querySelector('[data-semantic-lens-overlay],[data-lens-match],[data-lens-selected],[data-lens-peer],[data-legend-preview-match],[data-legend-bridge-runtime],[data-legend-count]'),viewBox:root.getAttribute('viewBox')===document.querySelector('.diagram-container > svg').getAttribute('viewBox')};
        })()`);
        assert.deepEqual(exported, { clean: true, viewBox: true });
      }
    }
    await load('trace'); await run(`Archify.semanticLens.select('backend')`);
    const animation = await run(`getComputedStyle(document.querySelector('.semantic-lens-flow')).animationName`);
    assert.equal(animation, 'archify-semantic-lens-flow');
    await run(`lensWait(()=>lensEnds.some(e=>e.trusted&&e.name==='archify-semantic-lens-flow'))`);
    assert.deepEqual((await snapshot('animation-finished')).active, ['backend']);
    await run(`Archify.motionGovernor.setMode('still')`);
    await run(`lensWait(()=>getComputedStyle(document.querySelector('.semantic-lens-flow')).animationName==='none')`);
    assert.deepEqual((await snapshot('still-selection')).active, ['backend']);
  });
});
```

## test/semantic-lens.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-semantic-lens-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers inherit one viewer-only Semantic Lens', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="semantic-lens" hidden role="dialog" aria-modal="false" aria-labelledby="semantic-lens-title"/, mode);
    assert.match(html, /id="btn-semantic-lens"[^>]+aria-label="Open semantic lens"[^>]+aria-expanded="false"[^>]+aria-controls="semantic-lens"/, mode);
    assert.match(html, /Archify\.semanticLens = \(function \(\)/, mode);
    assert.match(html, /svg\.querySelectorAll\('\[data-node-id\]\[data-node-kind\]'\)/, mode);
    assert.doesNotMatch(canonicalSvg(html), /semantic-lens-overlay|data-lens-active|data-lens-match/, mode);
  }
});

test('Semantic Lens derives honest kind counts and compares at most two roles', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /function collectKinds\(\)/);
  assert.match(html, /kind\.nodes\.push\(node\)/);
  assert.match(html, /Choose up to two semantic kinds/);
  assert.match(html, /if \(selectedKinds\.length >= 2\) return false/);
  assert.match(html, /var crossKind = selectedKinds\.length === 2/);
  assert.match(html, /fromKind === selectedKinds\[0\] && toKind === selectedKinds\[1\]/);
  assert.match(html, /fromKind === selectedKinds\[1\] && toKind === selectedKinds\[0\]/);
  assert.match(html, /direct relationship/);
  assert.match(html, /data-lens-peer/);
  assert.match(html, /data-lens-selected/);
});

test('Semantic Lens is shareable and yields cleanly to stronger reader intent', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /#lens=/);
  assert.match(html, /params\.get\('lens'\)/);
  assert.match(html, /window\.addEventListener\('hashchange', syncFromHash\)/);
  assert.match(html, /event\.composedPath\(\)/);
  assert.match(html, /eventPath\.indexOf\(panel\) >= 0/);
  assert.match(html, /Archify\.semanticLens\.clear\(\{ updateUrl: false/);
  assert.match(html, /Archify\.focus\.clear\(\{ updateUrl: false, preserveView: true \}\)/);
  assert.match(html, /Archify\.routeProbe\.clear\(\{ updateUrl: false, restoreFocus: false \}\)/);
  assert.match(html, /Archify\.guidedViews\.showAll\(\{ clearFocus: false, updateUrl: false \}\)/);
  assert.match(html, /if \(action === 'lens'\) return Archify\.semanticLens\.open\(\)/);
  assert.match(html, /e\.key === 'l' \|\| e\.key === 'L'/);
  assert.match(html, /e\.key === 'Escape' && Archify\.semanticLens\.isOpen\(\)/);
  assert.match(html, /e\.key === 'Escape' && Archify\.semanticLens\.active\(\)/);
});

test('Semantic Lens preserves Reading Depth, mobile containment, print, embed, and export boundaries', () => {
  const html = render('dataflow', CASES.dataflow);
  assert.match(html, /svg\[data-lens-active\] \[data-lens-match\] \[data-detail\]/);
  assert.match(html, /svg\[data-lens-active\] \[data-lens-match\] \[data-detail-anchor\]/);
  assert.match(html, /html\[data-embed="true"\] \.semantic-lens/);
  assert.match(html, /data-wide-diagram="true"\] \.semantic-lens/);
  assert.match(html, /@media print \{[\s\S]+svg\[data-lens-active\] \[data-node-id\][\s\S]+opacity: 1 !important/);
  assert.match(html, /clone\.removeAttribute\('data-lens-active'\)/);
  assert.match(html, /\[data-lens-match\], \[data-lens-selected\], \[data-lens-peer\]/);
  assert.match(html, /clone\.querySelectorAll\('[^']*\[data-lens-match\][^']*\[data-lens-selected\][^']*\[data-lens-peer\][^']*'\)\.length === 0/);
  assert.match(html, /class="semantic-lens no-print"/);
  assert.doesNotMatch(canonicalSvg(html), /data-lens-active|data-lens-match|data-lens-selected|data-lens-peer/);
});

test('Semantic Lens docks away from selected nodes without breaking mobile containment', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /function overlapArea\(a, b\)/);
  assert.match(html, /function dockPanel\(byId\)/);
  assert.match(html, /selectedKinds\.indexOf\(byId\[id\]\.getAttribute\('data-node-kind'\)/);
  assert.match(html, /var side = leftScore < rightScore \? 'left' : 'right'/);
  assert.match(html, /panel\.setAttribute\('data-dock-side', side\)/);
  assert.match(html, /\.semantic-lens\[data-dock-side="left"\]/);
  assert.match(html, /@media \(max-width: 720px\)[\s\S]+\.semantic-lens\[data-dock-side\] \{ left: auto; right: 0\.5rem; \}/);
  assert.match(html, /window\.addEventListener\('resize'/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/semantic-passport.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-semantic-passport-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function svg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers emit details-on-demand metadata and native SVG titles', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    const diagram = svg(html);
    assert.match(diagram, /data-node-kind="[^"]+"/, mode);
    assert.match(diagram, /data-node-sublabel="[^"]+"/, mode);
    assert.match(diagram, /data-node-context="[^"]+"/, mode);
    assert.match(diagram, /<g id="node-[^"]+"[\s\S]*?<title>[^<]+ · [^<]+<\/title>/, mode);
  }
});

test('renderer-owned structure supplies truthful Semantic Passport context', () => {
  const architecture = render('architecture', CASES.architecture);
  const workflow = render('workflow', CASES.workflow);
  const sequence = render('sequence', CASES.sequence);
  const dataflow = render('dataflow', CASES.dataflow);
  const lifecycle = render('lifecycle', CASES.lifecycle);

  assert.match(architecture, /data-node-id="api"[^>]+data-node-kind="backend"[^>]+data-node-context="AWS Region: us-west-2 › sg-api :443\/:8000"/);
  assert.match(workflow, /data-node-id="approval"[^>]+data-node-kind="security"[^>]+data-node-context="Policy &amp; Recovery › Human or policy stop › Plan \+ route"/);
  assert.match(sequence, /data-node-id="redis"[^>]+data-node-kind="database"[^>]+data-node-context="Sequence participant"/);
  assert.match(dataflow, /data-node-id="warehouse"[^>]+data-node-kind="database"[^>]+data-node-context="04 \/ Store"/);
  assert.match(lifecycle, /data-node-id="executing"[^>]+data-node-kind="active"[^>]+data-node-context="Lifecycle phases"/);
});

test('Relationship Lens renders one Semantic Passport and copyable stable focus link', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /<span class="relationship-lens-eyebrow">Semantic passport<\/span>/);
  assert.match(html, /id="focus-detail" hidden/);
  assert.match(html, /id="focus-kind" data-passport="kind"/);
  assert.match(html, /id="focus-context" data-passport="context" hidden/);
  assert.match(html, /id="focus-tag" data-passport="tag" hidden/);
  assert.match(html, /id="focus-id" data-passport="id"/);
  assert.match(html, /id="btn-focus-clear"[^>]+aria-label="Close semantic passport"[^>]+title="Close">&#215;<\/button>/);
  assert.match(html, /id="btn-focus-copy"[^>]+aria-label="Copy link to focused node"/);
  assert.match(html, /id="btn-focus-relations"[^>]+aria-expanded="false"[^>]+aria-controls="relationship-lens-list"/);
  assert.match(html, /function renderPassport\(id, node\)/);
  assert.match(html, /var relationId = record && record\.id/);
  assert.match(html, /\? '#relation=' \+ encodeURIComponent\(relationId\)/);
  assert.match(html, /: '#focus=' \+ encodeURIComponent\(activeIds\[0\]\)/);
  assert.match(html, /navigator\.clipboard\.writeText\(value\)/);
  assert.match(html, /document\.execCommand\('copy'\)/);
  assert.match(html, /copyLink: copyFocusLink/);
  assert.match(html, /compactOnMobile = mobile && chip\.getAttribute\('data-relations-expanded'\) !== 'true'/);
  assert.match(html, /nodeTop - chip\.offsetHeight - gap/);
  assert.match(html, /focus-chip:not\(\[data-relations-expanded="true"\]\) \.relationship-lens-list \{ display: none; \}/);
  assert.match(html, /clearBtn\.addEventListener\('click', function \(\) \{ clear\(\{ restoreFocus: true \}\); \}\)/);
  assert.match(html, /chip\.hidden \|\| !target \|\| typeof target\.closest !== 'function' \|\| chip\.contains\(target\)/);
  assert.match(html, /target\.closest\('\[data-node-id\], \[data-relationship-hit-key\], \.overview-map'\)/);
  assert.match(html, /document\.addEventListener\('click',[\s\S]+?clear\(\);\s+\}, true\);/);
  assert.match(html, /Archify\.focus\.clear\(\{ restoreFocus: true \}\)/);
});

test('Node Finder searches and presents the same passport facts', () => {
  const html = render('dataflow', CASES.dataflow);
  assert.match(html, /var authored = node\.getAttribute\('data-node-kind'\)/);
  assert.match(html, /var sublabel = node\.getAttribute\('data-node-sublabel'\) \|\| ''/);
  assert.match(html, /var context = node\.getAttribute\('data-node-context'\) \|\| ''/);
  assert.match(html, /var tag = node\.getAttribute\('data-node-tag'\) \|\| ''/);
  assert.match(html, /search: \(id \+ ' ' \+ label \+ ' ' \+ type \+ ' ' \+ sublabel \+ ' ' \+ context \+ ' ' \+ tag \+ ' ' \+ sourceSearch \+ ' ' \+ text\)\.toLowerCase\(\)/);
  assert.match(html, /\[viewerKindLabel\(item\.type\), item\.id, item\.sublabel, item\.tag\]\.filter\(Boolean\)\.join\(' \\u00b7 '\)/);
  assert.match(html, /meta\.title = \[viewerKindLabel\(item\.type\), item\.id, item\.context, item\.sublabel, item\.tag\]\.filter\(Boolean\)\.join\(' \\u00b7 '\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/semantic-radar.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-semantic-radar-'));
const chromePath = process.env.ARCHIFY_CHROME ? findChrome() : null;

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

async function evaluate(browser, sessionId, expression, awaitPromise = false) {
  const response = await browser.cdp.send('Runtime.evaluate', {
    expression,
    awaitPromise,
    returnByValue: true,
  }, sessionId);
  if (response.exceptionDetails) {
    throw new Error(response.exceptionDetails.exception?.description
      || response.exceptionDetails.text
      || 'Runtime.evaluate failed');
  }
  return response.result?.value;
}

async function loadArtifact(browser, artifactPath, { width = 1440, height = 900 } = {}) {
  const sessionId = await browser.sessionPromise;
  await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
    width,
    height,
    deviceScaleFactor: 1,
    mobile: false,
  }, sessionId);
  const loaded = browser.cdp.waitFor('Page.loadEventFired', sessionId);
  const navigation = await browser.cdp.send('Page.navigate', {
    url: pathToFileURL(artifactPath).href,
  }, sessionId);
  if (navigation.errorText) throw new Error(`Chrome navigation failed: ${navigation.errorText}`);
  await loaded;
  await evaluate(browser, sessionId, `(function () {
    document.documentElement.setAttribute('data-motion', 'still');
    var fontsReady = document.fonts && document.fonts.ready
      ? document.fonts.ready.catch(function () {})
      : Promise.resolve();
    return fontsReady.then(function () {
      return new Promise(function (resolve) {
        requestAnimationFrame(function () { requestAnimationFrame(resolve); });
      });
    });
  })()`, true);
  return sessionId;
}

async function radarRects(browser, sessionId, setup) {
  return evaluate(browser, sessionId, `(function () {
    ${setup}
    return new Promise(function (resolve) {
      requestAnimationFrame(function () {
        requestAnimationFrame(function () {
          var radar = document.getElementById('overview-map').getBoundingClientRect();
          var controls = document.querySelector('.diagram-nav').getBoundingClientRect();
          var passport = document.getElementById('focus-chip');
          var passportRect = passport && !passport.hidden ? passport.getBoundingClientRect() : null;
          resolve({
            radar: { left: radar.left, top: radar.top, right: radar.right, bottom: radar.bottom },
            controls: { left: controls.left, top: controls.top, right: controls.right, bottom: controls.bottom },
            passport: passportRect ? {
              left: passportRect.left,
              top: passportRect.top,
              right: passportRect.right,
              bottom: passportRect.bottom
            } : null
          });
        });
      });
    });
  })()`, true);
}

async function dragMouse(browser, sessionId, from, to) {
  await browser.cdp.send('Input.dispatchMouseEvent', {
    type: 'mousePressed',
    x: from.x,
    y: from.y,
    button: 'left',
    buttons: 1,
    clickCount: 1,
  }, sessionId);
  await browser.cdp.send('Input.dispatchMouseEvent', {
    type: 'mouseMoved',
    x: to.x,
    y: to.y,
    button: 'left',
    buttons: 1,
  }, sessionId);
  await browser.cdp.send('Input.dispatchMouseEvent', {
    type: 'mouseReleased',
    x: to.x,
    y: to.y,
    button: 'left',
    buttons: 0,
    clickCount: 1,
  }, sessionId);
  await evaluate(browser, sessionId, `new Promise(function (resolve) {
    requestAnimationFrame(function () { requestAnimationFrame(resolve); });
  })`, true);
}

function overlaps(a, b, gap = 0) {
  return a.left < b.right + gap
    && a.right > b.left - gap
    && a.top < b.bottom + gap
    && a.bottom > b.top - gap;
}

test('all typed renderers inherit one viewer-only Semantic Radar', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="overview-map" hidden role="region" aria-labelledby="overview-map-title"/, mode);
    assert.match(html, /id="overview-map-surface" tabindex="0" role="group"/, mode);
    assert.match(html, /id="overview-map-expand"[^>]+aria-label="Open full semantic radar"/, mode);
    assert.match(html, /id="overview-map-feedback" role="status" aria-live="polite" hidden/, mode);
    assert.match(html, /id="btn-overview-map"[^>]+aria-label="Open semantic radar"[^>]+aria-expanded="false"[^>]+aria-controls="overview-map"/, mode);
    assert.match(html, /Archify\.radar = \(function \(\)/, mode);
    assert.match(html, /document\.createElementNS\(namespace, 'svg'\)/, mode);
    assert.match(html, /mapSvg\.setAttribute\('aria-label', viewerText\('viewer\.radar\.nodes'\)\)/, mode);
    assert.match(html, /diagram\.querySelectorAll\('\[data-node-id\]'\)/, mode);
    assert.equal((html.match(/<svg\b/g) || []).length, 1, `${mode} keeps one static canonical SVG`);
    assert.doesNotMatch(canonicalSvg(html), /overview-map|Semantic radar|data-radar-node-id/, mode);
  }
});

test('Semantic Radar derives semantic node bounds and focuses stable IDs', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /box = node\.getBBox\(\)/);
  assert.match(html, /rect\.setAttribute\('data-radar-node-id', id\)/);
  assert.match(html, /rect\.setAttribute\('data-kind', node\.getAttribute\('data-node-kind'\) \|\| 'neutral'\)/);
  assert.match(html, /rect\.setAttribute\('aria-label', viewerText\('viewer\.radar\.focus'/);
  assert.match(html, /Archify\.focus\.set\(id, \{ toggle: false \}\)/);
  assert.match(html, /Archify\.view\.reveal\(\[id\], \{ includeNeighbors: true, reason: 'radar' \}\)/);
  assert.match(html, /function bringNodeIntoWindow\(node\)/);
  assert.match(html, /window\.scrollY \+ rect\.top \+ rect\.height \/ 2 - window\.innerHeight \/ 2/);
  assert.match(html, /data-radar-active/);
});

test('Semantic Radar tracks desktop camera and mobile contained scroll', () => {
  const html = render('sequence', CASES.sequence);
  assert.match(html, /function logicalViewport\(\)/);
  assert.match(html, /x = viewBox\.x \+ container\.scrollLeft \/ metrics\.scale/);
  assert.match(html, /x = viewBox\.x \+ \(\(-state\.x \/ state\.scale\) - metrics\.offsetX\) \/ metrics\.scale/);
  assert.match(html, /viewport\.setAttribute\('width', String\(visible\.width\)\)/);
  assert.match(html, /viewerText\('viewer\.radar\.viewport\.width'/);
  assert.match(html, /function centerAt\(logicalX, logicalY, options\)/);
  assert.match(html, /minimumScale: 1\.5, instant: true/);
  assert.match(html, /container\.scrollTo\(\{ left: mobileTarget, behavior: options\.instant \? 'auto' : 'smooth' \}\)/);
  assert.match(html, /data-wide-diagram="true"\] \.overview-map/);
  assert.match(html, /function updateDocking\(\)/);
  assert.match(html, /chip\.style\.top = Math\.round\(top\) \+ 'px';[\s\S]+Archify\.radar\.sync\(\)/);
  assert.match(html, /var navigation = container\.querySelector\('\.diagram-nav'\)/);
  assert.match(html, /if \(controlRect\) bottom = Math\.min\(bottom, controlRect\.top - placementGap\)/);
  assert.match(html, /hardBlockers: \[lensRect, controlRect, legendRect\]\.filter\(Boolean\)/);
  assert.match(html, /function cornerCandidates\(context\)/);
  assert.match(html, /function nearbyCandidates\(context, reference\)/);
  assert.match(html, /nearbyCandidates\(context, reference\)\.concat\(cornerCandidates\(context\)\)/);
  assert.match(html, /var placementOptions = \{ softWeight: manualPosition \? 0 : 100 \}/);
  assert.match(html, /manualPosition && positionIsValid\(manualPosition, context\)/);
  assert.match(html, /panelHead\.addEventListener\('pointerdown', beginPanelDrag\)/);
  assert.match(html, /surface\.addEventListener\('pointerdown',[\s\S]+viewportDrag = \{ pointerId: event\.pointerId \}/);
  assert.match(html, /target\.closest\('\[data-node-id\], \[data-relationship-hit-key\], \.overview-map'\)/);
  assert.match(html, /--archify-radar-top/);
  assert.match(html, /\.overview-map\[data-docked="true"\]/);
});

test('Semantic Radar keeps redundant accessible navigation and clean exports', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /Semantic radar \(M\)/);
  assert.match(html, /e\.key === 'm' \|\| e\.key === 'M'/);
  assert.match(html, /e\.key === 'Escape' && Archify\.radar\.isOpen\(\)/);
  assert.match(html, /event\.key === 'ArrowLeft'[\s\S]+event\.key === 'ArrowRight'[\s\S]+event\.key === 'ArrowUp'[\s\S]+event\.key === 'ArrowDown'/);
  assert.match(html, /node && \(event\.key === 'Enter' \|\| event\.key === ' '\)/);
  assert.match(html, /\.overview-map-viewport \{[\s\S]*?pointer-events: none;/);
  assert.match(html, /html\[data-embed="true"\] \.overview-map/);
  assert.match(html, /class="overview-map no-print"/);
  assert.match(html, /The radar is built at runtime so the checked artifact still contains[\s\S]+one canonical SVG block/);
  assert.doesNotMatch(canonicalSvg(html), /overview-map-node|overview-map-viewport/);
});

test('Semantic Radar stays above the measured MAP control strip', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const artifact = path.join(tmp, 'radar-control-clearance.html');
  execFileSync(process.execPath, [
    path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
    path.join(skillRoot, 'examples', CASES.architecture),
    artifact,
  ]);
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await loadArtifact(browser, artifact, { width: 1440, height: 900 });
    const rects = await radarRects(browser, sessionId, `
      var container = document.querySelector('.diagram-container');
      window.scrollTo(0, Math.max(0, container.offsetTop + container.offsetHeight - window.innerHeight + 8));
      Archify.radar.open();
    `);
    const controlGap = rects.controls.top - rects.radar.bottom;
    assert.ok(controlGap >= 15, JSON.stringify({ ...rects, controlGap }, null, 2));
    assert.ok(
      rects.radar.left < rects.controls.left,
      `automatic placement should prefer the lower-left corner: ${JSON.stringify(rects, null, 2)}`,
    );
  } finally {
    await browser.close();
  }
});

test('Semantic Radar avoids an expanded mobile Passport without hiding a collision', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const artifact = path.join(tmp, 'radar-mobile-passport.html');
  execFileSync(process.execPath, [
    path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
    path.join(skillRoot, 'examples', CASES.architecture),
    artifact,
  ]);
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await loadArtifact(browser, artifact, { width: 390, height: 600 });
    const state = await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      window.scrollTo(0, Math.max(0, container.offsetTop));
      Archify.focus.set('lb', { toggle: false });
      document.getElementById('btn-focus-relations').click();
      Archify.radar.open();
      return new Promise(function (resolve) {
        setTimeout(function () {
            var radar = document.getElementById('overview-map');
            var radarRect = radar.getBoundingClientRect();
            var passportRect = document.getElementById('focus-chip').getBoundingClientRect();
            var containerRect = document.querySelector('.diagram-container').getBoundingClientRect();
            var controlsRect = document.querySelector('.diagram-nav').getBoundingClientRect();
            var legendRect = document.querySelector('[data-legend]').getBoundingClientRect();
            resolve({
              radar: { left: radarRect.left, top: radarRect.top, right: radarRect.right, bottom: radarRect.bottom },
              passport: { left: passportRect.left, top: passportRect.top, right: passportRect.right, bottom: passportRect.bottom },
              container: { left: containerRect.left, top: containerRect.top, right: containerRect.right, bottom: containerRect.bottom },
              controls: { left: controlsRect.left, top: controlsRect.top, right: controlsRect.right, bottom: controlsRect.bottom },
              legend: { left: legendRect.left, top: legendRect.top, right: legendRect.right, bottom: legendRect.bottom },
              viewport: { width: window.innerWidth, height: window.innerHeight },
              invalid: radar.getAttribute('data-placement-invalid'),
              compact: radar.getAttribute('data-compact'),
              unavailable: radar.getAttribute('data-placement-unavailable')
            });
        }, 180);
      });
    })()`, true);
    assert.equal(overlaps(state.radar, state.passport, 10), false, JSON.stringify(state, null, 2));
    assert.notEqual(state.invalid, 'true', JSON.stringify(state, null, 2));
    assert.equal(state.compact, 'true', JSON.stringify(state, null, 2));

    const expanded = await evaluate(browser, sessionId, `(function () {
      document.getElementById('overview-map-expand').click();
      return new Promise(function (resolve) {
        setTimeout(function () {
            var radar = document.getElementById('overview-map');
            var rect = radar.getBoundingClientRect();
            var passport = document.getElementById('focus-chip');
            resolve({
              compact: radar.getAttribute('data-compact'),
              height: rect.height,
              surfaceVisible: getComputedStyle(document.getElementById('overview-map-surface')).display !== 'none',
              passportYielded: passport.getAttribute('data-radar-yielded'),
              passportVisible: getComputedStyle(passport).display !== 'none'
            });
        }, 120);
      });
    })()`, true);
    assert.equal(expanded.compact, null, JSON.stringify(expanded, null, 2));
    assert.equal(expanded.surfaceVisible, true, JSON.stringify(expanded, null, 2));
    assert.equal(expanded.passportYielded, 'true', JSON.stringify(expanded, null, 2));
    assert.equal(expanded.passportVisible, false, JSON.stringify(expanded, null, 2));
    assert.ok(expanded.height > state.radar.bottom - state.radar.top, JSON.stringify({ state, expanded }, null, 2));

    const closed = await evaluate(browser, sessionId, `(function () {
      document.getElementById('overview-map-close').click();
      var passport = document.getElementById('focus-chip');
      return {
        radarHidden: document.getElementById('overview-map').hidden,
        passportYielded: passport.getAttribute('data-radar-yielded'),
        passportVisible: getComputedStyle(passport).display !== 'none'
      };
    })()`);
    assert.equal(closed.radarHidden, true, JSON.stringify(closed, null, 2));
    assert.equal(closed.passportYielded, null, JSON.stringify(closed, null, 2));
    assert.equal(closed.passportVisible, true, JSON.stringify(closed, null, 2));

    for (const original of [null, 'false', 'true']) {
      const restored = await evaluate(browser, sessionId, `(function () {
        var passport = document.getElementById('focus-chip');
        var original = ${JSON.stringify(original)};
        if (original === null) passport.removeAttribute('aria-hidden');
        else passport.setAttribute('aria-hidden', original);
        Archify.radar.open();
        document.getElementById('overview-map-expand').click();
        var during = { yielded: passport.getAttribute('data-radar-yielded'), aria: passport.getAttribute('aria-hidden') };
        Archify.radar.close();
        return { during: during, yielded: passport.getAttribute('data-radar-yielded'), aria: passport.getAttribute('aria-hidden') };
      })()`);
      assert.deepEqual(restored, { during: { yielded: 'true', aria: 'true' }, yielded: null, aria: original });
    }
  } finally {
    await browser.close();
  }
});

test('Semantic Radar reports a consistent unavailable state and recovers when space returns', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const artifact = path.join(tmp, 'radar-unavailable.html');
  execFileSync(process.execPath, [
    path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
    path.join(skillRoot, 'examples', CASES.architecture),
    artifact,
  ]);
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await loadArtifact(browser, artifact, { width: 390, height: 300 });
    const unavailable = await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      window.scrollTo(0, Math.max(0, container.offsetTop));
      Archify.focus.set('lb', { toggle: false });
      document.getElementById('btn-focus-relations').click();
      Archify.radar.open();
      return new Promise(function (resolve) {
        setTimeout(function () {
          var panel = document.getElementById('overview-map');
          var trigger = document.getElementById('btn-overview-map');
          var feedback = document.getElementById('overview-map-feedback');
          resolve({
            requested: Archify.radar.isOpen(),
            panelHidden: panel.hidden,
            expanded: trigger.getAttribute('aria-expanded'),
            limited: trigger.getAttribute('data-radar-space-limited'),
            feedbackHidden: feedback.hidden,
            feedback: feedback.textContent.trim()
          });
        }, 260);
      });
    })()`, true);
    assert.equal(unavailable.requested, true);
    assert.equal(unavailable.panelHidden, true, JSON.stringify(unavailable, null, 2));
    assert.equal(unavailable.expanded, 'false', JSON.stringify(unavailable, null, 2));
    assert.equal(unavailable.limited, 'true', JSON.stringify(unavailable, null, 2));
    assert.equal(unavailable.feedbackHidden, false, JSON.stringify(unavailable, null, 2));
    assert.match(unavailable.feedback, /space/i);

    await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
      width: 390,
      height: 600,
      deviceScaleFactor: 1,
      mobile: false,
    }, sessionId);
    const recovered = await evaluate(browser, sessionId, `new Promise(function (resolve) {
      setTimeout(function () {
        var panel = document.getElementById('overview-map');
        var trigger = document.getElementById('btn-overview-map');
        var feedback = document.getElementById('overview-map-feedback');
        resolve({
          requested: Archify.radar.isOpen(),
          panelHidden: panel.hidden,
          expanded: trigger.getAttribute('aria-expanded'),
          feedbackHidden: feedback.hidden
        });
      }, 260);
    })`, true);
    assert.equal(recovered.requested, true);
    assert.equal(recovered.panelHidden, false, JSON.stringify(recovered, null, 2));
    assert.equal(recovered.expanded, 'true', JSON.stringify(recovered, null, 2));
    assert.equal(recovered.feedbackHidden, true, JSON.stringify(recovered, null, 2));
  } finally {
    await browser.close();
  }
});

test('Semantic Radar automatically avoids a tall Semantic Passport', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const input = path.join(tmp, 'tall-passport.architecture.json');
  const artifact = path.join(tmp, 'tall-passport.html');
  const peers = Array.from({ length: 12 }, (_, index) => ({
    id: `peer-${index + 1}`,
    type: index % 2 ? 'backend' : 'database',
    label: `Peer ${index + 1}`,
    sublabel: 'Connected system',
    pos: [80, 40 + index * 90],
    size: [130, 60],
  }));
  fs.writeFileSync(input, JSON.stringify({
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Tall Passport Radar Regression', output: artifact },
    components: [
      ...peers,
      { id: 'hub', type: 'security', label: 'Relationship Hub', sublabel: 'Many authored links', pos: [900, 500], size: [150, 70] },
    ],
    boundaries: [],
    connections: peers.map((peer, index) => ({
      id: `hub-to-${peer.id}`,
      from: 'hub',
      to: peer.id,
      fromSide: 'left',
      toSide: 'right',
      via: [[840, 535], [840, peer.pos[1] + 30]],
    })),
    cards: [],
  }, null, 2));
  execFileSync(process.execPath, [
    path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
    input,
    artifact,
  ]);
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await loadArtifact(browser, artifact, { width: 1200, height: 700 });
    const rects = await radarRects(browser, sessionId, `
      var container = document.querySelector('.diagram-container');
      window.scrollTo(0, Math.max(0, container.offsetTop));
      Archify.focus.set('hub', { toggle: false });
      Archify.radar.open();
    `);
    assert.ok(rects.passport, JSON.stringify(rects, null, 2));
    assert.equal(overlaps(rects.radar, rects.passport, 10), false, JSON.stringify(rects, null, 2));

    const dragGeometry = await evaluate(browser, sessionId, `(function () {
      var radar = document.getElementById('overview-map').getBoundingClientRect();
      var head = document.querySelector('.overview-map-head').getBoundingClientRect();
      var passport = document.getElementById('focus-chip').getBoundingClientRect();
      var active = document.querySelector('[data-focus-selected]');
      var nearestLeft = passport.right + 16;
      active.getBoundingClientRect = function () {
        return {
          left: nearestLeft,
          top: radar.top,
          right: nearestLeft + radar.width,
          bottom: radar.top + radar.height,
          width: radar.width,
          height: radar.height
        };
      };
      return {
        radar: { left: radar.left, top: radar.top },
        head: { left: head.left, top: head.top, height: head.height },
        requested: { left: passport.right + 8, top: radar.top },
        nearest: { left: nearestLeft, top: radar.top }
      };
    })()`);
    await dragMouse(browser, sessionId, {
      x: dragGeometry.head.left + 48,
      y: dragGeometry.head.top + dragGeometry.head.height / 2,
    }, {
      x: dragGeometry.head.left + 48 + dragGeometry.requested.left - dragGeometry.radar.left,
      y: dragGeometry.head.top + dragGeometry.head.height / 2 + dragGeometry.requested.top - dragGeometry.radar.top,
    });
    const snappedRects = await radarRects(browser, sessionId, '');
    assert.equal(overlaps(snappedRects.radar, snappedRects.passport, 10), false, JSON.stringify(snappedRects, null, 2));
    assert.ok(Math.abs(snappedRects.radar.left - dragGeometry.nearest.left) <= 2, JSON.stringify({ dragGeometry, snappedRects }, null, 2));
    assert.ok(Math.abs(snappedRects.radar.top - dragGeometry.nearest.top) <= 2, JSON.stringify({ dragGeometry, snappedRects }, null, 2));
  } finally {
    await browser.close();
  }
});

test('Semantic Radar titlebar drag persists while surface drag still pans the diagram', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const artifact = path.join(tmp, 'radar-dragging.html');
  execFileSync(process.execPath, [
    path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
    path.join(skillRoot, 'examples', CASES.architecture),
    artifact,
  ]);
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await loadArtifact(browser, artifact, { width: 1440, height: 900 });
    const geometry = await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      window.scrollTo(0, Math.max(0, container.offsetTop + container.offsetHeight - window.innerHeight + 8));
      Archify.radar.open();
      var radar = document.getElementById('overview-map').getBoundingClientRect();
      var head = document.querySelector('.overview-map-head').getBoundingClientRect();
      var containerRect = container.getBoundingClientRect();
      return {
        radar: { left: radar.left, top: radar.top, width: radar.width, height: radar.height },
        head: { left: head.left, top: head.top, width: head.width, height: head.height },
        state: Archify.view.state(),
        target: {
          left: Math.max(24, containerRect.left + 360),
          top: Math.max(24, containerRect.top + 20)
        }
      };
    })()`);
    const titleStart = {
      x: geometry.head.left + 48,
      y: geometry.head.top + geometry.head.height / 2,
    };
    const titleTarget = {
      x: titleStart.x + geometry.target.left - geometry.radar.left,
      y: titleStart.y + geometry.target.top - geometry.radar.top,
    };
    await dragMouse(browser, sessionId, titleStart, titleTarget);

    const manuallyPlaced = await evaluate(browser, sessionId, `(function () {
      var radar = document.getElementById('overview-map').getBoundingClientRect();
      return { left: radar.left, top: radar.top, state: Archify.view.state() };
    })()`);
    assert.ok(Math.abs(manuallyPlaced.left - geometry.target.left) <= 2, JSON.stringify({ geometry, manuallyPlaced }, null, 2));
    assert.ok(Math.abs(manuallyPlaced.top - geometry.target.top) <= 2, JSON.stringify({ geometry, manuallyPlaced }, null, 2));
    assert.deepEqual(manuallyPlaced.state, geometry.state);

    // Capture the transient drag and cancellation in the same page operation.
    for (const cancel of ['pointercancel', 'Escape']) {
      const cancelled = await evaluate(browser, sessionId, `(function () {
        var panel = document.getElementById('overview-map');
        var head = panel.querySelector('.overview-map-head');
        var rect = panel.getBoundingClientRect();
        var start = { left: rect.left, top: rect.top };
        var h = head.getBoundingClientRect();
        function pointer(type, offset) {
          head.dispatchEvent(new PointerEvent(type, { bubbles: true, cancelable: true, pointerId: 71,
            button: 0, clientX: h.left + 48 + offset, clientY: h.top + h.height / 2 }));
        }
        pointer('pointerdown', 0);
        pointer('pointermove', 80);
        var moved = panel.getBoundingClientRect().left !== start.left;
        var dragging = panel.getAttribute('data-panel-dragging');
        if (${JSON.stringify(cancel)} === 'Escape') document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
        else pointer('pointercancel', 80);
        var end = panel.getBoundingClientRect();
        return { start: start, end: { left: end.left, top: end.top }, moved: moved, dragging: dragging,
          afterDragging: panel.getAttribute('data-panel-dragging'), open: Archify.radar.isOpen(), state: Archify.view.state() };
      })()`);
      assert.equal(cancelled.moved, true, cancel);
      assert.equal(cancelled.dragging, 'true', cancel);
      assert.equal(cancelled.afterDragging, null, cancel);
      assert.equal(cancelled.open, true, cancel);
      assert.deepEqual(cancelled.end, cancelled.start, cancel);
      assert.deepEqual(cancelled.state, manuallyPlaced.state, cancel);
    }
    const reopened = await evaluate(browser, sessionId, `(function () {
      Archify.radar.close();
      Archify.radar.open();
      Archify.radar.open();
      var rect = document.getElementById('overview-map').getBoundingClientRect();
      return { left: rect.left, top: rect.top, roots: document.querySelectorAll('#overview-map-surface > svg').length,
        count: Archify.radar.count(), nodes: document.querySelectorAll('[data-radar-node-id]').length };
    })()`);
    assert.equal(reopened.left, manuallyPlaced.left);
    assert.equal(reopened.top, manuallyPlaced.top);
    assert.equal(reopened.roots, 1);
    assert.ok(reopened.count > 0);
    assert.equal(reopened.count, reopened.nodes);

    const surfaceState = await evaluate(browser, sessionId, `(function () {
      var radar = document.getElementById('overview-map').getBoundingClientRect();
      var surface = document.getElementById('overview-map-surface').getBoundingClientRect();
      return {
        radar: { left: radar.left, top: radar.top },
        state: Archify.view.state(),
        start: { x: surface.left + 8, y: surface.top + 8 },
        end: { x: surface.right - 8, y: surface.bottom - 8 }
      };
    })()`);
    await dragMouse(browser, sessionId, surfaceState.start, surfaceState.end);
    const afterSurfaceDrag = await evaluate(browser, sessionId, `(function () {
      var radar = document.getElementById('overview-map').getBoundingClientRect();
      return {
        radar: { left: radar.left, top: radar.top },
        state: Archify.view.state()
      };
    })()`);
    assert.deepEqual(afterSurfaceDrag.radar, surfaceState.radar);
    assert.notDeepEqual(afterSurfaceDrag.state, surfaceState.state);

    await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
      width: 640,
      height: 700,
      deviceScaleFactor: 1,
      mobile: false,
    }, sessionId);
    const afterResize = await evaluate(browser, sessionId, `new Promise(function (resolve) {
      setTimeout(function () {
        var radar = document.getElementById('overview-map').getBoundingClientRect();
        var controls = document.querySelector('.diagram-nav').getBoundingClientRect();
        var container = document.querySelector('.diagram-container').getBoundingClientRect();
        resolve({
          radar: { left: radar.left, top: radar.top, right: radar.right, bottom: radar.bottom },
          controls: { left: controls.left, top: controls.top, right: controls.right, bottom: controls.bottom },
          container: { left: container.left, top: container.top, right: container.right, bottom: container.bottom },
          viewport: { width: window.innerWidth, height: window.innerHeight }
        });
      }, 120);
    })`, true);
    assert.ok(afterResize.radar.left >= Math.max(0, afterResize.container.left), JSON.stringify(afterResize, null, 2));
    assert.ok(afterResize.radar.right <= Math.min(afterResize.viewport.width, afterResize.container.right), JSON.stringify(afterResize, null, 2));
    assert.equal(overlaps(afterResize.radar, afterResize.controls, 10), false, JSON.stringify(afterResize, null, 2));
  } finally {
    await browser.close();
  }
});

test('closing a pending Radar request prevents retry and reflow from reopening it', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  render('architecture', CASES.architecture);
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await loadArtifact(browser, path.join(tmp, 'architecture.html'), { width: 390, height: 300 });
    const transition = await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      window.scrollTo(0, container.offsetTop);
      Archify.focus.set('lb', { toggle: false });
      document.getElementById('btn-focus-relations').click();
      var opened = Archify.radar.open();
      var panel = document.getElementById('overview-map');
      var waiting = { result: opened, requested: Archify.radar.isOpen(), hidden: panel.hidden };
      var closed = Archify.radar.close({ restoreFocus: true });
      return { waiting: waiting, closed: closed, requested: Archify.radar.isOpen(),
        focused: document.activeElement.id, feedbackHidden: document.getElementById('overview-map-feedback').hidden };
    })()`);
    assert.deepEqual(transition, { waiting: { result: true, requested: true, hidden: true },
      closed: false, requested: false, focused: 'btn-overview-map', feedbackHidden: true });
    await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
      width: 390, height: 600, deviceScaleFactor: 1, mobile: false,
    }, sessionId);
    // Observe the whole original retry window, not just a single settled frame.
    const after = await evaluate(browser, sessionId, `new Promise(function (resolve) {
      var reopened = false;
      var start = performance.now();
      function sample() {
        var panel = document.getElementById('overview-map');
        reopened = reopened || Archify.radar.isOpen() || !panel.hidden;
        if (performance.now() - start < 500) return requestAnimationFrame(sample);
        resolve({ reopened: reopened, expanded: document.getElementById('btn-overview-map').getAttribute('aria-expanded'),
          feedbackHidden: document.getElementById('overview-map-feedback').hidden });
      }
      requestAnimationFrame(sample);
    })`, true);
    assert.deepEqual(after, { reopened: false, expanded: 'false', feedbackHidden: true });
  } finally {
    await browser.close();
  }
});

test('Radar reflects camera viewport, status and Focus/Story activity through normal callers', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  render('architecture', CASES.architecture);
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await loadArtifact(browser, path.join(tmp, 'architecture.html'));
    await browser.cdp.send('Emulation.setEmulatedMedia', { features: [
      { name: 'prefers-reduced-motion', value: 'reduce' },
    ] }, sessionId);
    async function observe(action) {
      return evaluate(browser, sessionId, `(async () => {
        ${action}
        let previous = '', equal = 0;
        for (let frame = 0; frame < 240; frame += 1) {
          await new Promise(resolve => requestAnimationFrame(resolve));
          const viewport = document.querySelector('.overview-map-viewport');
          const actual = ['x','y','width','height'].map(name => Number(viewport.getAttribute(name)));
          const logical = Archify.view.logicalViewport();
          const expected = [logical.x,logical.y,logical.width,logical.height];
          const active = Array.from(document.querySelectorAll('[data-radar-active]'), node => node.getAttribute('data-radar-node-id')).sort();
          const status = document.getElementById('overview-map-status').textContent;
          const state = { actual, expected, active, status, count: Archify.radar.count(), scale: logical.scale,
            beat: Archify.guidedViews.beat()?.nodeId || null };
          const value = JSON.stringify(state);
          equal = value === previous ? equal + 1 : 0; previous = value;
          if (equal >= 8) return state;
        }
        throw new Error('Radar observations did not settle');
      })()`, true);
    }
    const initial = await observe(`window.scrollTo(0, document.querySelector('.diagram-container').offsetTop); Archify.radar.open();`);
    assert.deepEqual(initial.actual, initial.expected);
    assert.equal(initial.status, initial.count + ' nodes · full map');
    const zoomed = await observe(`document.querySelector('[data-view="in"]').click();`);
    assert.deepEqual(zoomed.actual, zoomed.expected);
    assert.notDeepEqual(zoomed.actual, initial.actual);
    assert.equal(zoomed.status, zoomed.count + ' nodes · ' + Math.round(zoomed.scale * 100) + '% viewport');
    assert.notEqual(zoomed.status, initial.status);
    assert.deepEqual((await observe(`Archify.focus.set('lb', { toggle:false });`)).active, ['lb']);
    assert.deepEqual((await observe(`Archify.focus.set('db', { toggle:false });`)).active, ['db']);
    assert.deepEqual((await observe(`Archify.focus.clear();`)).active, []);
    await observe(`Archify.guidedViews.activate('request-path');`);
    const first = await observe(`document.querySelector('[data-story-index="0"]').click(); Archify.focus.clear({ updateUrl:false });`);
    assert.ok(first.beat);
    assert.deepEqual(first.active, [first.beat]);
    const second = await observe(`document.querySelector('[data-story-index="1"]').click(); Archify.focus.clear({ updateUrl:false });`);
    assert.ok(second.beat);
    assert.notEqual(second.beat, first.beat);
    assert.deepEqual(second.active, [second.beat]);
    assert.deepEqual((await observe(`Archify.guidedViews.showAll();`)).active, []);
  } finally {
    await browser.close();
  }
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/semantic-zoom.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-semantic-zoom-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function svg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all typed renderers emit explicit context and fine reading-depth semantics', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /data-detail="context"/, mode);
    assert.match(html, /data-detail="fine"/, mode);
    assert.match(html, /data-detail-anchor/, mode);
    assert.doesNotMatch(html, /<text[^>]*data-detail="(?:context|fine)"[^>]*class="t-primary"/, mode);
    assert.match(html, /class="diagram-container" data-detail-level="read"/, mode);
  }
});

test('semantic zoom exposes MAP, READ, and FULL at deterministic thresholds', () => {
  const html = render('workflow', CASES.workflow);
  assert.match(html, /function detailLevel\(\)/);
  assert.match(html, /if \(state\.mode === 'semantic'\) return 'full'/);
  assert.match(html, /if \(state\.scale >= 1\.75\) return 'full'/);
  assert.match(html, /if \(state\.scale >= 1\) return 'read'/);
  assert.match(html, /return 'map'/);
  assert.match(html, /container\.setAttribute\('data-detail-level', detail\)/);
  assert.match(html, /var levelLabel = viewerText\('viewer\.nav\.level\.' \+ detail\)/);
  assert.match(html, /var resolvedLevel = semantic \? viewerText\('viewer\.nav\.level\.auto'\) : levelLabel/);
  assert.match(html, /Zoom in to reveal relationship labels and node context/);
  assert.match(html, /Zoom in again to reveal tags and annotations/);
  assert.match(html, /Full diagram detail/);
});

test('reading depth stays quiet at overview and yields to semantic intent', () => {
  const html = render('architecture', CASES.architecture);
  assert.match(html, /\.diagram-container\[data-detail-level="map"\] svg \[data-detail="context"\]/);
  assert.match(html, /\.diagram-container\[data-detail-level="map"\] svg \[data-detail="fine"\]/);
  assert.match(html, /\.diagram-container\[data-detail-level="read"\] svg \[data-detail="fine"\]/);
  assert.match(html, /\.diagram-container\[data-detail-level="map"\] svg \[data-detail-anchor\]/);
  assert.match(html, /svg\[data-focus-active\] \[data-focus-match\] \[data-detail\]/);
  assert.match(html, /svg\[data-intent-trace-active\] \[data-intent-trace-match\] \[data-detail\]/);
  assert.match(html, /svg\[data-route-active\] \[data-route-match\] \[data-detail\]/);
  assert.match(html, /svg\[data-story-active\] \[data-story-step\] \[data-detail\]/);
  assert.match(html, /svg\[data-relationship-preview-active\] \[data-relationship-preview\] \[data-detail\]/);
});

test('semantic zoom is motion-safe and full-fidelity in print and export', () => {
  const html = render('dataflow', CASES.dataflow);
  assert.match(html, /\[data-detail\] \{ transition: opacity 160ms ease/);
  assert.match(html, /@media print \{[\s\S]+\.diagram-container svg \[data-detail\] \{[\s\S]+opacity: 1 !important/);
  assert.match(html, /@media \(prefers-reduced-motion: reduce\)[\s\S]+svg \[data-detail\]/);
  assert.match(html, /clone\.querySelectorAll\('\[data-detail\], \[data-detail-anchor\]'\)/);
  assert.match(html, /el\.removeAttribute\('data-detail'\)/);
  assert.match(html, /el\.removeAttribute\('data-detail-anchor'\)/);
  assert.match(html, /clone\.querySelectorAll\('[^']*\[data-detail\][^']*\[data-detail-anchor\][^']*'\)\.length === 0/);
  assert.doesNotMatch(svg(html), /data-detail-level=/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/sequence-column-fit.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { textUnits } from '../renderers/shared/utils.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');

function renderOutcome(doc) {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-column-fit-'));
  const input = path.join(tmp, 'input.json');
  const output = path.join(tmp, 'output.html');
  fs.writeFileSync(input, JSON.stringify(doc));
  try {
    execFileSync('node', [
      path.join(skillRoot, 'renderers/sequence/render-sequence.mjs'),
      input,
      output,
    ], { stdio: ['ignore', 'ignore', 'pipe'] });
    return { code: 0, stderr: '', html: fs.readFileSync(output, 'utf8') };
  } catch (err) {
    return { code: err.status ?? 1, stderr: String(err.stderr || ''), html: '' };
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
}

function render(doc) {
  const outcome = renderOutcome(doc);
  assert.equal(outcome.code, 0, outcome.stderr);
  return outcome.html;
}

function participantBoxes(html) {
  return [...html.matchAll(/<rect x="([\d.]+)" y="72" width="([\d.]+)" height="54"/g)]
    .map(([, x, width]) => ({ x: Number(x), width: Number(width) }))
    .filter((box, index, all) => all.findIndex((other) => other.x === box.x) === index)
    .sort((left, right) => left.x - right.x);
}

function wideSequence(columnFit) {
  const meta = { title: 'Column fit', viewBox: [1320, 620] };
  if (columnFit) meta.column_fit = columnFit;
  return {
    schema_version: 1,
    diagram_type: 'sequence',
    meta,
    participants: [
      { id: 'browser', type: 'frontend', label: 'Browser' },
      { id: 'gateway', type: 'backend', label: 'Gateway' },
      { id: 'idp', type: 'security', label: 'IdP' },
      { id: 'api', type: 'backend', label: 'API' },
      { id: 'store', type: 'database', label: 'Store' }
    ],
    messages: [
      { from: 'browser', to: 'gateway', y: 200, label: 'request' },
      { from: 'gateway', to: 'idp', y: 260, label: 'authorize' },
      { from: 'idp', to: 'api', y: 320, label: 'token' },
      { from: 'api', to: 'store', y: 380, label: 'read' }
    ]
  };
}

test('fixed column fit keeps the historical 108px gap regardless of viewBox width', () => {
  const boxes = participantBoxes(render(wideSequence()));
  assert.equal(boxes.length, 5);
  assert.equal(boxes[0].width, 86);
  assert.equal(boxes[1].x - boxes[0].x, 108);
  assert.equal(boxes.at(-1).x + boxes.at(-1).width < 600, true,
    'fixed lanes stay packed on the left, leaving the wide canvas unused');
});

test('spread column fit uses the viewBox width and stays inside it', () => {
  const boxes = participantBoxes(render(wideSequence('spread')));
  assert.equal(boxes.length, 5);
  assert.ok(boxes[0].width > 86, 'participant boxes widen with the available room');
  assert.ok(boxes[1].x - boxes[0].x > 108, 'columns spread past the fixed gap');
  assert.equal(boxes[0].x, 62, 'first lane keeps the side margin');
  assert.ok(boxes.at(-1).x + boxes.at(-1).width <= 1320 - 40,
    'last lane stays inside the viewBox with the reserved margin');
});

test('spread column fit is opt-in, so an unset value renders like fixed', () => {
  assert.equal(render(wideSequence()), render(wideSequence('fixed')));
});

const wideLabel = 'Payment Gateway Service';

function labelledSequence(columnFit) {
  const doc = wideSequence(columnFit);
  doc.participants[1].label = wideLabel;
  return doc;
}

test('a label the fixed box rejects fits the spread box on the same viewBox', () => {
  const estimatedLabelW = textUnits(wideLabel) * 6.8;
  assert.ok(estimatedLabelW > 86 + 6, 'the fixture label must actually exceed the fixed box');

  const fixed = renderOutcome(labelledSequence());
  assert.notEqual(fixed.code, 0, 'the fixed box still rejects a label it cannot hold');
  assert.ok(fixed.stderr.includes(`Label "${wideLabel}"`), `expected the label in stderr:\n${fixed.stderr}`);
  assert.ok(fixed.stderr.includes('86px participant box'), `expected the fixed box width in stderr:\n${fixed.stderr}`);

  const spread = renderOutcome(labelledSequence('spread'));
  assert.equal(spread.code, 0, spread.stderr);
  const box = participantBoxes(spread.html)[1];
  assert.ok(estimatedLabelW <= box.width + 6, `label ~${estimatedLabelW}px must fit the ${box.width}px spread box`);
  assert.ok(spread.html.includes(`>${wideLabel}</text>`), 'the label renders unshortened');
});

test('the sublabel diagnostic reports the width in force, not the historical constant', () => {
  const unrescuable = 'Payment authorization gateway detail text that stays far too long to shrink';
  const doc = wideSequence('spread');
  doc.participants[0].sublabel = unrescuable;

  const { code, stderr } = renderOutcome(doc);
  assert.notEqual(code, 0, 'a sublabel past the legible minimum is still rejected');
  assert.match(stderr, /participant boxes are 190px for this viewBox width and 5 participants/);
  assert.doesNotMatch(stderr, /boxes are a fixed/, 'spread must not quote the fixed layout');
});

test('the fast authoring path explains when to opt into spread', () => {
  const schema = JSON.parse(fs.readFileSync(path.join(skillRoot, 'schemas/sequence.schema.json'), 'utf8'));
  const description = schema.properties.meta.properties.column_fit.description;
  const skill = fs.readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8');
  const rendererReadme = fs.readFileSync(path.join(skillRoot, 'renderers/sequence/README.md'), 'utf8');

  assert.match(description, /wide viewBox/);
  assert.match(description, /meaningful participant labels/);
  assert.match(skill, /do not shorten semantic labels before trying `spread`/);
  assert.match(rendererReadme, /Use `"spread"` when a wide/);
  assert.match(rendererReadme, /try `meta\.column_fit: "spread"` before shortening/);
});
```

## test/settled-flow.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { animateAttr } from '../renderers/shared/cli.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-settled-flow-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8'));
  doc.meta = { ...doc.meta, animation: 'trace' };
  const input = path.join(tmp, `${mode}.json`);
  const output = path.join(tmp, `${mode}.html`);
  fs.writeFileSync(input, JSON.stringify(doc));
  execFileSync('node', [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, output], {
    stdio: ['ignore', 'ignore', 'pipe'],
  });
  return fs.readFileSync(output, 'utf8');
}

test('all five renderers inherit one finite running-to-settled ambient contract', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /data-ambient-motion/, mode);
    assert.match(html, /function startAmbient\(\)/, mode);
    assert.match(html, /function settleAmbient\(reason\)/, mode);
    assert.match(html, /animation: archify-edge-flow [^;]+ 1;/, mode);
    assert.match(html, /animation: archify-node-pulse [^;]+ 1;/, mode);
    assert.doesNotMatch(html, /animation: archify-edge-flow [^;]+ infinite/, mode);
    assert.doesNotMatch(html, /animation: archify-node-pulse [^;]+ infinite/, mode);
  }
});

test('settled flow restores authored security and async dash semantics', () => {
  assert.match(template, /\.a-security\s*\{[^}]*stroke-dasharray:\s*5,5/);
  assert.match(template, /\.a-dashed\s*\{[^}]*stroke-dasharray:\s*4,4/);
  assert.match(template, /@keyframes archify-edge-flow\s*\{[\s\S]*?stroke-dasharray:\s*10 8/);
  assert.match(template, /100%\s*\{\s*stroke-dashoffset:\s*0;\s*opacity:\s*1;\s*\}/);
  const runningRule = template.match(/html\[data-ambient-motion="running"\][^{]+\[data-animate="edge"\][^{]*\{([^}]*)\}/)?.[1] || '';
  assert.ok(runningRule, 'running edge rule missing');
  assert.doesNotMatch(runningRule, /stroke-dasharray|stroke-dashoffset/);
});

test('ambient ownership is generation-bounded and cannot replay after settle', () => {
  assert.match(template, /var ambientStarted = false/);
  assert.match(template, /var ambientPending = new Set\(\)/);
  assert.match(template, /if \(ambientStarted \|\| !capable\) return false/);
  assert.match(template, /ambientStarted = true;[\s\S]*?html\.setAttribute\('data-ambient-motion', 'running'\)/);
  assert.match(template, /ambientPending\.delete\(event\.target\)/);
  assert.match(template, /if \(!ambientPending\.size\) settleAmbient\('complete'\)/);
  assert.match(template, /svg\.addEventListener\('animationend', onAmbientBoundary, true\)/);
  assert.match(template, /svg\.addEventListener\('animationcancel', onAmbientBoundary, true\)/);
  assert.match(template, /if \(paused \|\| owner \|\| html\.hasAttribute\('data-embed'\)/);
  assert.doesNotMatch(template, /setInterval\([^)]*ambient|addEventListener\('scroll'[^)]*ambient/);
});

test('animation delay is capped without changing normal authored order', () => {
  assert.equal(animateAttr({ animation: 'trace' }, 'edge', 0), ' data-animate="edge" style="--step:0"');
  assert.equal(animateAttr({ animation: 'trace' }, 'node', 8), ' data-animate="node" style="--step:8"');
  assert.equal(animateAttr({ animation: 'trace' }, 'edge', 99), ' data-animate="edge" style="--step:12"');
  assert.equal(animateAttr({}, 'edge', 99), '');
});

test('only the WebM canvas scene opts into a repeatable finite motion timeline', () => {
  assert.match(template, /var motionScene = createMotionScene\(svg\)/);
  assert.match(template, /drawMotionFrame\(ctx, backgroundImage, motionScene, elapsed\)/);
  assert.match(template, /var data = serializeSvg\(scale\);/);
  assert.match(template, /getPointAtLength/);
  assert.doesNotMatch(template, /serializeSvg\(1, \{ autoTheme: true, motion: true \}\)/);
});

test('Still, reduced motion, embed, share, hidden state, and stronger intent settle ambient flow', () => {
  assert.match(template, /reducedMotion\(\)/);
  assert.match(template, /html\.hasAttribute\('data-embed'\)/);
  assert.match(template, /html\.hasAttribute\('data-share-playback'\)/);
  assert.match(template, /html\.hasAttribute\('data-document-hidden'\)/);
  assert.match(template, /paused \|\| owner/);
  assert.match(template, /settleAmbient\('suppressed'\)/);
  assert.match(template, /html\[data-motion="still"\] svg\[data-animation="trace"\] \[data-animate\]/);
  assert.match(template, /@media \(prefers-reduced-motion: reduce\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/share-card-export.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const repoRoot = path.resolve(skillRoot, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-share-card-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', CASES[mode]),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function svgBlock(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers expose one explicit 1200x630 Share Card export', () => {
  for (const mode of Object.keys(CASES)) {
    const html = render(mode);
    assert.match(html, /data-format="share-card"/, mode);
    assert.match(html, /Share Card[\s\S]*?1200(?:&times;|×)630 PNG/, mode);
    assert.match(html, /var SHARE_CARD_WIDTH = 1200;/, mode);
    assert.match(html, /var SHARE_CARD_HEIGHT = 630;/, mode);
    assert.match(html, /function rasterizeShareCard\(options\)/, mode);
    assert.match(html, /format === 'share-card'/, mode);
  }
});

test('Share Card uses contain-only canonical geometry with fixed safe areas', () => {
  const html = render('architecture');
  assert.match(html, /var availableWidth = SHARE_CARD_WIDTH - SHARE_CARD_PADDING \* 2;/);
  assert.match(html, /var availableHeight = SHARE_CARD_HEIGHT - SHARE_CARD_HEADER - SHARE_CARD_PADDING;/);
  assert.match(html, /var fit = Math\.min\(availableWidth \/ data\.width, availableHeight \/ data\.height\);/);
  assert.match(html, /ctx\.drawImage\(img, drawX, drawY, drawWidth, drawHeight\);/);
  assert.match(html, /function canvas2dOrThrow\(canvas, label\)/);
  assert.match(html, /throw exportError\('viewer\.export\.error\.contextUnavailable'/);
  assert.match(html, /throw exportError\('viewer\.export\.error\.toBlobUnavailable'/);
  assert.match(html, /img\.onload = function \(\) \{\s*try \{/);
  assert.match(html, /function rasterizeShareCard\(options\)[\s\S]*?if \(!options\.variant\) return renderShareCard\(\);/);
  assert.match(html, /function renderShareCard\(options\)[\s\S]*?serializeSvg\(sourceScale, \{ routeSnapshot: routeSnapshot, reachSnapshot: reachSnapshot \}\)/);
  assert.match(html, /fitCanvasText\(ctx, title, [^)]+\)/);
  assert.match(html, /ARCHIFY ·/);
  assert.doesNotMatch(svgBlock(html), /share-card|Share Card|ARCHIFY ·/);
});

test('Share Card is a canonical PNG with exact receipt dimensions and filename', () => {
  const html = render('workflow');
  assert.match(html, /recordExportReceipt\('share-card', blob, true, \{ width: SHARE_CARD_WIDTH, height: SHARE_CARD_HEIGHT \}\)/);
  assert.match(html, /base \+ '-share-card\.png'/);
  assert.match(html, /data-last-export-width/);
  assert.match(html, /data-last-export-height/);
  assert.match(html, /shareCard: rasterizeShareCard/);
  assert.match(html, /if \(format === 'share-card'\) return true;/);
});

test('Copy Share Card reuses one canonical card blob and writes only PNG to the clipboard', () => {
  const html = render('architecture');
  assert.match(html, /data-action="copy-share-card"/);
  assert.match(html, /Copy Share Card[\s\S]*?<small class="hint">PNG to clipboard<\/small>/);
  assert.match(html, /function runCopyShareCard\(\)[\s\S]*?var blobPromise = rasterizeShareCard\(\);/);
  const copyBlock = html.match(/function runCopyShareCard\(\) \{[\s\S]*?\n      \}/)?.[0] || '';
  assert.equal((copyBlock.match(/rasterizeShareCard\(\)/g) || []).length, 1);
  assert.match(copyBlock, /writePngToClipboard\(blobPromise\)/);
  assert.match(html, /new ClipboardItem\(\{ 'image\/png': blobPromise \}\)/);
  assert.match(copyBlock, /recordExportReceipt\('share-card', blob, true, \{ width: SHARE_CARD_WIDTH, height: SHARE_CARD_HEIGHT \}\)/);
  assert.match(copyBlock, /toast\(viewerText\('viewer\.export\.copiedShare'\)\)/);
  assert.match(html, /copyShareCard: runCopyShareCard/);
});

test('Copy Share Card fails closed when image clipboard writing is unavailable', () => {
  const html = render('workflow');
  assert.match(html, /it\.dataset\.action === 'copy-share-card'[\s\S]*?!canCopyImage\(\)/);
  assert.match(html, /function runCopyShareCard\(\)[\s\S]*?if \(!canCopyImage\(\)\)/);
  assert.match(html, /Clipboard image write not supported by this browser/);
  assert.match(html, /button\[data-action="copy-share-card"\]/);
  assert.match(html, /document\.documentElement\.removeAttribute\('data-last-export-format'\)/);
  assert.match(html, /data-last-export-error-format', 'share-card'/);
});

test('ordinary Copy PNG keeps its existing full-diagram raster path', () => {
  const html = render('sequence');
  assert.match(html, /function runCopy\(\)[\s\S]*?var blobPromise = rasterize\('png'\);/);
  assert.match(html, /runCopy\(\)[\s\S]*?writePngToClipboard\(blobPromise\)/);
  assert.doesNotMatch(svgBlock(html), /copy-share-card|Copy Share Card/);
});

test('Share Card stays viewer-only and reuses export cleanup instead of source state', () => {
  const html = render('sequence');
  assert.match(html, /html\[data-embed="true"\] \.toolbar/);
  assert.match(html, /@media print[\s\S]*?\.toolbar/);
  assert.match(html, /function rasterizeShareCard\(options\)[\s\S]*?if \(!options\.variant\) return renderShareCard\(\);/);
  assert.match(html, /function renderShareCard\(options\)[\s\S]*?serializeSvg\(sourceScale, \{ routeSnapshot: routeSnapshot, reachSnapshot: reachSnapshot \}\)/);
  assert.match(html, /if \(!data\.canonicalStateClean\) return Promise\.reject\(exportError\('viewer\.export\.error\.viewerState'\)\);/);
  assert.match(html, /canonicalStateClean/);
  assert.doesNotMatch(svgBlock(html), /data-last-export-|data-format="share-card"/);
});

test('the skill and every README make the optional Share Card discoverable', () => {
  const viewer = fs.readFileSync(path.join(skillRoot, 'references', 'viewer-runtime.md'), 'utf8');
  assert.match(viewer, /optional 1200(?:×|x)630 Share Card PNG/i);
  assert.match(viewer, /current theme and visual preset/i);
  assert.match(viewer, /never claim(?:s|ing)? validation/i);
  assert.match(viewer, /Copy Share Card/i);

  for (const readme of ['README.md', 'README_EN.md', 'README_ZH.md']) {
    const text = fs.readFileSync(path.join(repoRoot, readme), 'utf8');
    assert.match(text, /Share Card/i, readme);
    assert.match(text, /1200(?:×|x)630/, readme);
    assert.match(text, /copy|复制/i, readme);
  }
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/site-language-continuity.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import http from 'node:http';
import os from 'node:os';
import path from 'node:path';
import vm from 'node:vm';
import { fileURLToPath } from 'node:url';

import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';
import { DIAGRAM_TYPES, DIAGRAM_TYPE_LABELS } from '../../scripts/site-copy.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '../..');
const runtimePath = path.join(repoRoot, 'docs/assets/site-language.js');
const navigationPath = path.join(repoRoot, 'docs/assets/site-navigation.css');
const integrationEnabled = process.env.ARCHIFY_SITE_INTEGRATION === '1';
const chromePath = integrationEnabled && process.env.ARCHIFY_CHROME ? findChrome() : null;

function loadRuntime({
  url = 'https://example.test/',
  values = new Map(),
  storageError = false,
  historyError = false,
  source = runtimePath,
} = {}) {
  const localStorage = {
    getItem(key) {
      if (storageError) throw new Error('storage unavailable');
      return values.has(key) ? values.get(key) : null;
    },
    setItem(key, value) {
      if (storageError) throw new Error('storage unavailable');
      values.set(key, String(value));
    },
  };
  let currentUrl = new URL(url);
  const location = {};
  function syncLocation() {
    location.href = currentUrl.href;
    location.search = currentUrl.search;
    location.pathname = currentUrl.pathname;
    location.hash = currentUrl.hash;
  }
  syncLocation();
  const window = {
    location,
    history: {
      replaceState(_state, _title, next) {
        if (historyError) throw new Error('history unavailable');
        currentUrl = new URL(next, currentUrl);
        syncLocation();
      },
    },
    localStorage,
  };
  vm.runInNewContext(fs.readFileSync(source, 'utf8'), { window, URL, URLSearchParams });
  return { language: window.ArchifySiteLanguage, values, url: () => new URL(currentUrl) };
}

async function evaluate(browser, sessionId, expression) {
  const response = await browser.cdp.send('Runtime.evaluate', {
    expression,
    awaitPromise: true,
    returnByValue: true,
  }, sessionId);
  if (response.exceptionDetails) {
    throw new Error(response.exceptionDetails.exception?.description
      || response.exceptionDetails.text
      || 'Runtime.evaluate failed');
  }
  return response.result?.value;
}

async function navigate(browser, sessionId, url) {
  const loaded = browser.cdp.waitFor('Page.loadEventFired', sessionId);
  const navigation = await browser.cdp.send('Page.navigate', { url }, sessionId);
  if (navigation.errorText) throw new Error(`Chrome navigation failed: ${navigation.errorText}`);
  await loaded;
}

async function clickAndNavigate(browser, sessionId, selector) {
  const loaded = browser.cdp.waitFor('Page.loadEventFired', sessionId);
  await evaluate(browser, sessionId, `(function () {
    var link = document.querySelector(${JSON.stringify(selector)});
    if (!link) throw new Error('Missing navigation link: ' + ${JSON.stringify(selector)});
    link.click();
  })()`);
  await loaded;
}

function startStaticServer(root) {
  const server = http.createServer((request, response) => {
    const requestUrl = new URL(request.url || '/', 'http://127.0.0.1');
    const relative = decodeURIComponent(requestUrl.pathname).replace(/^\/+/, '') || 'index.html';
    const requestedPath = path.resolve(root, relative);
    if (!requestedPath.startsWith(`${path.resolve(root)}${path.sep}`)) {
      response.writeHead(403).end('Forbidden');
      return;
    }
    try {
      const body = fs.readFileSync(requestedPath);
      const contentType = requestedPath.endsWith('.css') ? 'text/css'
        : requestedPath.endsWith('.js') ? 'text/javascript'
          : requestedPath.endsWith('.json') ? 'application/json'
            : 'text/html';
      response.writeHead(200, { 'content-type': `${contentType}; charset=utf-8` });
      response.end(body);
    } catch (_) {
      response.writeHead(404).end('Not found');
    }
  });
  return server;
}

test('site language runtime normalizes one entry parameter into one durable preference', () => {
  const canonical = loadRuntime({ values: new Map([['archify-lang', 'zh']]) });
  assert.equal(canonical.language.read(), 'zh');

  for (const legacyKey of ['archify-gallery-language', 'archify-guide-language']) {
    const legacy = loadRuntime({ values: new Map([[legacyKey, 'zh']]) });
    assert.equal(legacy.language.read(), 'zh', `${legacyKey} must remain readable during migration`);
    assert.equal(legacy.values.get('archify-lang'), 'zh', `${legacyKey} must migrate to the canonical key`);
  }

  const canonicalWins = loadRuntime({
    values: new Map([
      ['archify-lang', 'en'],
      ['archify-gallery-language', 'zh'],
      ['archify-guide-language', 'zh'],
    ]),
  });
  assert.equal(canonicalWins.language.read(), 'en');

  const secondLegacyFallback = loadRuntime({
    values: new Map([
      ['archify-gallery-language', 'fr'],
      ['archify-guide-language', 'zh'],
    ]),
  });
  assert.equal(secondLegacyFallback.language.read(), 'zh');
  assert.equal(secondLegacyFallback.values.get('archify-lang'), 'zh');

  const conflictingLegacy = loadRuntime({
    values: new Map([
      ['archify-gallery-language', 'zh'],
      ['archify-guide-language', 'en'],
    ]),
  });
  assert.equal(conflictingLegacy.language.read(), 'zh');
  assert.equal(conflictingLegacy.values.get('archify-lang'), 'zh');
  conflictingLegacy.values.set('archify-gallery-language', 'en');
  const migrated = loadRuntime({ values: conflictingLegacy.values });
  assert.equal(migrated.language.read(), 'zh', 'the canonical migration must win on later page loads');

  const explicit = loadRuntime({
    url: 'https://example.test/guide.html?lang=en&type=workflow#chooser',
    values: new Map([['archify-lang', 'zh']]),
  });
  assert.equal(explicit.language.read(), 'en');
  assert.equal(explicit.values.get('archify-lang'), 'en');
  assert.equal(explicit.url().searchParams.has('lang'), false);
  assert.equal(explicit.url().searchParams.get('type'), 'workflow');
  assert.equal(explicit.url().hash, '#chooser');

  const historyBlocked = loadRuntime({
    url: 'https://example.test/guide.html?lang=zh&type=workflow#chooser',
    values: new Map([['archify-lang', 'en']]),
    historyError: true,
  });
  assert.equal(historyBlocked.language.read(), 'zh');
  assert.equal(historyBlocked.values.get('archify-lang'), 'zh');
  assert.equal(historyBlocked.url().searchParams.get('lang'), 'zh');

  assert.equal(explicit.language.write('zh'), 'zh');
  const refreshed = loadRuntime({ url: explicit.url().href, values: explicit.values });
  assert.equal(refreshed.language.read(), 'zh');

  const unsupported = loadRuntime({
    url: 'https://example.test/?lang=fr',
    values: new Map([['archify-lang', 'zh']]),
  });
  assert.equal(unsupported.language.read(), 'zh');
  assert.equal(unsupported.url().searchParams.has('lang'), false);

  const defaultLanguage = loadRuntime();
  assert.equal(defaultLanguage.language.read(), 'en');

  const blocked = loadRuntime({ storageError: true });
  assert.equal(blocked.language.read(), 'en');
  assert.equal(blocked.language.write('zh'), 'zh');

  const source = fs.readFileSync(runtimePath, 'utf8');
  assert.match(source, /archify-gallery-language/);
  assert.match(source, /archify-guide-language/);
  assert.doesNotMatch(source, /navigator\.language|detectBrowserLanguage|select\s*:/);
});

test('custom site builders emit every shared site asset and preserve entry, navigation, selection, and refresh state', {
  skip: integrationEnabled ? false : 'Run through the serialized site integration gate.',
}, () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-site-language-'));
  try {
    const builds = [
      { script: 'build-start.mjs', args: [path.join(tmp, 'start-site/start.html')], root: 'start-site' },
      { script: 'build-guide.mjs', args: [path.join(tmp, 'guide-site/guide.html')], root: 'guide-site' },
      { script: 'build-gallery.mjs', args: [path.join(tmp, 'gallery-site')], root: 'gallery-site' },
    ];

    for (const build of builds) {
      execFileSync(process.execPath, [path.join(repoRoot, 'scripts', build.script), ...build.args]);
      for (const asset of ['site-language.js', 'site-navigation.css']) {
        const emitted = path.join(tmp, build.root, 'assets', asset);
        const canonicalAsset = path.join(repoRoot, 'docs/assets', asset);
        assert.ok(fs.existsSync(emitted), `${build.script}: ${asset} missing from custom output`);
        assert.equal(fs.readFileSync(emitted, 'utf8'), fs.readFileSync(canonicalAsset, 'utf8'));
      }
    }

    const emittedRuntime = path.join(tmp, 'start-site/assets/site-language.js');
    const values = new Map();

    const landing = loadRuntime({
      url: 'https://example.test/?lang=zh&utm_source=readme#proof',
      values,
      source: emittedRuntime,
    });
    assert.equal(landing.language.read(), 'zh');
    assert.equal(values.get('archify-lang'), 'zh');
    assert.equal(landing.url().searchParams.has('lang'), false);
    assert.equal(landing.url().searchParams.get('utm_source'), 'readme');
    assert.equal(landing.url().hash, '#proof');

    for (const page of ['gallery.html', 'guide.html', 'start.html']) {
      const navigation = loadRuntime({ url: `https://example.test/${page}`, values, source: emittedRuntime });
      assert.equal(navigation.language.read(), 'zh', page);
    }

    const explicitEnglish = loadRuntime({
      url: 'https://example.test/guide.html?lang=en#recipes',
      values,
      source: emittedRuntime,
    });
    assert.equal(explicitEnglish.language.read(), 'en');
    assert.equal(values.get('archify-lang'), 'en');
    assert.equal(explicitEnglish.url().searchParams.has('lang'), false);

    explicitEnglish.language.write('zh');
    assert.equal(explicitEnglish.url().searchParams.has('lang'), false);
    assert.equal(explicitEnglish.url().hash, '#recipes');

    const refreshed = loadRuntime({ url: explicitEnglish.url().href, values, source: emittedRuntime });
    assert.equal(refreshed.language.read(), 'zh');

    const nextPage = loadRuntime({ url: 'https://example.test/gallery.html', values, source: emittedRuntime });
    assert.equal(nextPage.language.read(), 'zh');

    nextPage.language.write('en');
    const refreshedEnglish = loadRuntime({ url: nextPage.url().href, values, source: emittedRuntime });
    assert.equal(refreshedEnglish.language.read(), 'en');
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('all site pages consume one language runtime and one navigation contract', () => {
  const pages = [
    'docs/index.html',
    'scripts/gallery-template.html',
    'scripts/guide-template.html',
    'scripts/start-template.html',
    'docs/gallery.html',
    'docs/guide.html',
    'docs/start.html',
  ];

  for (const relative of pages) {
    const html = fs.readFileSync(path.join(repoRoot, relative), 'utf8');
    assert.match(html, /<script src="assets\/site-language\.js"><\/script>/, `${relative}: shared runtime missing`);
    assert.match(html, /<link rel="stylesheet" href="assets\/site-navigation\.css">/, `${relative}: shared navigation missing`);
    assert.match(html, /<nav class="site-nav" aria-label="Primary navigation">/, `${relative}: canonical navigation root missing`);
    assert.match(html, /ArchifySiteLanguage\.read\(/, `${relative}: shared language read missing`);
    assert.match(html, /ArchifySiteLanguage\.write\(/, `${relative}: shared language write missing`);
    assert.match(html, /href="guide\.html"/, `${relative}: Guide navigation missing`);
    assert.match(html, /href="gallery\.html"/, `${relative}: Proof Lab navigation missing`);
    assert.match(html, /href="start\.html"/, `${relative}: Start navigation missing`);
    assert.match(html, /class="btn btn-primary nav-cta"/, `${relative}: install action missing`);
    assert.doesNotMatch(html, /(?:^|\s)nav\s*\{/, `${relative}: inline navigation layout bypasses the shared contract`);
    assert.doesNotMatch(html, /\.nav-right\s*\{/, `${relative}: inline navigation actions bypass the shared contract`);
    assert.doesNotMatch(
      html,
      /localStorage\.setItem\(['"]archify-(?:lang|gallery-language|guide-language)['"]/,
      `${relative}: page bypasses the shared language writer`,
    );
  }

  const navigation = fs.readFileSync(navigationPath, 'utf8');
  assert.match(navigation, /\.site-nav\s*\{/);
  assert.match(navigation, /\.site-nav \.nav-right\s*\{/);
  assert.match(navigation, /@media \(max-width: 640px\)/);
});

test('site page identity paths localize with the selected language', () => {
  const pages = [
    { paths: ['scripts/guide-template.html', 'docs/guide.html'], en: '/ guide', zh: '/ 场景指南' },
    { paths: ['scripts/gallery-template.html', 'docs/gallery.html'], en: '/ proof lab', zh: '/ 验证作品集' },
    { paths: ['scripts/start-template.html', 'docs/start.html'], en: '/ start', zh: '/ 快速上手' },
  ];

  for (const page of pages) {
    for (const relative of page.paths) {
      const html = fs.readFileSync(path.join(repoRoot, relative), 'utf8');
      assert.ok(
        html.includes(`<span class="nav-logo-path" data-en="${page.en}" data-zh="${page.zh}">${page.en}</span>`),
        `${relative}: page identity path must expose matching English and Chinese copy`,
      );
      assert.match(
        html,
        /querySelectorAll\('\[data-en\]\[data-zh\]'\)/,
        `${relative}: language changes must update bilingual page identity copy`,
      );
    }
  }
});

test('proof gallery type filters localize with the selected language', () => {
  const filters = DIAGRAM_TYPES.map((type) => ({
    type,
    en: DIAGRAM_TYPE_LABELS.en[type],
    zh: DIAGRAM_TYPE_LABELS.zh[type],
  }));

  const template = fs.readFileSync(path.join(repoRoot, 'scripts/gallery-template.html'), 'utf8');
  for (const filter of filters) {
    const placeholder = filter.type.toUpperCase();
    assert.ok(
      template.includes(`data-filter="${filter.type}" aria-pressed="false" data-en="[[DIAGRAM_TYPE_${placeholder}_EN]]" data-zh="[[DIAGRAM_TYPE_${placeholder}_ZH]]"`),
      `gallery template: ${filter.type} filter must consume the shared copy source`,
    );
  }

  for (const relative of ['docs/gallery.html']) {
    const html = fs.readFileSync(path.join(repoRoot, relative), 'utf8');
    assert.match(
      html,
      /<button(?=[^>]*data-filter="all")(?=[^>]*data-en="All \/ [^"]+")(?=[^>]*data-zh="全部配方 \/ [^"]+")[^>]*>All \/ [^<]+<\/button>/,
      `${relative}: all filter must expose English and Chinese copy`,
    );
    for (const filter of filters) {
      const bilingualFilter = new RegExp(
        `<button(?=[^>]*data-filter="${filter.type}")(?=[^>]*data-en="${filter.en}")(?=[^>]*data-zh="${filter.zh}")[^>]*>${filter.en}<\\/button>`,
      );
      assert.match(html, bilingualFilter, `${relative}: ${filter.type} filter must expose English and Chinese copy`);
    }
    assert.match(
      html,
      /querySelectorAll\('\[data-en\]\[data-zh\]'\)/,
      `${relative}: language changes must update bilingual gallery filters`,
    );
  }
});

test('scenario guide type filters use consistent Chinese diagram names', () => {
  const template = fs.readFileSync(path.join(repoRoot, 'scripts/guide-template.html'), 'utf8');
  assert.match(template, /var types = \[\[DIAGRAM_TYPES_JSON\]\];/);
  assert.match(template, /var labels = \[\[DIAGRAM_TYPE_LABELS_JSON\]\];/);

  const html = fs.readFileSync(path.join(repoRoot, 'docs/guide.html'), 'utf8');
  assert.ok(
    html.includes(`var labels = ${JSON.stringify(DIAGRAM_TYPE_LABELS)};`),
    'docs/guide.html: Guide filters must use the shared Chinese diagram names',
  );
});

test('real Chrome preserves language through entry, navigation, selection, refresh, and consistent navigation chrome', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real site regression.',
  timeout: 60000,
}, async () => {
  const docsRoot = path.join(repoRoot, 'docs');
  const server = startStaticServer(docsRoot);
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  const address = server.address();
  const baseUrl = `http://127.0.0.1:${address.port}`;
  const browser = new ChromeVisualBrowser(chromePath);

  try {
    const sessionId = await browser.sessionPromise;
    await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
      width: 1440,
      height: 900,
      deviceScaleFactor: 1,
      mobile: false,
    }, sessionId);

    await navigate(browser, sessionId, `${baseUrl}/index.html`);
    await evaluate(browser, sessionId, 'localStorage.clear()');

    await evaluate(browser, sessionId, "localStorage.setItem('archify-guide-language', 'zh')");
    await navigate(browser, sessionId, `${baseUrl}/index.html`);
    assert.deepEqual(await evaluate(browser, sessionId, `({
      language: document.documentElement.lang,
      stored: localStorage.getItem('archify-lang')
    })`), { language: 'zh-CN', stored: 'zh' });

    await evaluate(browser, sessionId, 'localStorage.clear()');
    await navigate(browser, sessionId, `${baseUrl}/index.html?lang=zh&utm_source=browser-test#proof`);

    let state = await evaluate(browser, sessionId, `({
      language: document.documentElement.lang,
      stored: localStorage.getItem('archify-lang'),
      langQuery: new URL(location.href).searchParams.get('lang'),
      campaign: new URL(location.href).searchParams.get('utm_source'),
      hash: location.hash
    })`);
    assert.deepEqual(state, {
      language: 'zh-CN', stored: 'zh', langQuery: null, campaign: 'browser-test', hash: '#proof',
    });

    await clickAndNavigate(browser, sessionId, '.site-nav a[href="gallery.html"]');
    assert.equal(await evaluate(browser, sessionId, 'document.documentElement.lang'), 'zh-CN');
    assert.equal(await evaluate(browser, sessionId, 'document.querySelector(".nav-logo-path").textContent'), '/ 验证作品集');
    assert.deepEqual(await evaluate(browser, sessionId, `Array.from(document.querySelectorAll('[data-filter]')).map(function (button) {
      return button.textContent;
    })`), ['全部配方 / 11', '架构图', '工作流', '时序图', '数据流', '生命周期']);

    await evaluate(browser, sessionId, 'document.querySelector(\'[data-filter="architecture"]\').click()');
    state = await evaluate(browser, sessionId, `({
      language: document.documentElement.lang,
      selected: document.querySelector('[data-filter="architecture"]').getAttribute('aria-pressed'),
      typeQuery: new URL(location.href).searchParams.get('type'),
      visibleCount: document.querySelectorAll('.showcase-card:not([hidden])').length,
      onlyArchitecture: Array.from(document.querySelectorAll('.showcase-card:not([hidden])')).every(function (card) {
        return card.getAttribute('data-type') === 'architecture';
      })
    })`);
    assert.deepEqual(state, {
      language: 'zh-CN', selected: 'true', typeQuery: 'architecture', visibleCount: 2, onlyArchitecture: true,
    });

    let loaded = browser.cdp.waitFor('Page.loadEventFired', sessionId);
    await browser.cdp.send('Page.reload', {}, sessionId);
    await loaded;
    assert.deepEqual(await evaluate(browser, sessionId, `({
      language: document.documentElement.lang,
      selected: document.querySelector('[data-filter="architecture"]').getAttribute('aria-pressed'),
      visibleCount: document.querySelectorAll('.showcase-card:not([hidden])').length
    })`), { language: 'zh-CN', selected: 'true', visibleCount: 2 });

    await evaluate(browser, sessionId, 'document.getElementById("language").click()');
    assert.equal(await evaluate(browser, sessionId, 'document.documentElement.lang'), 'en');
    assert.equal(await evaluate(browser, sessionId, 'document.querySelector(".nav-logo-path").textContent'), '/ proof lab');
    assert.deepEqual(await evaluate(browser, sessionId, `Array.from(document.querySelectorAll('[data-filter]')).map(function (button) {
      return button.textContent;
    })`), ['All / 11', 'Architecture', 'Workflow', 'Sequence', 'Data flow', 'Lifecycle']);

    loaded = browser.cdp.waitFor('Page.loadEventFired', sessionId);
    await browser.cdp.send('Page.reload', {}, sessionId);
    await loaded;
    assert.equal(await evaluate(browser, sessionId, 'document.documentElement.lang'), 'en');

    await clickAndNavigate(browser, sessionId, '.site-nav a[href="guide.html"]');
    assert.equal(await evaluate(browser, sessionId, 'document.documentElement.lang'), 'en');
    await navigate(browser, sessionId, `${baseUrl}/guide.html?lang=zh#recipes`);
    state = await evaluate(browser, sessionId, `({
      language: document.documentElement.lang,
      stored: localStorage.getItem('archify-lang'),
      langQuery: new URL(location.href).searchParams.get('lang'),
      hash: location.hash
    })`);
    assert.deepEqual(state, { language: 'zh-CN', stored: 'zh', langQuery: null, hash: '#recipes' });
    assert.equal(await evaluate(browser, sessionId, 'document.querySelector(".nav-logo-path").textContent'), '/ 场景指南');
    assert.deepEqual(await evaluate(browser, sessionId, `Array.from(document.querySelectorAll('#filters [data-filter]')).map(function (button) {
      return button.textContent;
    })`), ['全部配方', '架构图', '工作流', '时序图', '数据流', '生命周期']);

    await evaluate(browser, sessionId, 'document.querySelector(\'#filters [data-filter="sequence"]\').click()');
    state = await evaluate(browser, sessionId, `({
      language: document.documentElement.lang,
      selected: document.querySelector('#filters [data-filter="sequence"]').classList.contains('active'),
      visibleCount: document.querySelectorAll('#cards .card').length,
      onlySequence: Array.from(document.querySelectorAll('#cards .card .card-type')).every(function (label) {
        return label.textContent === 'sequence';
      }),
      labels: Array.from(document.querySelectorAll('#filters [data-filter]')).map(function (button) {
        return button.textContent;
      })
    })`);
    assert.deepEqual(state, {
      language: 'zh-CN',
      selected: true,
      visibleCount: 2,
      onlySequence: true,
      labels: ['全部配方', '架构图', '工作流', '时序图', '数据流', '生命周期'],
    });

    await clickAndNavigate(browser, sessionId, '.site-nav a[href="start.html"]');
    assert.equal(await evaluate(browser, sessionId, 'document.documentElement.lang'), 'zh-CN');
    assert.equal(await evaluate(browser, sessionId, 'new URL(location.href).searchParams.has("lang")'), false);
    assert.equal(await evaluate(browser, sessionId, 'document.querySelector(".nav-logo-path").textContent'), '/ 快速上手');

    const pages = ['index.html', 'gallery.html', 'guide.html', 'start.html'];
    const desktopReceipts = [];
    for (const page of pages) {
      await navigate(browser, sessionId, `${baseUrl}/${page}`);
      desktopReceipts.push(await evaluate(browser, sessionId, `(function () {
        var nav = document.querySelector('.site-nav');
        var logo = nav.querySelector('.nav-logo-text');
        var actions = nav.querySelector('.nav-right');
        var language = nav.querySelector('.btn-lang');
        var cta = nav.querySelector('.nav-cta');
        var navStyle = getComputedStyle(nav);
        var logoStyle = getComputedStyle(logo);
        var actionsStyle = getComputedStyle(actions);
        var languageStyle = getComputedStyle(language);
        var ctaStyle = getComputedStyle(cta);
        return {
          height: nav.getBoundingClientRect().height,
          position: navStyle.position,
          paddingLeft: navStyle.paddingLeft,
          background: navStyle.backgroundColor,
          borderBottom: navStyle.borderBottomWidth + ' ' + navStyle.borderBottomStyle + ' ' + navStyle.borderBottomColor,
          logoFont: logoStyle.fontFamily,
          logoSize: logoStyle.fontSize,
          actionGap: actionsStyle.gap,
          languageHeight: language.getBoundingClientRect().height,
          languageRadius: languageStyle.borderRadius,
          ctaHeight: cta.getBoundingClientRect().height,
          ctaRadius: ctaStyle.borderRadius,
          linkCount: nav.querySelectorAll('.nav-link').length
        };
      })()`));
    }
    for (const receipt of desktopReceipts.slice(1)) assert.deepEqual(receipt, desktopReceipts[0]);

    await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
      width: 390,
      height: 844,
      deviceScaleFactor: 1,
      mobile: true,
    }, sessionId);
    for (const page of pages) {
      await navigate(browser, sessionId, `${baseUrl}/${page}`);
      const mobile = await evaluate(browser, sessionId, `(function () {
        var nav = document.querySelector('.site-nav');
        var rect = nav.getBoundingClientRect();
        var actions = nav.querySelector('.nav-right').getBoundingClientRect();
        return {
          height: rect.height,
          left: rect.left,
          right: rect.right,
          actionsRight: actions.right,
          linkDisplay: getComputedStyle(nav.querySelector('.nav-link')).display
        };
      })()`);
      assert.deepEqual(mobile, { height: 60, left: 0, right: 390, actionsRight: 370, linkDisplay: 'none' }, page);
    }
  } finally {
    await browser.close();
    await new Promise((resolve) => server.close(resolve));
  }
});
```

## test/site-language-integration.mjs

```js
process.env.ARCHIFY_SITE_INTEGRATION = '1';

// GitHub-hosted Linux runners require the same explicit Chrome sandbox opt-out
// already used by this workflow's other real-browser regression steps.
if (process.platform === 'linux' && process.env.GITHUB_ACTIONS === 'true') {
  process.env.ARCHIFY_CHROME_NO_SANDBOX = '1';
}

await import('./site-language-continuity.test.mjs');
```

## test/skill-metadata.test.mjs

```js
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import assert from 'node:assert/strict';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.join(here, '..');
const skill = readFileSync(path.join(skillRoot, 'SKILL.md'), 'utf8');
const authoringContract = readFileSync(path.join(skillRoot, 'references', 'authoring-contract.md'), 'utf8');
const frontmatter = skill.match(/^---\n([\s\S]*?)\n---/);

test('skill description is portable across 1024-character runtimes and remains searchable', () => {
  assert.ok(frontmatter, 'SKILL.md must start with YAML frontmatter');
  const description = frontmatter[1].match(/^description:\s*(.+)$/m)?.[1]?.trim();
  assert.ok(description, 'frontmatter must include a one-line description');
  assert.ok(description.length <= 1024, `description is ${description.length} characters; maximum is 1024`);
  assert.ok(Buffer.byteLength(description, 'utf8') <= 1024, 'description must also fit a 1024-byte runtime limit');

  for (const trigger of ['architecture', 'workflow', 'sequence', 'data-flow', 'lifecycle', 'Mermaid']) {
    assert.match(description, new RegExp(`\\b${trigger}\\b`, 'i'), `description must retain the ${trigger} trigger`);
  }
  assert.match(description, /standalone HTML/i);
  assert.match(description, /Use when/i);
});

test('literal packaged-skill path references resolve inside the installed skill root', () => {
  const references = [...skill.matchAll(/`((?:assets|bin|examples|recipes|references|renderers|schemas|scripts)\/[^`\s]+)`/g)]
    .map((match) => match[1])
    .filter((reference) => !/[<>{}*\[\]]/.test(reference));

  assert.ok(references.length > 0, 'expected literal packaged-skill references');
  for (const reference of new Set(references)) {
    assert.equal(existsSync(path.join(skillRoot, reference)), true, `SKILL.md references missing packaged path ${reference}`);
  }
});

test('main skill stays a bounded authoring router with progressive references', () => {
  const lines = skill.trimEnd().split('\n');
  assert.ok(lines.length <= 160, `SKILL.md is ${lines.length} lines; keep the entrypoint at 160 or fewer`);
  for (const reference of [
    'references/authoring-contract.md',
    'references/viewer-runtime.md',
    'references/delivery-contract.md',
  ]) {
    assert.match(skill, new RegExp(reference.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
    assert.equal(existsSync(path.join(skillRoot, reference)), true, `${reference} must ship with the skill`);
  }
});

test('update awareness is notification-only and never replaces the requested workflow', () => {
  assert.match(skill, /`scripts\/check-update\.mjs`/);
  assert.match(skill, /`silent`[\s\S]*without mentioning/i);
  assert.match(skill, /`update_available`[\s\S]*compact notice/i);
  assert.match(skill, /information, not permission/i);
  assert.match(skill, /`severity` is `security`[\s\S]*security update[\s\S]*emphasis only, never user autonomy/i);
  assert.match(skill, /continue the user's original task/i);
  assert.match(skill, /installed version unchanged/i);
  assert.doesNotMatch(skill, /npx skills update|gh skill update/i);
});

test('language behavior stays within the bounded locale contract', () => {
  assert.match(skill, /one primary authored language/);
  assert.match(skill, /explicit user choice; otherwise follow the request or conversation's dominant language/);
  assert.match(skill, /`meta\.locale` controls only renderer-owned Viewer UI/);
  assert.match(skill, /use `"en"` or `"zh-CN"`/);
  assert.match(skill, /For every other language, omit `meta\.locale`/);
  assert.match(skill, /fixed Viewer UI and `<html lang>` fall back to English/);
  assert.match(skill, /renderer never translates authored content/i);
  assert.match(skill, /product names.*code identifiers.*protocols.*API paths.*environment names/);
  assert.match(authoringContract, /`meta\.locale` controls only renderer-owned reader surfaces/);
  assert.match(authoringContract, /outside `en` and `zh-CN`/);
  assert.match(authoringContract, /artifact is\s+not fully localized/);
  assert.match(authoringContract, /Do not silently substitute\s+`zh-CN` for another language or Chinese locale/);
  assert.match(authoringContract, /It never translates authored content/);
  assert.match(authoringContract, /Renderer-owned default legend labels follow `meta\.locale`/);
  assert.match(authoringContract, /The fallback\s+applies only to renderer-owned surfaces/);
});

test('skill keeps the title hierarchy compact by default', () => {
  assert.match(skill, /Omit `meta\.subtitle` by default/);
  assert.match(skill, /Never invent a subtitle that restates the title, nodes, or cards/);
  assert.match(authoringContract, /omitted or blank subtitle must not leave an empty visual row/);
});
```

## test/stable-update-manifest.test.mjs

```js
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(here, '..', '..');
const checker = path.join(repoRoot, 'scripts', 'check-stable-update-manifest.mjs');

function git(root, args) {
  return spawnSync('git', args, { cwd: root, encoding: 'utf8' });
}

function annotatedTaggerTime(root, tag) {
  const result = git(root, [
    'for-each-ref',
    '--format=%(taggerdate:unix)',
    `refs/tags/${tag}`,
  ]);
  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout.trim(), /^\d+$/);
  return new Date(Number(result.stdout.trim()) * 1_000)
    .toISOString()
    .replace('.000Z', 'Z');
}

function writeJson(target, value) {
  fs.mkdirSync(path.dirname(target), { recursive: true });
  fs.writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`);
}

function runCheck(root, archive, extraArguments = []) {
  return spawnSync(process.execPath, [
    checker,
    '--root', root,
    '--archive', archive,
    '--tag', 'v3.0.0',
    ...extraArguments,
  ], { encoding: 'utf8' });
}

test('stable release gate binds manifest tag, tree, and final archive digest', () => {
  const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-stable-manifest-'));
  try {
    writeJson(path.join(fixture, 'archify', 'package.json'), { version: '3.0.0' });
    fs.writeFileSync(path.join(fixture, 'archify', 'SKILL.md'), 'stable fixture\n');
    assert.equal(git(fixture, ['init']).status, 0);
    assert.equal(git(fixture, ['add', 'archify']).status, 0);
    assert.equal(git(fixture, [
      '-c', 'user.name=Archify Test',
      '-c', 'user.email=archify@example.invalid',
      'commit', '-m', 'stable fixture',
    ]).status, 0);
    assert.equal(git(fixture, [
      '-c', 'user.name=Archify Test',
      '-c', 'user.email=archify@example.invalid',
      'tag', '-a', 'v3.0.0', '-m', 'stable v3.0.0',
    ]).status, 0);
    const publishedAt = annotatedTaggerTime(fixture, 'v3.0.0');
    const tree = git(fixture, ['rev-parse', 'HEAD:archify']);
    assert.equal(tree.status, 0, tree.stderr);
    const treeSha = tree.stdout.trim();
    const archive = path.join(fixture, 'archify.zip');
    const archiveBytes = Buffer.from('deterministic stable archive fixture');
    fs.writeFileSync(archive, archiveBytes);
    const artifactSha = crypto.createHash('sha256').update(archiveBytes).digest('hex');
    const manifestPath = path.join(fixture, 'docs', 'skill-updates', 'archify', 'stable.json');
    const manifest = {
      schemaVersion: 1,
      skillId: 'archify',
      channel: 'stable',
      version: '3.0.0',
      publishedAt,
      source: {
        repository: 'https://github.com/tt-a1i/archify',
        ref: 'v3.0.0',
        treeSha,
      },
      artifact: { sha256: artifactSha },
      summary: 'Stable release fixture.',
      releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v3.0.0',
      severity: 'normal',
    };
    writeJson(manifestPath, manifest);

    const passing = runCheck(fixture, archive);
    assert.equal(passing.status, 0, passing.stderr);
    assert.match(passing.stdout, /stable update manifest ok: v3\.0\.0/);

    writeJson(path.join(fixture, 'archify', 'package.json'), { version: '4.0.0-dev.0' });
    const historicalRelease = runCheck(fixture, archive, ['--source-ref', 'v3.0.0']);
    assert.equal(historicalRelease.status, 0, historicalRelease.stderr);
    const wrongHistoricalRef = runCheck(fixture, archive, ['--source-ref', 'v2.9.0']);
    assert.notEqual(wrongHistoricalRef.status, 0);
    assert.match(wrongHistoricalRef.stderr, /--source-ref must be HEAD or the exact release tag/);
    writeJson(path.join(fixture, 'archify', 'package.json'), { version: '3.0.0' });

    writeJson(manifestPath, {
      ...manifest,
      publishedAt: '2026-08-28T08:00:00+08:00',
    });
    const nonUtcTimestamp = runCheck(fixture, archive);
    assert.notEqual(nonUtcTimestamp.status, 0);
    assert.match(nonUtcTimestamp.stderr, /stable update manifest identity/);
    writeJson(manifestPath, manifest);

    writeJson(manifestPath, {
      ...manifest,
      publishedAt: new Date(Date.parse(publishedAt) + 1_000)
        .toISOString()
        .replace('.000Z', 'Z'),
    });
    const wrongTaggerTime = runCheck(fixture, archive);
    assert.notEqual(wrongTaggerTime.status, 0);
    assert.match(wrongTaggerTime.stderr, /publishedAt .* annotated tagger time/);
    writeJson(manifestPath, manifest);

    const invalidContracts = [
      { ...manifest, extra: true },
      { ...manifest, source: { ...manifest.source, extra: true } },
      { ...manifest, artifact: { ...manifest.artifact, extra: true } },
      { ...manifest, summary: '\u202eunsafe' },
      { ...manifest, severity: 'urgent' },
    ];
    for (const invalid of invalidContracts) {
      writeJson(manifestPath, invalid);
      const rejected = runCheck(fixture, archive);
      assert.notEqual(rejected.status, 0);
      assert.match(rejected.stderr, /stable update manifest identity/);
    }
    writeJson(manifestPath, manifest);

    writeJson(manifestPath, null);
    const nullManifest = runCheck(fixture, archive);
    assert.notEqual(nullManifest.status, 0);
    assert.match(nullManifest.stderr, /stable update manifest identity/);
    assert.doesNotMatch(nullManifest.stderr, /TypeError|check-stable-update-manifest\.mjs:\d+/);
    writeJson(manifestPath, manifest);

    fs.appendFileSync(archive, 'tampered');
    const archiveMismatch = runCheck(fixture, archive);
    assert.notEqual(archiveMismatch.status, 0);
    assert.match(archiveMismatch.stderr, /archive sha256 .* does not match/);

    fs.writeFileSync(archive, archiveBytes);
    writeJson(manifestPath, {
      ...manifest,
      source: { ...manifest.source, treeSha: 'c'.repeat(40) },
    });
    const treeMismatch = runCheck(fixture, archive);
    assert.notEqual(treeMismatch.status, 0);
    assert.match(treeMismatch.stderr, /treeSha .* does not match HEAD:archify/);

    writeJson(manifestPath, manifest);
    assert.equal(git(fixture, ['tag', '-d', 'v3.0.0']).status, 0);
    assert.equal(git(fixture, ['tag', 'v3.0.0']).status, 0);
    const lightweightTag = runCheck(fixture, archive);
    assert.notEqual(lightweightTag.status, 0);
    assert.match(lightweightTag.stderr, /must be an annotated tag/);
  } finally {
    fs.rmSync(fixture, { recursive: true, force: true });
  }
});
```

## test/start-page.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import vm from 'node:vm';
import { fileURLToPath } from 'node:url';
import { SCENARIO_RECIPES, startPromptsFor } from '../recipes/scenarios.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const repoRoot = path.resolve(skillRoot, '..');

class FakeElement {
  constructor({ id = '', textContent = '', dataset = {} } = {}) {
    this.id = id;
    this.textContent = textContent;
    this.dataset = dataset;
    this.style = {};
    this.attributes = {};
    this.listeners = {};
    this.tabIndex = 0;
  }

  setAttribute(name, value) { this.attributes[name] = String(value); }
  getAttribute(name) { return this.attributes[name]; }
  addEventListener(name, listener) { this.listeners[name] = listener; }
  replaceChildren(...children) { this.children = children; }
  appendChild() {}
  remove() {}
  select() {}
  focus() { this.focused = true; }
  click() { return this.listeners.click?.({ preventDefault() {} }); }
  dispatchKey(key) { return this.listeners.keydown?.({ key, preventDefault() {} }); }
}

function executeStartPage(html) {
  const dataMatch = html.match(/<script id="start-data" type="application\/json">([\s\S]*?)<\/script>/);
  const scriptMatch = html.match(/<script>\n([\s\S]*?)\n  <\/script>\n<\/body>/);
  assert.ok(dataMatch);
  assert.ok(scriptMatch);

  const ids = Object.fromEntries([
    'recipe-title', 'recipe-question', 'recipe-prompt', 'include-list', 'proof-link',
    'proof-meta', 'copy-status', 'language', 'agent-state', 'install-command',
    'project-command', 'copy-prompt', 'copy-starter',
  ].map((id) => [id, new FakeElement({ id })]));
  ids['start-data'] = new FakeElement({ id: 'start-data', textContent: dataMatch[1] });

  const types = ['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']
    .map((type) => new FakeElement({ dataset: { type } }));
  const agents = ['cursor', 'codex', 'claude-code', 'opencode']
    .map((agent) => new FakeElement({ textContent: agent === 'codex' ? 'Codex' : agent, dataset: { agent } }));
  const inputs = ['description', 'repository']
    .map((input) => new FakeElement({ dataset: { input } }));
  const copySources = ['install-command', 'project-command']
    .map((copySource) => new FakeElement({ dataset: { copySource } }));
  const copied = [];
  const stored = new Map();
  let replacedUrl = '';
  const document = {
    documentElement: {},
    body: { appendChild() {} },
    getElementById(id) { return ids[id]; },
    createElement() { return new FakeElement(); },
    execCommand() { return true; },
    querySelector(selector) {
      const match = selector.match(/^\[data-agent="([^"]+)"\]$/);
      return match ? agents.find((element) => element.dataset.agent === match[1]) : null;
    },
    querySelectorAll(selector) {
      if (selector === '[data-type]') return types;
      if (selector === '[data-agent]') return agents;
      if (selector === '[data-input]') return inputs;
      if (selector === '[data-copy-source]') return copySources;
      if (selector === '[data-en][data-zh]') return [];
      return [];
    },
  };
  const window = {
    location: { href: 'https://example.test/start.html', search: '', pathname: '/start.html' },
    isSecureContext: true,
    dispatchEvent() {},
    ArchifySiteLanguage: {
      read() { return 'en'; },
      write(value) { return value; },
    },
  };
  const context = {
    window,
    ArchifySiteLanguage: window.ArchifySiteLanguage,
    document,
    navigator: { languages: ['en'], language: 'en', clipboard: { async writeText(value) { copied.push(value); } } },
    history: { replaceState(_state, _title, url) { replacedUrl = url; } },
    sessionStorage: {
      getItem(key) { return stored.get(key) ?? null; },
      setItem(key, value) { stored.set(key, value); },
    },
    CustomEvent: class { constructor(name, options) { this.name = name; this.detail = options.detail; } },
    URL,
    URLSearchParams,
    Set,
    Array,
    JSON,
    encodeURIComponent,
  };
  vm.createContext(context);
  new vm.Script(scriptMatch[1]).runInContext(context);
  return { data: JSON.parse(dataMatch[1]), ids, inputs, copied, window, getUrl: () => replacedUrl };
}

test('start page: checked-in HTML is reproducible from canonical scenario recipes', () => {
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-start-page-'));
  const generated = path.join(tmp, 'start.html');
  try {
    execFileSync(process.execPath, [path.join(repoRoot, 'scripts/build-start.mjs'), generated]);
    assert.equal(
      fs.readFileSync(generated, 'utf8'),
      fs.readFileSync(path.join(repoRoot, 'docs/start.html'), 'utf8'),
    );
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('start page: offers five bounded bilingual starts without ingesting source content', () => {
  const html = fs.readFileSync(path.join(repoRoot, 'docs/start.html'), 'utf8');
  assert.doesNotMatch(html, /\[\[[A-Z0-9_]+\]\]/);
  assert.match(html, /npx -y skills add tt-a1i\/archify --skill archify --agent codex --global --copy --yes/);
  assert.match(html, /npx -y skills add tt-a1i\/archify --skill archify --agent codex --copy --yes/);
  for (const agent of ['cursor', 'codex', 'claude-code', 'opencode']) {
    assert.match(html, new RegExp(`role="tab" data-agent="${agent}"`));
  }
  assert.match(html, /data-en="Describe it\."/);
  assert.match(html, /data-en="Archify maps it\."/);
  assert.match(html, /data-zh="直接说，"/);
  assert.match(html, /data-zh="Archify 就能画。"/);
  assert.match(html, /id="copy-starter"/);
  assert.match(html, /data-en="Copy install \+ prompt"/);
  assert.match(html, /data-zh="复制安装命令 \+ 提示词"/);
  assert.match(html, /data-en="No repository is required\./);
  assert.match(html, /data-zh="不需要绑定代码库。/);
  assert.match(html, /data-input="description"/);
  assert.match(html, /data-input="repository"/);

  const dataMatch = html.match(/<script id="start-data" type="application\/json">([\s\S]*?)<\/script>/);
  assert.ok(dataMatch);
  const data = JSON.parse(dataMatch[1]);
  assert.deepEqual(Object.keys(data), ['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']);
  assert.ok(Object.values(data).every((entry) => entry.en.prompt && entry.zh.prompt && entry.en.descriptionPrompt && entry.zh.descriptionPrompt && entry.en.repositoryPrompt && entry.zh.repositoryPrompt && entry.proof));

  const scriptMatch = html.match(/<script>\n([\s\S]*?)\n  <\/script>\n<\/body>/);
  assert.ok(scriptMatch);
  assert.doesNotThrow(() => new vm.Script(scriptMatch[1]));
  assert.match(scriptMatch[1], /KNOWN_TYPES\.has\(requestedType\)/);
  assert.match(scriptMatch[1], /KNOWN_AGENTS\.has\(requestedAgent\)/);
  assert.match(scriptMatch[1], /KNOWN_SOURCES\.has\(requestedSource\)/);
  assert.match(scriptMatch[1], /KNOWN_INPUTS\.has\(requestedInput\)/);
  assert.match(scriptMatch[1], /next\.searchParams\.set\('agent', agent\)/);
  assert.match(scriptMatch[1], /next\.searchParams\.set\('source', source\)/);
  assert.match(scriptMatch[1], /next\.searchParams\.set\('input', input\)/);
  assert.match(scriptMatch[1], /next\.searchParams\.delete\('lang'\)/);
  assert.match(scriptMatch[1], /--agent ' \+ agent \+ ' --global --copy --yes/);
  assert.match(scriptMatch[1], /--agent ' \+ agent \+ ' --copy --yes/);
  assert.match(scriptMatch[1], /function starterText\(\)/);
  assert.match(scriptMatch[1], /archify:start-funnel/);
  assert.match(scriptMatch[1], /archify\.start\.events\.v1/);
  assert.doesNotMatch(scriptMatch[1], /fetch\(|sendBeacon\(|XMLHttpRequest/);
  assert.match(scriptMatch[1], /textContent/);
  assert.match(scriptMatch[1], /replaceChildren/);
  assert.doesNotMatch(scriptMatch[1], /innerHTML/);
});

test('start page: canonical recipes own description and repository prompt variants', () => {
  const selected = new Map([
    ['architecture', 'system-overview'],
    ['workflow', 'agent-tool-call'],
    ['sequence', 'api-request'],
    ['dataflow', 'event-stream'],
    ['lifecycle', 'object-lifecycle'],
  ]);
  for (const [type, id] of selected) {
    const recipe = SCENARIO_RECIPES.find((candidate) => candidate.id === id);
    assert.equal(recipe?.type, type);
    for (const language of ['en', 'zh']) {
      const prompts = startPromptsFor(recipe, language);
      assert.equal(prompts.descriptionPrompt, recipe.start[language].descriptionPrompt);
      assert.ok(prompts.repositoryPrompt.toLowerCase().includes(recipe[language].prompt.toLowerCase()));
    }
  }
});

test('start page: input mode drives rendered prompt, copy, keyboard, and URL without changing event schema', async () => {
  const html = fs.readFileSync(path.join(repoRoot, 'docs/start.html'), 'utf8');
  const page = executeStartPage(html);
  const descriptionPrompt = page.data.architecture.en.descriptionPrompt;
  const repositoryPrompt = page.data.architecture.en.repositoryPrompt;

  assert.equal(page.inputs[0].getAttribute('aria-selected'), 'true');
  assert.equal(page.inputs[1].getAttribute('aria-selected'), 'false');
  assert.equal(page.ids['recipe-prompt'].textContent, descriptionPrompt);
  assert.equal(new URL(page.getUrl(), 'https://example.test').searchParams.get('input'), 'description');

  page.inputs[1].click();
  assert.equal(page.inputs[1].getAttribute('aria-selected'), 'true');
  assert.equal(page.ids['recipe-prompt'].textContent, repositoryPrompt);
  assert.equal(new URL(page.getUrl(), 'https://example.test').searchParams.get('input'), 'repository');

  page.ids['copy-prompt'].click();
  await new Promise((resolve) => setImmediate(resolve));
  assert.equal(page.copied.at(-1), repositoryPrompt);

  page.inputs[1].dispatchKey('ArrowLeft');
  assert.equal(page.inputs[0].getAttribute('aria-selected'), 'true');
  assert.equal(page.inputs[0].focused, true);
  assert.equal(page.ids['recipe-prompt'].textContent, descriptionPrompt);

  page.ids['copy-starter'].click();
  await new Promise((resolve) => setImmediate(resolve));
  assert.match(page.copied.at(-1), /Then start any new chat and tell Codex:/);
  assert.ok(page.copied.at(-1).endsWith(descriptionPrompt));

  const [viewEvent, promptEvent, starterEvent] = page.window.ArchifyStartMetrics.snapshot();
  for (const event of [viewEvent, promptEvent, starterEvent]) {
    assert.deepEqual(Object.keys(event), ['schemaVersion', 'step', 'source', 'type', 'agent', 'language']);
    assert.equal('input' in event, false);
  }
  assert.deepEqual([viewEvent.step, promptEvent.step, starterEvent.step], ['start_view', 'prompt_copy', 'starter_copy']);
});

test('generated artifacts omit the promotional footer and shortcut manual', () => {
  const examples = {
    architecture: 'web-app.architecture.json',
    workflow: 'agent-tool-call.workflow.json',
    sequence: 'cache-miss-request.sequence.json',
    dataflow: 'product-analytics.dataflow.json',
    lifecycle: 'agent-run.lifecycle.json',
  };
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-start-artifacts-'));
  try {
    for (const [type, input] of Object.entries(examples)) {
      const out = path.join(tmp, `${type}.html`);
      execFileSync(process.execPath, [
        path.join(skillRoot, `renderers/${type}/render-${type}.mjs`),
        path.join(skillRoot, 'examples', input),
        out,
      ]);
      const html = fs.readFileSync(out, 'utf8');
      assert.doesNotMatch(html, /<p class="footer">/, `${type}: footer element`);
      assert.doesNotMatch(html, /Built with Archify/, `${type}: product signature`);
      assert.doesNotMatch(html, /Create yours/, `${type}: promotional CTA`);
      assert.doesNotMatch(html, /Hover to trace/, `${type}: shortcut manual`);
      assert.doesNotMatch(html, /source=artifact/, `${type}: removed artifact CTA URL`);
      assert.match(html, /id="btn-diagram-guide"/, `${type}: diagram guide remains available`);

      const svg = html.match(/<svg[\s\S]*?<\/svg>/)?.[0];
      assert.ok(svg, `${type}: SVG missing`);
    }
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('viewer gives wide screens a larger canvas without forcing a subtitle row', () => {
  const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
  assert.match(template, /max-width: var\(--archify-reader-width, 1440px\)/);
  assert.match(template, /Archify\.readerLayout = \(function \(\)/);

  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-title-hierarchy-'));
  try {
    const input = JSON.parse(fs.readFileSync(
      path.join(skillRoot, 'examples', 'web-app.architecture.json'),
      'utf8',
    ));
    delete input.meta.subtitle;
    const source = path.join(tmp, 'without-subtitle.architecture.json');
    const output = path.join(tmp, 'without-subtitle.html');
    fs.writeFileSync(source, `${JSON.stringify(input, null, 2)}\n`);
    execFileSync(process.execPath, [
      path.join(skillRoot, 'renderers', 'architecture', 'render-architecture.mjs'),
      source,
      output,
    ]);
    assert.doesNotMatch(fs.readFileSync(output, 'utf8'), /class="subtitle"/);
  } finally {
    fs.rmSync(tmp, { recursive: true, force: true });
  }
});

test('artifact-to-install measurement plan separates observable funnel steps from first-diagram success', () => {
  const plan = fs.readFileSync(
    path.join(repoRoot, 'docs/artifact-install-v2-measurement.md'),
    'utf8',
  );

  for (const required of [
    'start_view',
    'starter_copy',
    'global_install_copy',
    'project_install_copy',
    'prompt_copy',
    'proof_open',
    'starter_copy / start_view',
    'First-diagram success is not observable from this static page',
    'No network request',
    'source=artifact',
    'source=gallery',
  ]) {
    assert.match(plan, new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), required);
  }
});
```

## test/story-beat-navigator.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-story-beat-navigator-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ], { encoding: 'utf8' });
  return { result, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

test('all five renderers inherit native inspectable Story Beat controls without changing canonical SVG', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const { result, html } = render(mode, example);
    assert.equal(result.status, 0, result.stderr);
    assert.match(html, /var stop = document\.createElement\('button'\)/);
    assert.match(html, /stop\.type = 'button'/);
    assert.match(html, /stop\.setAttribute\('aria-label', storyBeatAria\(step, storySteps\.length\)\)/);
    assert.match(html, /stop\.setAttribute\('aria-current', 'step'\)/);
    assert.doesNotMatch(html, /stop\.setAttribute\('aria-pressed'/);
    const generatedSvg = html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
    assert.doesNotMatch(generatedSvg, /data-story-(?:active|playing|beat|step|overlay|pulse)/);
  }
});

test('adjacent stable IDs classify start, forward, reverse, group, and multiple without inferred cross-links', () => {
  assert.match(template, /function storyStep\(view, index, edgeList, byId\)/);
  assert.match(template, /from === previousId && to === id/);
  assert.match(template, /from === id && to === previousId/);
  assert.match(template, /edges\.length === 1 && forward\.length === 1 \? 'forward'/);
  assert.match(template, /edges\.length === 1 && reverse\.length === 1 \? 'reverse' : 'multiple'/);
  assert.match(template, /index === 0 \? 'start' : \(!edges\.length \? 'group'/);
  assert.match(template, /storySteps\.forEach\(function \(step\) \{\s*step\.edges\.forEach/);
  assert.doesNotMatch(template, /var storyOrder = \{\}/);
  assert.doesNotMatch(template, /Math\.max\(storyOrder/);
});

test('focus pauses without selection while native activation pins one beat and preserves chapter and link ownership', () => {
  assert.match(template, /trail\.addEventListener\('focusin'[\s\S]*if \(playing\) pausePlayback\(\)/);
  assert.match(template, /trail\.addEventListener\('click'[\s\S]*selectStoryBeat\(Number\(stop\.getAttribute\('data-story-index'\)\)\)/);
  assert.match(template, /trail\.addEventListener\('keydown'[\s\S]*event\.key !== 'Enter' && event\.key !== ' '[\s\S]*event\.preventDefault\(\)/);
  assert.match(template, /function selectStoryBeat\(index\)/);
  assert.match(template, /setStoryBeat\(index, \{ manual: true, center: true, pulse: true, follow: true \}\)/);
  assert.match(template, /stop\.setAttribute\('aria-current', 'step'\)/);
  assert.match(template, /else stop\.removeAttribute\('aria-current'\)/);
  assert.match(template, /trail\.scrollLeft = target/);
  const selection = template.match(/function selectStoryBeat\(index\) \{([\s\S]*?)\n      function updateUrl/)?.[1] || '';
  assert.match(selection, /updateUrl: false/);
  assert.doesNotMatch(selection, /history\.|location\.|scrollIntoView/);
  assert.match(template, /beat: function \(\)[\s\S]*edgeKeys: step\.edgeKeys\.slice\(\)/);
});

test('one generation-owned scheduler resumes remaining dwell and finite exact-edge signals never loop', () => {
  assert.equal((template.match(/storyBeatTimer = setTimeout/g) || []).length, 1);
  assert.doesNotMatch(template, /storyBeatTimer = setInterval/);
  assert.match(template, /storyPlaybackGeneration \+= 1/);
  assert.match(template, /generation !== storyPlaybackGeneration/);
  assert.match(template, /preserveElapsed: options\.complete !== true/);
  assert.match(template, /storyBeatDwellMs - storyBeatElapsedMs/);
  assert.match(template, /afterHandoff\(function \(\)[\s\S]*scheduleStoryPlayback\(\)/);
  assert.match(template, /animation: archify-story-flow 0\.78s linear 1 both/);
  assert.doesNotMatch(template, /archify-story-flow 0\.78s linear infinite/);
  assert.match(template, /svg\[data-preset="blueprint"\]\[data-story-beat\] \[data-story-step\]\[data-story-beat-state="active"\] \{\s*filter: none;\s*animation: none;/);
  assert.match(template, /step\.relation !== 'forward' && step\.relation !== 'reverse'/);
  assert.match(template, /step\.edges\.length !== 1/);
  assert.match(template, /addEventListener\('animationend'[\s\S]*clearStoryPulse/);
});

test('target size, reduced motion, print, embed, and export keep Story Beats viewer-only', () => {
  assert.match(template, /\.guided-view-trail \.guided-view-stop \{[\s\S]*min-height: 1\.5rem/);
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*\.guided-view-trail \.guided-view-stop \{[\s\S]*min-height: 2rem/);
  assert.match(template, /touch-action: pan-x/);
  assert.match(template, /document\.documentElement\.getAttribute\('data-embed'\) !== 'true' && typeof MutationObserver !== 'undefined' && typeof Node !== 'undefined' && svg instanceof Node && document\.documentElement instanceof Node/);
  assert.match(template, /@media print[\s\S]*\.story-trail-overlay,\s*\.story-carrier-overlay \{ display: none !important; \}/);
  assert.match(template, /html\[data-embed="true"\][\s\S]*\.guided-views/);
  assert.match(template, /html\[data-motion="still"\] \.story-trail-flow/);
  assert.match(template, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.story-trail-flow/);
  assert.match(template, /clone\.querySelectorAll\('\[data-story-overlay\], \[data-story-carrier-overlay\]'\)/);
  assert.match(template, /clone\.querySelectorAll\('\[data-story-step\], \[data-story-beat-state\], \[data-story-beat-step\]'\)/);
  assert.match(template, /canonicalStateClean[\s\S]*data-story-beat-step/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/story-carrier.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-story-carrier-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one viewer-only Semantic Story Carrier', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /Archify\.flowTokens = \{/, mode);
    assert.match(html, /className: 'story-flow-token'/, mode);
    assert.match(html, /data-story-carrier-token/, mode);
    assert.match(html, /animation: archify-relationship-token-life 0\.78s linear 1 both/, mode);
    assert.doesNotMatch(canonicalSvg(html), /story-flow-token|story-carrier-token|semantic-flow-token/, mode);
  }
});

test('Story deduplicates SVG path and label fragments by stable authored edge key', () => {
  assert.match(template, /function uniqueStoryEdges\(edgeList\)/);
  assert.match(template, /var key = storyEdgeKey\(edge\)/);
  assert.match(template, /Object\.prototype\.hasOwnProperty\.call\(positions, key\)/);
  assert.match(template, /!storyGeometry\(unique\[index\]\)\.length && storyGeometry\(edge\)\.length/);
  assert.match(template, /forward = uniqueStoryEdges\(forward\)/);
  assert.match(template, /reverse = uniqueStoryEdges\(reverse\)/);
  assert.match(template, /edges\.length === 1 && forward\.length === 1 \? 'forward'/);
});

test('Story reuses the exact semantic token vocabulary on its existing finite edge pulse', () => {
  assert.match(template, /function createSemanticFlowToken\(edge, shape, options\)/);
  assert.match(template, /relationshipTokenGeometry\(shape, relationshipTokenKind\(edge\), key, options\)/);
  assert.match(template, /Archify\.flowTokens\.create\(edge, shapes\[0\], \{/);
  assert.match(template, /className: 'story-flow-token'/);
  assert.match(template, /duration: '0\.78s'/);
  assert.match(template, /carrier\.setAttribute\('data-story-beat-step', String\(step\.index\)\)/);
  assert.match(template, /carrierOverlay\.setAttribute\('data-story-carrier-overlay', ''\)/);
  assert.match(template, /carrierWrapper\.appendChild\(carrier\)/);
  assert.match(template, /svg\.insertBefore\(carrierOverlay, firstNode\)/);
  assert.match(template, /semantic-flow-token-halo/);
  assert.doesNotMatch(template, /story-flow-token[^}]+infinite/);
});

test('only explicit play=1 embeds may show the finite carrier', () => {
  assert.match(template, /data-embed'\) === 'true' &&\s*document\.documentElement\.getAttribute\('data-share-playback'\) !== 'true'/);
  assert.match(template, /autoplayPending = sharePlaybackRequested\(\)/);
  assert.match(template, /document\.documentElement\.setAttribute\('data-share-playback', 'true'\)/);
  assert.match(template, /html\[data-motion="still"\] \.story-carrier-overlay/);
  assert.match(template, /html\[data-document-hidden="true"\] \.story-carrier-overlay/);
  assert.match(template, /@media \(prefers-reduced-motion: reduce\)[\s\S]+\.story-carrier-overlay \{ display: none !important; \}/);
});

test('Story Carrier cleanup and export remain owned by Story Trail', () => {
  assert.match(template, /svg\.querySelectorAll\('\[data-story-carrier-overlay\]'\)/);
  assert.match(template, /overlay\.remove\(\)/);
  assert.match(template, /var pulseGeneration = storyPulseGeneration/);
  assert.match(template, /if \(pulseGeneration === storyPulseGeneration\) clearStoryPulse\(\)/);
  assert.equal((template.match(/storyPulseOwnerToken = Archify\.motionGovernor\.claim\('story'/g) || []).length, 1);
  assert.match(template, /clone\.querySelectorAll\('\[data-story-overlay\], \[data-story-carrier-overlay\]'\)/);
  assert.match(template, /@media print \{[\s\S]+\.story-trail-overlay,[\s\S]+\.story-carrier-overlay \{ display: none !important; \}/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/story-director-strip.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-story-director-strip-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one viewer-only Story Director Strip', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /id="guided-story-caption" hidden aria-live="polite" aria-atomic="true"/, mode);
    assert.match(html, /function renderStoryCaption\(step, total, nextStep\)/, mode);
    assert.match(html, /storyCaptionRoute\.textContent = storyCaptionRouteCopy\(step\)/, mode);
    assert.match(html, /storyCaptionDetail\.textContent = storyCaptionDetailCopy\(step\)/, mode);
    assert.doesNotMatch(canonicalSvg(html), /guided-story-caption|data-story-caption/, mode);
  }
});

test('captions derive only authored edge labels and existing node facts', () => {
  assert.match(template, /edgeLabels: edges\.map\(function \(edge\) \{ return edge\.getAttribute\('data-edge-label'\) \|\| ''; \}\)/);
  assert.match(template, /responsibility: node \? \(node\.getAttribute\('data-node-sublabel'\) \|\| ''\) : ''/);
  assert.match(template, /context: node \? \(node\.getAttribute\('data-node-context'\) \|\| ''\) : ''/);
  assert.match(template, /step\.edgeLabels\.slice\(0, 3\)\.join\(' \+ '\)/);
  assert.match(template, /viewerText\('viewer\.guided\.caption\.grouped'\)/);
  assert.match(template, /if \(!facts\.length\) facts\.push\(viewerText\('viewer\.guided\.caption\.starting'\)\)/);
  assert.match(template, /viewerText\('viewer\.guided\.caption\.direction'/);
  assert.doesNotMatch(template, /inferred relationship|likely transition|calls service/);
});

test('route copy preserves start, forward, reverse, multiple, and grouped semantics', () => {
  assert.match(template, /viewerText\('viewer\.guided\.beat\.start'/);
  assert.match(template, /viewerText\('viewer\.guided\.beat\.forward'/);
  assert.match(template, /viewerText\('viewer\.guided\.beat\.reverse'/);
  assert.match(template, /viewerText\('viewer\.guided\.beat\.multiple'/);
  assert.match(template, /viewerText\('viewer\.guided\.beat\.group'/);
});

test('playback announcements and motion remain reader-controlled', () => {
  assert.match(template, /storyCaption\.setAttribute\('aria-live', playing \? 'off' : 'polite'\)/);
  assert.match(template, /html\[data-motion="still"\] \.guided-story-caption/);
  assert.match(template, /@media \(prefers-reduced-motion: reduce\) \{\s*\.guided-story-caption \{ animation: none !important; \}/);
  assert.match(template, /animation: archify-story-caption-in 140ms/);
});

test('Presentation playback removes secondary chrome without hiding Pause or navigation', () => {
  assert.match(template, /\.guided-views\[data-story-beat\] \.guided-view-copy > #guided-view-label/);
  assert.match(template, /@media \(min-width: 721px\)[\s\S]*\.guided-views\[data-playing="true"\] \.guided-view-index/);
  assert.match(template, /\.guided-views\[data-playing="true"\] \.guided-view-beat-link/);
  assert.match(template, /\.guided-views\[data-playing="true"\] \.guided-view-all/);
  const presentationRule = template.match(/@media \(min-width: 721px\) \{([\s\S]*?)\n    \}/)?.[1] || '';
  assert.doesNotMatch(presentationRule, /guided-view-play/);
  assert.doesNotMatch(presentationRule, /guided-view-prev|guided-view-next/);
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*\.guided-views > #guided-view-prev,[\s\S]*height: 2\.75rem/);
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*\.guided-view-play,[\s\S]*\.guided-view-beat-link \{ min-height: 2\.75rem; \}/);
});

test('embed, print, and canonical export boundaries stay clean', () => {
  assert.match(template, /html\[data-embed="true"\] \.guided-views \{ display: none !important; \}/);
  assert.match(template, /@media print[\s\S]*\.guided-views/);
  assert.doesNotMatch(canonicalSvg(render('workflow', CASES.workflow)), /Story Director|guided-story-caption|data-story-caption/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/story-follow-camera.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-story-follow-camera-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one viewer-only Story Follow Camera', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const html = render(mode, example);
    assert.match(html, /function followStoryStep\(step, options\)/, mode);
    assert.match(html, /panel\.setAttribute\('data-story-follow', 'moving'\)/, mode);
    assert.match(html, /panel\.setAttribute\('data-story-follow-node', step\.nodeId\)/, mode);
    assert.doesNotMatch(canonicalSvg(html), /data-story-follow/, mode);
  }
});

test('Story Follow frames the exact previous, current, and next authored stops through the shared camera', () => {
  assert.match(template, /function storyFrameIds\(step\)/);
  assert.match(template, /step\.index > 0 && storySteps\[step\.index - 1\]/);
  assert.match(template, /ids\.push\(step\.nodeId\)/);
  assert.match(template, /step\.index \+ 1 < storySteps\.length/);
  assert.match(template, /var ids = storyFrameIds\(step\)/);
  assert.match(template, /Archify\.view\.reveal\(ids, \{/);
  assert.match(template, /reason: options\.manual === true \? 'story-beat' : 'story-follow'/);
  assert.match(template, /padding: 64/);
  assert.match(template, /maxScale: 1\.65/);
  assert.match(template, /duration: STORY_FOLLOW_DURATION_MS/);
  assert.match(template, /storyFollowGeneration/);
  assert.match(template, /generation !== storyFollowGeneration \|\| storyBeatIndex !== step\.index/);
});

test('playback and deliberate beat activation follow while stable moment restoration stays calm', () => {
  assert.match(template, /setStoryBeat\(0, \{ pulse: true, follow: true \}\)/);
  assert.match(template, /setStoryBeat\(storyBeatIndex \+ 1, \{ pulse: true, follow: true \}\)/);
  assert.match(template, /if \(storyBeatIndex >= 0\) followStoryStep\(storySteps\[storyBeatIndex\]\)/);
  assert.match(template, /setStoryBeat\(index, \{ manual: true, center: true, pulse: true, follow: true \}\)/);
  assert.match(template, /follow: options\.follow === true/);
  assert.match(template, /selectStoryBeatById\(requestedBeat, \{ linked: true, follow: true, followInstant: true \}\)/);
  assert.match(template, /if \(embed && !explicitEmbedPlayback && options\.linked !== true\) return false/);
  assert.match(template, /var deferredGeneration = \+\+storyFollowGeneration/);
  assert.match(template, /requestAnimationFrame\(function \(\) \{[\s\S]*?storyBeatIndex !== deferredIndex[\s\S]*?followStoryStep\(step, options\)/);
});

test('adaptive dwell, Still, reduced motion, hidden pages, and print keep camera motion bounded', () => {
  assert.match(template, /var STORY_FOLLOW_MIN_DWELL_MS = 1100/);
  assert.match(template, /var STORY_FOLLOW_DURATION_MS = 320/);
  assert.match(template, /Math\.max\(STORY_FOLLOW_MIN_DWELL_MS, VIEW_INTERVAL_MS \/ Math\.max\(1, total\)\)/);
  assert.match(template, /storyBeatDwellMs = storyBeatDwell\(total\)/);
  assert.match(template, /if \(!step \|\| document\.hidden/);
  assert.match(template, /window\.matchMedia\('print'\)\.matches/);
  assert.match(template, /instant: options\.instant === true \|\| reducedMotion\(\) \|\| document\.documentElement\.getAttribute\('data-motion'\) !== 'live'/);
  assert.match(template, /function storyAutomaticPlaybackAllowed\(\)/);
  assert.match(template, /Archify\.motionGovernor && Archify\.motionGovernor\.capable\) return !Archify\.motionGovernor\.isPaused\(\)/);
  assert.match(template, /play\.disabled = !playing && !automaticPlaybackAllowed/);
  assert.match(template, /'viewer\.guided\.motionUnavailable'/);
  assert.match(template, /function startPlayback\(\) \{[\s\S]*?if \(!storyAutomaticPlaybackAllowed\(\)\)/);
  assert.match(template, /if \(shouldPlay && svg\.getAttribute\('data-story-playing'\) !== 'true'\)/);
  assert.match(template, /else if \(!shouldPlay && svg\.hasAttribute\('data-story-playing'\)\)/);
  assert.doesNotMatch(template, /storyFollowTimer = setInterval/);
});

test('pause, settle, overview, and manual camera takeover cancel Story Follow state', () => {
  assert.match(template, /function clearStoryFollow\(\)/);
  assert.match(template, /svg\.removeAttribute\('data-story-follow'\)/);
  assert.match(template, /panel\.removeAttribute\('data-story-follow'\)/);
  assert.match(template, /function pausePlayback\(options\)[\s\S]*?clearStoryFollow\(\)/);
  assert.match(template, /function settleStoryBeats\(\)[\s\S]*?clearStoryFollow\(\)/);
  assert.match(template, /function clearStoryTrail\(\)[\s\S]*?clearStoryFollow\(\)/);
  assert.match(template, /function interruptCamera\(reason\)[\s\S]*?Archify\.guidedViews\.pause\(\)/);
  assert.match(template, /clone\.removeAttribute\('data-story-follow'\)/);
  assert.match(template, /!clone\.hasAttribute\('data-story-follow'\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/story-horizon.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-story-horizon-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ], { encoding: 'utf8' });
  return { result, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

test('all five renderers inherit one viewer-only Story Horizon', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const { result, html } = render(mode, example);
    assert.equal(result.status, 0, result.stderr);
    assert.match(html, /data-story-beat-state="next"/, mode);
    assert.match(html, /step === storyBeatIndex \+ 1/, mode);
    assert.match(html, /svg\.setAttribute\('data-story-next', nextStep\.nodeId\)/, mode);
    assert.match(html, /panel\.setAttribute\('data-story-next', nextStep\.nodeId\)/, mode);
    assert.match(html, /id="guided-story-caption-next" hidden aria-hidden="true"/, mode);
    const generatedSvg = html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
    assert.doesNotMatch(generatedSvg, /data-story-next|data-story-beat-state="next"/, mode);
  }
});

test('the temporal hierarchy has one bounded next state and a clean final beat', () => {
  assert.match(template, /if \(step < storyBeatIndex\) return 'past'/);
  assert.match(template, /if \(step === storyBeatIndex\) return 'active'/);
  assert.match(template, /if \(step === storyBeatIndex \+ 1\) return 'next'/);
  assert.match(template, /return 'pending'/);
  assert.match(template, /storyBeatIndex \+ 1 < storySteps\.length \? storySteps\[storyBeatIndex \+ 1\] : null/);
  assert.match(template, /else \{\s*svg\.removeAttribute\('data-story-next'\);\s*panel\.removeAttribute\('data-story-next'\)/);
  assert.match(template, /storyCaptionNext\.hidden = !nextStep/);
});

test('next edges reuse exact authored step membership without synthesizing topology', () => {
  assert.match(template, /storySteps\.forEach\(function \(step\) \{\s*step\.edges\.forEach/);
  assert.match(template, /edge\.setAttribute\('data-story-beat-step', String\(edgeBeat\)\)/);
  assert.match(template, /edge\.setAttribute\('data-story-beat-state', storyBeatState\(step\)\)/);
  assert.match(template, /index === 0 \? 'start' : \(!edges\.length \? 'group'/);
  assert.match(template, /edges\.length === 1 && forward\.length === 1 \? 'forward'/);
  assert.match(template, /edges\.length === 1 && reverse\.length === 1 \? 'reverse' : 'multiple'/);
  assert.doesNotMatch(template, /createElementNS\([^\n]+story-horizon|data-story-horizon-edge/);
});

test('next remains static, subordinate, preset-safe, and mobile-height neutral', () => {
  assert.match(template, /data-story-step\]\[data-story-beat-state="next"\] \{\s*opacity: 0\.5;\s*filter: saturate\(0\.66\)/);
  assert.match(template, /data-story-beat-state="past"\] \{\s*opacity: 0\.72/);
  assert.match(template, /data-edge-from\]\[data-story-beat-step\]\[data-story-beat-state="next"\] \{ opacity: 0\.34; \}/);
  assert.match(template, /guided-view-stop\[data-story-beat-state="next"\][\s\S]*border-style: dashed/);
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*\.guided-story-caption-next \{ display: none; \}/);
  const nextRules = template.match(/[^\n{]*data-story-beat-state="next"[^\n{]*\{[^}]*\}/g)?.join('\n') || '';
  assert.doesNotMatch(nextRules, /animation:|drop-shadow|stroke-dasharray/);
});

test('Still, accessibility, teardown, and export preserve the product boundary', () => {
  assert.match(template, /id="guided-story-caption-next" hidden aria-hidden="true"/);
  assert.match(template, /storyCaption\.setAttribute\('aria-live', playing \? 'off' : 'polite'\)/);
  assert.doesNotMatch(template, /storyCaptionNext\.setAttribute\('aria-live'/);
  assert.match(template, /html\[data-motion="still"\] \[data-story-step\]/);
  assert.match(template, /html\[data-motion="still"\] svg \[data-node-id\][\s\S]*transition: none !important/);
  assert.match(template, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.story-trail-flow/);
  assert.ok((template.match(/svg\.removeAttribute\('data-story-next'\)/g) || []).length >= 3);
  assert.ok((template.match(/panel\.removeAttribute\('data-story-next'\)/g) || []).length >= 2);
  assert.match(template, /clone\.removeAttribute\('data-story-next'\)/);
  const cleanup = template.match(/function cleanExportClone\(clone\) \{[\s\S]*?\n      \}/)?.[0] || '';
  assert.match(cleanup, /return !clone[\s\S]*!clone\.hasAttribute\('data-story-next'\)/);
  assert.match(template, /var canonicalStateClean = cleanExportClone\(clone\);/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/story-moment-link.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-story-moment-link-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ], { encoding: 'utf8' });
  return { result, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one viewer-only Story Moment Link control', () => {
  for (const [mode, example] of Object.entries(CASES)) {
    const { result, html } = render(mode, example);
    assert.equal(result.status, 0, result.stderr);
    assert.match(html, /id="guided-view-beat-link"[^>]+aria-label="Select a Story Beat to copy its exact link"[^>]+disabled/);
    assert.match(html, /id="guided-view-beat-link-label">Copy moment<\/span>/);
    assert.doesNotMatch(canonicalSvg(html), /data-story-moment|guided-view-beat-link|#view=/);
  }
});

test('moment links use exact stable view and node ids without mutating manual selection URLs', () => {
  assert.match(template, /function storyMomentLink\(\)/);
  assert.match(template, /url\.searchParams\.delete\('play'\)/);
  assert.match(template, /url\.hash = 'view=' \+ encodeURIComponent\(view\.id\) \+ '&beat=' \+ encodeURIComponent\(step\.nodeId\)/);
  assert.match(template, /function selectStoryBeatById\(id, options\)/);
  assert.match(template, /storySteps\.findIndex\(function \(step\) \{ return step\.nodeId === id; \}\)/);
  assert.match(template, /var requestedBeat = params\.get\('beat'\)/);
  assert.match(template, /var restoreGeneration = \+\+momentRestoreGeneration/);
  assert.match(template, /afterHandoff\(function \(\) \{[\s\S]*restoreGeneration !== momentRestoreGeneration[\s\S]*selectStoryBeatById\(requestedBeat, \{ linked: true, follow: true, followInstant: true \}\)/);
  const manualSelection = template.match(/function selectStoryBeat\(index\) \{([\s\S]*?)\n      function updateUrl/)?.[1] || '';
  assert.doesNotMatch(manualSelection, /history\.|location\.|updateUrl\(/);
});

test('invalid or cross-chapter beat ids fail closed while the public receipt stays read-only', () => {
  assert.match(template, /if \(index < 0\) return false/);
  assert.doesNotMatch(template, /Number\(params\.get\('beat'\)\)/);
  assert.match(template, /setStoryBeat\(index, \{[\s\S]*?manual: false,[\s\S]*?center: true,[\s\S]*?pulse: false,[\s\S]*?follow: options\.follow === true,[\s\S]*?linked: options\.linked === true,[\s\S]*?followInstant: options\.followInstant === true/);
  assert.match(template, /beatLink: storyMomentLink/);
  assert.match(template, /copyBeatLink: copyStoryMomentLink/);
  assert.match(template, /beat: function \(\)[\s\S]*nodeId: step\.nodeId/);
});

test('copy feedback, one-shot playback, and reduced motion preserve the requested moment', () => {
  assert.match(template, /navigator\.clipboard && typeof navigator\.clipboard\.writeText === 'function'/);
  assert.match(template, /navigator\.clipboard\.writeText\(value\)/);
  assert.match(template, /document\.execCommand\('copy'\)/);
  assert.match(template, /beatLinkLabel\.textContent = viewerText\(copied \? 'viewer\.guided\.copied' : 'viewer\.guided\.copyFailed'\)/);
  assert.match(template, /viewerText\(copied \? 'viewer\.guided\.momentCopied' : 'viewer\.guided\.momentCopyFailed'\)/);
  assert.match(template, /function hashBeatMatchesCurrent\(\)/);
  assert.match(template, /if \(!storyAutomaticPlaybackAllowed\(\)\)[\s\S]*hashBeatMatchesCurrent\(\)[\s\S]*setAutoplayState\('reduced-motion'\)/);
  assert.match(template, /storyPlaybackScope = 'chapter'/);
  assert.match(template, /if \(storyBeatIndex < 0\)[\s\S]*setStoryBeat\(0, \{ pulse: true, follow: true \}\)/);
});

test('the control keeps stable desktop/mobile geometry and existing viewer boundaries', () => {
  assert.match(template, /\.guided-view-beat-link \{[\s\S]*min-height: 1\.5rem/);
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*\.guided-view-actions \{[\s\S]*grid-template-columns: repeat\(3, minmax\(0, 1fr\)\)/);
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*\.guided-view-beat-link \{[\s\S]*min-height: 2\.75rem/);
  assert.match(template, /html\[data-embed="true"\][\s\S]*\.guided-views \{ display: none !important; \}/);
  assert.match(template, /html\[data-embed="true"\]\[data-share-moment="true"\] \.share-chapter-cue:not\(\[hidden\]\)/);
  assert.match(template, /pinnedMode = !shareMode && embedMode && hashBeatMatchesCurrent\(\)/);
  assert.match(template, /pinned: viewerText\('viewer\.guided\.state\.pinned'\)/);
  assert.match(template, /@media print[\s\S]*\.guided-views/);
  assert.match(template, /syncStoryControlsDisabled\(\)[\s\S]*beatLink\.disabled =/);
  assert.match(template, /clone\.querySelectorAll\('\[data-story-overlay\], \[data-story-carrier-overlay\]'\)/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/story-shelf.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-story-shelf-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', CASES[mode]),
    output,
  ]);
  return fs.readFileSync(output, 'utf8');
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

test('all five renderers inherit one compact cold-open Story Shelf', () => {
  for (const mode of Object.keys(CASES)) {
    const html = render(mode);
    assert.match(html, /\.guided-views\[data-active-view="all"\]\s*\{/, mode);
    assert.match(html, /panel\.setAttribute\('data-active-view', view \? view\.id : 'all'\)/, mode);
    assert.doesNotMatch(canonicalSvg(html), /Story Shelf|data-active-view|guided-view-index/, mode);
  }
});

test('cold shelf keeps chapter identity and Play while removing only unavailable duplicate controls', () => {
  assert.match(template, /\.guided-views\[data-active-view="all"\] > #guided-view-prev,[\s\S]*#guided-view-next,[\s\S]*\.guided-view-beat-link,[\s\S]*\.guided-view-all\s*\{\s*display: none;\s*\}/);
  assert.doesNotMatch(template, /\.guided-views\[data-active-view="all"\][^{]*(?:\.guided-view-play|\.guided-view-index)[^{]*\{\s*display:\s*none/);
  assert.match(template, /<button class="guided-view-play"/);
  assert.match(template, /<nav class="guided-view-index"/);
});

test('desktop shelf follows DOM order and returns vertical space to the diagram', () => {
  const rule = template.match(/\.guided-views\[data-active-view="all"\]\s*\{([^}]*)\}/)?.[1] || '';
  assert.match(rule, /grid-template-columns:\s*minmax\(11rem,\s*\.72fr\)\s+auto\s+minmax\(0,\s*2fr\)/);
  assert.match(template, /\.guided-views\[data-active-view="all"\] \.guided-view-copy\s*\{[^}]*grid-column:\s*1/);
  assert.match(template, /\.guided-views\[data-active-view="all"\] \.guided-view-actions\s*\{[^}]*grid-column:\s*2/);
  assert.match(template, /\.guided-views\[data-active-view="all"\] \.guided-view-index\s*\{[^}]*grid-column:\s*3/);
  assert.match(template, /\.guided-views\[data-active-view="all"\] \.guided-view-copy > #guided-view-note\s*\{\s*display:\s*none/);
});

test('mobile shelf preserves 44px controls, horizontal chapters, and honest expansion', () => {
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*\.guided-views\[data-active-view="all"\][\s\S]*grid-template-columns:\s*minmax\(0,\s*1fr\)\s+auto/);
  assert.match(template, /\.guided-views\[data-active-view="all"\] \.guided-view-index\s*\{[^}]*grid-column:\s*1 \/ -1;[^}]*grid-row:\s*2/);
  assert.match(template, /\.guided-views\[data-active-view="all"\] \.guided-view-play\s*\{[^}]*min-height:\s*2\.75rem/);
  assert.match(template, /\.guided-view-chapters\s*\{[^}]*overflow-x:\s*auto/);
  assert.match(template, /\.guided-view-chapter\s*\{[^}]*min-height:\s*2\.75rem/);
});

test('active stories expand through existing state without storage or a second interaction owner', () => {
  assert.match(template, /panel\.setAttribute\('data-active-view', 'all'\);\s*panel\.hidden = false/);
  assert.match(template, /data-active-view', view \? view\.id : 'all'/);
  assert.match(template, /if \(activeIndex < 0\) activate\(0, \{ playback: true \}\)/);
  assert.match(template, /showAll\([\s\S]*activeIndex = -1;[\s\S]*render\(\)/);
  assert.doesNotMatch(template, /storyShelf(?:Open|Expanded|Storage)|archify-story-shelf|localStorage[^\n]*shelf/i);
});

test('Story Shelf remains viewer-only, embed-safe, print-safe, and motion-neutral', () => {
  assert.match(template, /html\[data-embed="true"\] \.guided-views \{ display: none !important; \}/);
  assert.match(template, /\.toolbar, \.diagram-nav, \.focus-chip, \.guided-views, \.archify-toast, \.no-print \{ display: none !important; \}/);
  assert.doesNotMatch(canonicalSvg(render('workflow')), /Story Shelf|guided-view|data-active-view/);
  assert.doesNotMatch(template, /@keyframes\s+archify-story-shelf|animation:[^;]*story-shelf/i);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/story-trail.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-story-trail-'));

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  const result = spawnSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    output,
  ], { encoding: 'utf8' });
  return { result, html: fs.existsSync(output) ? fs.readFileSync(output, 'utf8') : '' };
}

for (const [mode, example] of Object.entries(CASES)) {
  test(`${mode}: guided paths expose a viewer-only Story Trail`, () => {
    const { result, html } = render(mode, example);
    assert.equal(result.status, 0, result.stderr);
    assert.match(html, /id="guided-view-trail" hidden role="group" aria-label="Story trail"/);
    assert.match(html, /function renderStoryTrail\(view\)/);
    assert.match(html, /document\.createElement\('button'\)/);
    assert.match(html, /stop\.type = 'button'/);
    assert.match(html, /data-story-node/);
    assert.match(html, /data-story-link/);
    assert.match(html, /edges\.length === 1 && forward\.length === 1 \? 'forward'/);
    assert.match(html, /data-story-overlay/);
    assert.match(html, /data-story-playing/);
    assert.match(html, /data-story-beat/);
    assert.match(html, /data-story-beat-state/);
    assert.match(html, /data-story-beat-step/);
    assert.match(html, /storySteps\.forEach\(function \(step\)/);
    assert.match(html, /step\.edges\.forEach\(function \(edge\)/);
    assert.match(html, /edge\.setAttribute\('data-story-beat-step', String\(edgeBeat\)\)/);
    assert.match(html, /svg\[data-story-beat\] \[data-edge-from\]\[data-story-beat-step\]/);
    assert.match(html, /story-trail-flow/);
    assert.match(html, /function scheduleStoryPlayback\(\)/);
    assert.match(html, /storyBeatTimer = setTimeout/);
    assert.match(html, /storyBeatDwellMs = storyBeatDwell\(total\)/);
    assert.match(html, /Math\.max\(STORY_FOLLOW_MIN_DWELL_MS, VIEW_INTERVAL_MS \/ Math\.max\(1, total\)\)/);
    assert.match(html, /function storyStep\(view, index, edgeList, byId\)/);
    assert.match(html, /prefers-reduced-motion: reduce/);
    assert.match(html, /from === previousId && to === id/);
    assert.match(html, /from === id && to === previousId/);
    assert.match(html, /firstEdge\.parentNode\.insertBefore\(overlay, firstEdge\)/);
    assert.doesNotMatch(html, /content: '\\2192';\s*font-size: 0\.65rem/);

    const generatedSvg = html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
    assert.doesNotMatch(generatedSvg, /data-story-(?:active|playing|beat|step|overlay|carrier)/);
  });
}

test('Story Trail state is removed from every export clone', () => {
  const template = fs.readFileSync(path.join(skillRoot, 'assets', 'template.html'), 'utf8');
  assert.match(template, /clone\.removeAttribute\('data-story-active'\)/);
  assert.match(template, /clone\.removeAttribute\('data-story-playing'\)/);
  assert.match(template, /clone\.removeAttribute\('data-story-beat'\)/);
  assert.match(template, /clone\.querySelectorAll\('\[data-story-overlay\], \[data-story-carrier-overlay\]'\)/);
  assert.match(template, /clone\.querySelectorAll\('\[data-story-step\], \[data-story-beat-state\], \[data-story-beat-step\]'\)/);
  assert.match(template, /el\.removeAttribute\('data-story-beat-state'\)/);
  assert.match(template, /el\.removeAttribute\('data-story-beat-step'\)/);
  assert.match(template, /el\.style\.removeProperty\('--story-step'\)/);
  assert.match(template, /canonicalStateClean[\s\S]*data-story-beat-state/);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/toolbar-polish.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const template = fs.readFileSync(path.resolve(__dirname, '../assets/template.html'), 'utf8');

test('toolbar keeps four independent controls with explicit open states', () => {
  assert.match(template, /\.toolbar \{[\s\S]*?gap: 0\.5rem;[\s\S]*?padding: 0;[\s\S]*?background: transparent;[\s\S]*?box-shadow: none;/);
  assert.match(template, /\.toolbar button \{[\s\S]*?background: var\(--toolbar-bg\);[\s\S]*?border: 1px solid var\(--toolbar-border\);/);
  assert.match(template, /button\[aria-expanded="true"\]/);
  assert.doesNotMatch(template, /\.preset-wrap::before,[\s\S]*?\.export-wrap::before/);
  assert.match(template, /<span id="theme-icon" class="toolbar-icon"/);
  assert.match(template, /<span id="present-icon" class="toolbar-icon"/);
  assert.doesNotMatch(template.match(/<div class="toolbar"[\s\S]*?<div class="container">/)?.[0] || '', /<svg\b/);
  assert.doesNotMatch(template, /icon\.textContent =/);
});

test('export menu has grouped, single-column rows and a zoom-safe width', () => {
  const sectionCss = template.match(/\.export-menu-section \{[\s\S]*?\}/)?.[0] || '';
  assert.match(template, /class="export-menu-section" role="group" aria-label="\{\{i18n:viewer\.export\.share\}\}"/);
  assert.match(template, /class="export-menu-section" role="group" aria-label="\{\{i18n:viewer\.export\.raster\}\}"/);
  assert.match(template, /class="export-menu-section" role="group" aria-label="\{\{i18n:viewer\.export\.vectorMotion\}\}"/);
  assert.match(template, /class="export-menu-header" role="presentation"/);
  assert.match(template, /\.toolbar \.export-menu \{[\s\S]*?width: 19rem;[\s\S]*?max-width: calc\(100vw - 2rem\);/);
  assert.match(template, /\.export-menu-section \{[\s\S]*?grid-template-columns: minmax\(0, 1fr\);/);
  assert.doesNotMatch(sectionCss, /repeat\(2/);
  assert.match(template, /\.toolbar \.export-menu button \{[\s\S]*?grid-template-columns: 1\.15rem minmax\(0, 1fr\);[\s\S]*?white-space: nowrap;/);
  assert.match(template, /\.export-item-copy strong,[\s\S]*?\.export-item-copy small \{ display: block; \}/);
});

test('mobile menus share one viewport-safe placement and disabled exports remain explicit', () => {
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*?\.toolbar \.preset-menu,[\s\S]*?\.toolbar \.export-menu \{[\s\S]*?position: fixed;/);
  assert.match(template, /\.toolbar \.export-menu button:disabled \{[\s\S]*?cursor: not-allowed;/);
  assert.doesNotMatch(template, /it\.style\.opacity = '0\.5'/);
});

test('diagram view dock stays compact on desktop and touch-safe on narrow screens', () => {
  assert.match(template, /\.diagram-nav \{[\s\S]*?padding: 0\.15rem;[\s\S]*?border-radius: 0\.58rem;/);
  assert.match(template, /\.diagram-nav button \{[\s\S]*?min-width: 2rem;[\s\S]*?height: 2rem;/);
  assert.match(template, /@media \(max-width: 720px\)[\s\S]*?\.diagram-nav button \{[\s\S]*?min-width: 2\.75rem;[\s\S]*?height: 2\.75rem;/);
  assert.match(template, /class="diagram-nav-icon find"/);
  assert.match(template, /class="diagram-nav-icon guide"/);
  assert.match(template, /class="diagram-nav-icon minus"/);
  assert.match(template, /class="diagram-nav-icon plus"/);
});

test('diagram view reset separates semantic detail from zoom percentage', () => {
  assert.match(template, /data-view="reset"[\s\S]*?data-view-detail hidden>\{\{i18n:viewer\.nav\.read\}\}<[\s\S]*?data-view-percent>100%</);
  assert.match(template, /var resolvedLevel = semantic \? viewerText\('viewer\.nav\.level\.auto'\) : levelLabel;/);
  assert.match(template, /var showDetailLevel = semantic \|\| detail !== 'read';/);
  assert.match(template, /resetDetailLabel\.hidden = !showDetailLevel/);
  assert.match(template, /resetPercentLabel\.textContent = percent/);
  assert.match(template, /resetBtn\.toggleAttribute\('data-detail-visible', showDetailLevel\)/);
});
```

## test/update-contract.test.mjs

```js
import assert from 'node:assert/strict';
import test from 'node:test';

import {
  DEFAULT_MANIFEST_URL,
  EXPECTED_REPOSITORY,
  SKILL_ID,
  UpdateContractError,
  compareSemver,
  isStableCoreVersion,
  releaseChannelForVersion,
  validateCanonicalUtcTimestamp,
  validateLocalRelease,
  validateStableUpdateManifest,
} from '../scripts/update-contract.mjs';

function localRelease(overrides = {}) {
  return {
    schemaVersion: 1,
    skillId: SKILL_ID,
    channel: 'stable',
    version: '2.16.0',
    source: { repository: EXPECTED_REPOSITORY },
    updateManifestUrl: DEFAULT_MANIFEST_URL,
    ...overrides,
  };
}

function stableManifest(overrides = {}) {
  return {
    schemaVersion: 1,
    skillId: SKILL_ID,
    channel: 'stable',
    version: '2.16.0',
    publishedAt: '2026-08-28T07:00:00Z',
    source: {
      repository: EXPECTED_REPOSITORY,
      ref: 'v2.16.0',
      treeSha: 'a'.repeat(40),
    },
    artifact: { sha256: 'b'.repeat(64) },
    summary: 'Contract fixture.',
    releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0',
    severity: 'normal',
    ...overrides,
  };
}

test('shared release constants identify the only trusted updater source', () => {
  assert.equal(SKILL_ID, 'archify');
  assert.equal(EXPECTED_REPOSITORY, 'https://github.com/tt-a1i/archify');
  assert.equal(
    DEFAULT_MANIFEST_URL,
    'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json',
  );
});

test('SemVer precedence follows the complete prerelease ordering vector', () => {
  const ordered = [
    '1.0.0-alpha',
    '1.0.0-alpha.1',
    '1.0.0-alpha.beta',
    '1.0.0-beta',
    '1.0.0-beta.2',
    '1.0.0-beta.11',
    '1.0.0-rc.1',
    '1.0.0',
  ];
  for (let index = 1; index < ordered.length; index += 1) {
    assert.equal(compareSemver(ordered[index - 1], ordered[index]), -1);
  }
  assert.equal(compareSemver('1.0.0+build.9', '1.0.0+build.1'), 0);
  assert.equal(compareSemver('9007199254740993.0.0', '9007199254740992.0.0'), 1);
  assert.throws(() => compareSemver('1.0.0-alpha.01', '1.0.0'), UpdateContractError);
});

test('release channels and stable manifest versions use distinct SemVer policies', () => {
  assert.equal(releaseChannelForVersion('2.16.0'), 'stable');
  assert.equal(releaseChannelForVersion('2.16.0+local.1'), 'stable');
  assert.equal(releaseChannelForVersion('2.16.0-dev.0'), 'development');
  assert.equal(isStableCoreVersion('2.16.0'), true);
  assert.equal(isStableCoreVersion('2.16.0+build.1'), false);
  assert.equal(isStableCoreVersion('2.16.0-dev.0'), false);
  assert.equal(isStableCoreVersion('02.16.0'), false);
});

test('local release identity is exact and channel-consistent', () => {
  assert.deepEqual(validateLocalRelease(localRelease()), localRelease());
  assert.deepEqual(validateLocalRelease(localRelease({
    channel: 'development',
    version: '2.17.0-dev.0',
  })), localRelease({
    channel: 'development',
    version: '2.17.0-dev.0',
  }));
  assert.throws(() => validateLocalRelease(localRelease({ extra: true })), UpdateContractError);
  assert.throws(() => validateLocalRelease(localRelease({
    source: { repository: EXPECTED_REPOSITORY, extra: true },
  })), UpdateContractError);
  assert.throws(() => validateLocalRelease(localRelease({ channel: 'development' })), UpdateContractError);
  assert.throws(() => validateLocalRelease(localRelease({ version: '02.16.0' })), UpdateContractError);
});

test('publication timestamps are canonical UTC seconds with real calendar dates', () => {
  assert.equal(validateCanonicalUtcTimestamp('2024-02-29T23:59:59Z'), '2024-02-29T23:59:59Z');
  for (const value of [
    '2026-08-28T15:00:00+08:00',
    '2026-08-28',
    '2026-02-30T00:00:00Z',
    '2026-08-28T00:00:00.000Z',
  ]) {
    assert.throws(() => validateCanonicalUtcTimestamp(value), UpdateContractError, value);
  }
});

test('stable manifests enforce one exact schema and canonical release identity', () => {
  assert.deepEqual(validateStableUpdateManifest(stableManifest()), stableManifest());
  const invalid = [
    stableManifest({ extra: true }),
    stableManifest({ version: '2.16.0+build.1' }),
    stableManifest({ publishedAt: '2026-08-28T15:00:00+08:00' }),
    stableManifest({ source: { ...stableManifest().source, extra: true } }),
    stableManifest({ artifact: { sha256: 'b'.repeat(64), extra: true } }),
    stableManifest({ summary: '\u202eunsafe' }),
    stableManifest({ severity: 'urgent' }),
    stableManifest({ releaseNotes: 'https://github.com:443/tt-a1i/archify/releases/tag/v2.16.0' }),
    stableManifest({ releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0?' }),
    stableManifest({ releaseNotes: 'https://GITHUB.COM/tt-a1i/archify/releases/tag/v2.16.0' }),
  ];
  for (const value of invalid) {
    assert.throws(() => validateStableUpdateManifest(value), UpdateContractError);
  }
});
```

## test/update-notifier.test.mjs

```js
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import fs from 'node:fs';
import fsPromises from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import test from 'node:test';
import { fileURLToPath, pathToFileURL } from 'node:url';

import {
  acknowledgeUpdate,
  checkForUpdate,
} from '../scripts/check-update.mjs';
import { DEFAULT_MANIFEST_URL, compareSemver, parseSemver } from '../scripts/update-contract.mjs';

const here = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(here, '..');
const checkerPath = path.join(skillRoot, 'scripts', 'check-update.mjs');
const contractPath = path.join(skillRoot, 'scripts', 'update-contract.mjs');
const expectedRepository = 'https://github.com/tt-a1i/archify';
const expectedManifestUrl = 'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json';
const baseTime = Date.parse('2026-08-28T08:00:00Z');
const childCheckTimeoutMs = 2_000;
const parentCheckTimeoutMs = 5_000;
const maxCacheStateBytes = 64 * 1_024;

function writeJson(target, value) {
  fs.mkdirSync(path.dirname(target), { recursive: true });
  fs.writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`);
}

function compactStateSource(state) {
  return `${JSON.stringify(state)}\n`;
}

function metadataWithOverrides(metadata, overrides) {
  return new Proxy(metadata, {
    get(target, property) {
      if (Object.hasOwn(overrides, property)) return overrides[property];
      const value = Reflect.get(target, property, target);
      return typeof value === 'function' ? value.bind(target) : value;
    },
  });
}

function historyDigest(index) {
  return `sha256:${crypto.createHash('sha256').update(`history-${index}`).digest('hex')}`;
}

function cachedStateWithHistory({ offeredDigests = [], acknowledgedDigests = [] } = {}) {
  return {
    schemaVersion: 1,
    skillId: 'archify',
    installedVersion: '2.15.0',
    check: {
      nextCheckAt: '2020-01-01T00:00:00.000Z',
      consecutiveFailures: 0,
    },
    notification: {
      offeredDigests: [...offeredDigests],
      acknowledgedDigests: [...acknowledgedDigests],
    },
  };
}

function candidateStateForDigest(state, digest) {
  return {
    ...state,
    check: {
      nextCheckAt: new Date(baseTime + (72 * 60 * 60 * 1_000)).toISOString(),
      consecutiveFailures: 0,
    },
    notification: {
      offeredDigests: [...state.notification.offeredDigests, digest],
      acknowledgedDigests: [...state.notification.acknowledgedDigests],
    },
    candidate: {
      version: '2.16.0',
      targetDigest: digest,
      severity: 'normal',
      releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0',
    },
  };
}

function writeCompactCommittedState(testFixture, state, generation = 1n) {
  const target = path.join(operationPath(testFixture, 'committed', generation), 'state.json');
  fs.mkdirSync(path.dirname(target), { recursive: true });
  fs.writeFileSync(target, compactStateSource(state));
  return target;
}

function committedStateFiles(testFixture) {
  return fs.readdirSync(stateDirectory(testFixture), { withFileTypes: true })
    .filter((entry) => entry.isDirectory() && /^committed-\d+$/.test(entry.name))
    .map((entry) => path.join(stateDirectory(testFixture), entry.name, 'state.json'));
}

function localRelease(version = '2.15.0') {
  return {
    schemaVersion: 1,
    skillId: 'archify',
    channel: version.includes('-') ? 'development' : 'stable',
    version,
    source: { repository: expectedRepository },
    updateManifestUrl: expectedManifestUrl,
  };
}

function remoteRelease(overrides = {}) {
  return {
    schemaVersion: 1,
    skillId: 'archify',
    channel: 'stable',
    version: '2.16.0',
    publishedAt: '2026-08-28T07:00:00Z',
    source: {
      repository: expectedRepository,
      ref: 'v2.16.0',
      treeSha: 'a'.repeat(40),
    },
    artifact: {
      sha256: 'b'.repeat(64),
    },
    summary: 'Improve large-repository scanning and diagram layout.',
    releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0',
    severity: 'normal',
    ...overrides,
  };
}

function remoteReleaseForVersion(version, digest = 'b'.repeat(64)) {
  const release = remoteRelease();
  return {
    ...release,
    version,
    source: { ...release.source, ref: `v${version}` },
    artifact: { sha256: digest },
    releaseNotes: `https://github.com/tt-a1i/archify/releases/tag/v${version}`,
  };
}

function response(body, { status = 200, etag = '"archify-2.16.0"' } = {}) {
  return new Response(typeof body === 'string' ? body : JSON.stringify(body), {
    status,
    headers: {
      'content-type': 'application/json',
      etag,
    },
  });
}

function fixture(version = '2.15.0') {
  const root = fs.realpathSync(fs.mkdtempSync(
    path.join(os.tmpdir(), 'archify-update-notifier-'),
  ));
  const releasePath = path.join(root, 'skill-release.json');
  const cacheDirectory = path.join(root, 'cache');
  writeJson(releasePath, localRelease(version));
  return { root, releasePath, cacheDirectory };
}

function stateDirectory(testFixture, version = '2.15.0') {
  const partition = crypto.createHash('sha256').update(version).digest('hex').slice(0, 24);
  return path.join(testFixture.cacheDirectory, `version-${partition}`);
}

function statePath(testFixture, version = '2.15.0') {
  const directory = stateDirectory(testFixture, version);
  let committed = [];
  try {
    committed = fs.readdirSync(directory)
      .filter((name) => /^committed-\d+$/.test(name))
      .sort((left, right) => {
        const leftGeneration = BigInt(left.slice('committed-'.length));
        const rightGeneration = BigInt(right.slice('committed-'.length));
        return leftGeneration > rightGeneration ? -1 : 1;
      });
  } catch {
    // A seed may be written before the cache partition exists.
  }
  return committed[0]
    ? path.join(directory, committed[0], 'state.json')
    : path.join(operationPath(testFixture, 'committed', 1n, version), 'state.json');
}

function operationPath(testFixture, kind, generation, version = '2.15.0') {
  return path.join(
    stateDirectory(testFixture, version),
    `${kind}-${BigInt(generation).toString().padStart(20, '0')}`,
  );
}

function writePendingOperation(testFixture, {
  generation = 1n,
  owner = { pid: process.pid, token: 'e'.repeat(32) },
  modifiedAt = new Date(),
} = {}) {
  const directory = operationPath(testFixture, 'pending', generation);
  fs.mkdirSync(directory, { recursive: true });
  const ownerPath = path.join(directory, 'owner.json');
  fs.writeFileSync(ownerPath, typeof owner === 'string' ? owner : `${JSON.stringify(owner)}\n`);
  fs.utimesSync(ownerPath, modifiedAt, modifiedAt);
  return directory;
}

function pauseMkdirOnce(targetPath) {
  const originalMkdir = fsPromises.mkdir;
  let release;
  let markReached;
  let intercepted = false;
  const reached = new Promise((resolve) => { markReached = resolve; });
  const gate = new Promise((resolve) => { release = resolve; });
  fsPromises.mkdir = async (target, ...args) => {
    if (!intercepted && path.resolve(target) === path.resolve(targetPath)) {
      intercepted = true;
      markReached();
      await gate;
    }
    return originalMkdir(target, ...args);
  };
  return {
    reached,
    release,
    restore() {
      release();
      fsPromises.mkdir = originalMkdir;
    },
  };
}

function pauseReaddirSnapshot(targetDirectory, callNumber) {
  const originalReaddir = fsPromises.readdir;
  let release;
  let markReached;
  let calls = 0;
  const reached = new Promise((resolve) => { markReached = resolve; });
  const gate = new Promise((resolve) => { release = resolve; });
  fsPromises.readdir = async (target, ...args) => {
    const result = await originalReaddir(target, ...args);
    if (path.resolve(target) === path.resolve(targetDirectory) && ++calls === callNumber) {
      markReached();
      await gate;
    }
    return result;
  };
  return {
    reached,
    release,
    restore() {
      release();
      fsPromises.readdir = originalReaddir;
    },
  };
}

function pauseReaddirSnapshotOnce(targetDirectory, predicate) {
  const originalReaddir = fsPromises.readdir;
  let release;
  let markReached;
  let intercepted = false;
  const reached = new Promise((resolve) => { markReached = resolve; });
  const gate = new Promise((resolve) => { release = resolve; });
  fsPromises.readdir = async (target, ...args) => {
    const result = await originalReaddir(target, ...args);
    if (!intercepted
      && path.resolve(target) === path.resolve(targetDirectory)
      && predicate(result)) {
      intercepted = true;
      markReached();
      await gate;
    }
    return result;
  };
  return {
    reached,
    release,
    restore() {
      release();
      fsPromises.readdir = originalReaddir;
    },
  };
}

function pauseOpenOnce(predicate) {
  const originalOpen = fsPromises.open;
  let release;
  let markReached;
  let intercepted = false;
  const reached = new Promise((resolve) => { markReached = resolve; });
  const gate = new Promise((resolve) => { release = resolve; });
  fsPromises.open = async (target, ...args) => {
    if (!intercepted && predicate(target)) {
      intercepted = true;
      markReached();
      await gate;
    }
    return originalOpen(target, ...args);
  };
  return {
    reached,
    release,
    restore() {
      release();
      fsPromises.open = originalOpen;
    },
  };
}

function pauseWriteOnce(basename) {
  return pauseOpenOnce((target) => path.basename(target) === basename);
}

function pauseRenameMatchingOnce(predicate) {
  const originalRename = fsPromises.rename;
  let release;
  let markReached;
  let intercepted = false;
  const reached = new Promise((resolve) => { markReached = resolve; });
  const gate = new Promise((resolve) => { release = resolve; });
  fsPromises.rename = async (source, destination, ...args) => {
    if (!intercepted && predicate(source, destination)) {
      intercepted = true;
      markReached();
      await gate;
    }
    return originalRename(source, destination, ...args);
  };
  return {
    reached,
    release,
    restore() {
      release();
      fsPromises.rename = originalRename;
    },
  };
}

function pauseRenameOnce(sourcePath, destinationPath) {
  return pauseRenameMatchingOnce((source, destination) => (
    path.resolve(source) === path.resolve(sourcePath)
      && path.resolve(destination) === path.resolve(destinationPath)
  ));
}

function options(testFixture, fetchImpl, overrides = {}) {
  return {
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    fetchImpl,
    now: () => baseTime,
    random: () => 0.5,
    timeoutMs: 50,
    ...overrides,
  };
}

function cachedUpdateState() {
  return {
    schemaVersion: 1,
    skillId: 'archify',
    installedVersion: '2.15.0',
    check: {
      nextCheckAt: new Date(baseTime + (24 * 60 * 60 * 1_000)).toISOString(),
      consecutiveFailures: 0,
    },
    notification: {
      offeredDigests: [`sha256:${'b'.repeat(64)}`],
      acknowledgedDigests: [],
    },
    candidate: {
      version: '2.16.0',
      targetDigest: `sha256:${'b'.repeat(64)}`,
      severity: 'normal',
      releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0',
    },
  };
}

function createFifo(target) {
  const created = spawnSync('mkfifo', [target], { encoding: 'utf8' });
  if (created.error?.code === 'ENOENT') return false;
  assert.equal(created.status, 0, created.stderr || created.error?.message);
  return true;
}

function runCheckInChild(testFixture) {
  const manifest = remoteReleaseForVersion('2.15.0', 'd'.repeat(64));
  const childSource = `
    import { checkForUpdate } from ${JSON.stringify(pathToFileURL(checkerPath).href)};
    const watchdog = setTimeout(() => {
      process.stderr.write('child update check timed out\\n');
      process.exit(124);
    }, ${childCheckTimeoutMs});
    let requests = 0;
    try {
      const result = await checkForUpdate({
        releasePath: ${JSON.stringify(testFixture.releasePath)},
        cacheDirectory: ${JSON.stringify(testFixture.cacheDirectory)},
        fetchImpl: async () => {
          requests += 1;
          return new Response(${JSON.stringify(JSON.stringify(manifest))}, {
            status: 200,
            headers: { 'content-type': 'application/json' },
          });
        },
        now: () => ${baseTime},
        random: () => 0.5,
        timeoutMs: 50,
      });
      process.stdout.write(JSON.stringify({ result, requests }));
    } finally {
      clearTimeout(watchdog);
    }
  `;
  const child = spawnSync(process.execPath, ['--input-type=module', '--eval', childSource], {
    cwd: skillRoot,
    encoding: 'utf8',
    timeout: parentCheckTimeoutMs,
  });
  const diagnostics = [child.error?.stack, child.stderr, child.stdout].filter(Boolean).join('\n');
  assert.equal(child.error, undefined, diagnostics);
  assert.equal(child.status, 0, diagnostics);
  return JSON.parse(child.stdout);
}

function assertUnsafeCacheStateIsIgnored(testFixture) {
  assert.deepEqual(runCheckInChild(testFixture), {
    result: { status: 'silent', reason: 'current' },
    requests: 1,
  });
}

test('production manifest URL is a fixed trusted GitHub Pages resource', () => {
  assert.equal(DEFAULT_MANIFEST_URL, expectedManifestUrl);
  const local = JSON.parse(fs.readFileSync(path.join(skillRoot, 'skill-release.json'), 'utf8'));
  assert.equal(local.updateManifestUrl, expectedManifestUrl);
  assert.equal(local.source.repository, expectedRepository);
});

test('SemVer comparison handles stable, prerelease, and downgrade ordering', () => {
  assert.equal(compareSemver('2.16.0', '2.15.0'), 1);
  assert.equal(compareSemver('2.16.0-dev.0', '2.15.0'), 1);
  assert.equal(compareSemver('2.16.0', '2.16.0-dev.9'), 1);
  assert.equal(compareSemver('2.16.0-dev.2', '2.16.0-dev.10'), -1);
  assert.equal(compareSemver('2.16.0', '2.16.0'), 0);
  assert.equal(compareSemver('2.16.0+build.9', '2.16.0+build.1'), 0);
  assert.equal(compareSemver('9007199254740993.0.0', '9007199254740992.0.0'), 1);
  assert.equal(compareSemver('2.16.0-dev.9007199254740993', '2.16.0-dev.9007199254740992'), 1);
  assert.throws(() => compareSemver('2.16.0-dev.01', '2.16.0'));
});

test('development installs never treat the older stable release as an update', async (t) => {
  const testFixture = fixture('2.16.0-dev.0');
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));

  const result = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease({
      version: '2.15.0',
      source: {
        repository: expectedRepository,
        ref: 'v2.15.0',
        treeSha: 'c'.repeat(40),
      },
      artifact: { sha256: 'd'.repeat(64) },
      releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.15.0',
    })),
  ));

  assert.deepEqual(result, { status: 'silent', reason: 'current' });
});

test('a changed digest never bypasses same-version or downgrade protection', async () => {
  for (const [version, digest] of [
    ['2.15.0', 'c'.repeat(64)],
    ['2.14.9', 'd'.repeat(64)],
  ]) {
    const testFixture = fixture();
    try {
      const result = await checkForUpdate(options(
        testFixture,
        async () => response(remoteReleaseForVersion(version, digest)),
      ));
      assert.deepEqual(result, { status: 'silent', reason: 'current' });
    } finally {
      fs.rmSync(testFixture.root, { recursive: true, force: true });
    }
  }
});

test('a successful refresh withdraws a previously offered higher candidate', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(requests === 1
      ? remoteReleaseForVersion('3.0.0', 'c'.repeat(64))
      : remoteReleaseForVersion('2.15.0', 'd'.repeat(64)));
  };

  const offered = await checkForUpdate(options(testFixture, fetchImpl));
  assert.equal(offered.status, 'update_available');
  assert.equal(offered.latestVersion, '3.0.0');

  const withdrawn = await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }));
  assert.deepEqual(withdrawn, { status: 'silent', reason: 'current' });
  assert.equal(JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8')).candidate.version, '2.15.0');
  assert.equal(requests, 2);
});

test('a newer immutable candidate is re-offered until the visible notice is acknowledged', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  const first = await checkForUpdate(options(testFixture, fetchImpl));
  assert.equal(first.status, 'update_available');
  assert.equal(first.installedVersion, '2.15.0');
  assert.equal(first.latestVersion, '2.16.0');
  assert.equal(first.eventKey, `archify@sha256:${'b'.repeat(64)}`);
  assert.equal(first.targetDigest, `sha256:${'b'.repeat(64)}`);
  assert.equal(Object.hasOwn(first, 'updateCommand'), false);
  assert.equal(first.summary, 'Archify 2.16.0 is available; see the official release notes for details.');
  const persisted = JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8'));
  assert.deepEqual(Object.keys(persisted.check).sort(), [
    'consecutiveFailures', 'nextCheckAt',
  ]);
  assert.deepEqual(Object.keys(persisted.notification).sort(), [
    'acknowledgedDigests', 'offeredDigests',
  ]);
  assert.deepEqual(Object.keys(persisted.candidate).sort(), [
    'releaseNotes', 'severity', 'targetDigest', 'version',
  ]);

  const second = await checkForUpdate(options(testFixture, fetchImpl));
  assert.equal(second.status, 'update_available');
  assert.equal(second.eventKey, first.eventKey);
  assert.equal(requests, 1, 'fresh cached candidates must not make another request');

  const acknowledgement = await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: first.eventKey,
    now: () => baseTime + 1_000,
  });
  assert.deepEqual(acknowledgement, { status: 'acknowledged', eventKey: first.eventKey });
  assert.deepEqual(
    JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8')).notification.acknowledgedDigests,
    [first.targetDigest],
  );

  const third = await checkForUpdate(options(testFixture, fetchImpl));
  assert.deepEqual(third, { status: 'silent', reason: 'already-notified' });
  assert.equal(requests, 1);
});

test('an acknowledged candidate stays suppressed after a later candidate and manifest rollback', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const releases = [
    remoteReleaseForVersion('2.16.0', 'b'.repeat(64)),
    remoteReleaseForVersion('2.17.0', 'c'.repeat(64)),
    remoteReleaseForVersion('2.16.0', 'b'.repeat(64)),
  ];
  let requests = 0;
  const fetchImpl = async () => response(releases[requests++]);

  const first = await checkForUpdate(options(testFixture, fetchImpl));
  assert.equal(first.status, 'update_available');
  assert.deepEqual(await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: first.eventKey,
  }), { status: 'acknowledged', eventKey: first.eventKey });

  const second = await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }));
  assert.equal(second.status, 'update_available');
  assert.deepEqual(await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: second.eventKey,
  }), { status: 'acknowledged', eventKey: second.eventKey });

  assert.deepEqual(await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (146 * 60 * 60 * 1_000),
  })), { status: 'silent', reason: 'already-notified' });
  assert.equal(requests, 3);
  assert.deepEqual(
    JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8')).notification.acknowledgedDigests,
    [first.targetDigest, second.targetDigest],
  );
});

test('a 64 KiB multi-offer cache only returns an event whose acknowledgement closure can commit', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const earlierDigest = `sha256:${'a'.repeat(64)}`;
  const currentDigest = `sha256:${'b'.repeat(64)}`;
  const replacementDigest = `sha256:${'c'.repeat(64)}`;
  const cachedVersion = `3.16.${'9'.repeat(35)}`;
  const state = cachedStateWithHistory({
    offeredDigests: [earlierDigest, currentDigest],
    acknowledgedDigests: Array.from({ length: 877 }, (_, index) => historyDigest(index)),
  });
  state.check.nextCheckAt = 'Mon, 31 Aug 2026 08:00:00 GMT';
  state.candidate = {
    version: cachedVersion,
    targetDigest: currentDigest,
    severity: 'normal',
    releaseNotes: `https://github.com/tt-a1i/archify/releases/tag/v${cachedVersion}`,
  };
  assert.equal(Buffer.byteLength(compactStateSource(state)), maxCacheStateBytes);
  writeCompactCommittedState(testFixture, state);
  let requests = 0;

  const offered = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteReleaseForVersion('2.16.0', 'c'.repeat(64)));
  }));
  assert.equal(offered.status, 'update_available');
  const acknowledgement = await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: offered.eventKey,
  });

  assert.deepEqual(acknowledgement, {
    status: 'acknowledged',
    eventKey: offered.eventKey,
  });
  assert.equal(requests, 1, 'an unrecoverable cached offer must be rebuilt before exposure');
  assert.equal(offered.targetDigest, replacementDigest);
});

test('a recoverable multi-offer boundary acknowledges every event without pruning history', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const earlierDigest = `sha256:${'a'.repeat(64)}`;
  const currentDigest = `sha256:${'b'.repeat(64)}`;
  const cachedVersion = `3.16.${'9'.repeat(35)}`;
  const exactHistory = Array.from({ length: 877 }, (_, index) => historyDigest(index));
  const state = cachedStateWithHistory({
    offeredDigests: [earlierDigest, currentDigest],
    acknowledgedDigests: exactHistory,
  });
  state.check.nextCheckAt = 'Mon, 31 Aug 2026 8:00:00 GMT';
  state.candidate = {
    version: cachedVersion,
    targetDigest: currentDigest,
    severity: 'normal',
    releaseNotes: `https://github.com/tt-a1i/archify/releases/tag/v${cachedVersion}`,
  };
  assert.equal(Buffer.byteLength(compactStateSource(state)), maxCacheStateBytes - 1);
  writeCompactCommittedState(testFixture, state);

  const current = await checkForUpdate(options(testFixture, async () => {
    throw new Error('a fresh recoverable cache must not use the network');
  }));
  assert.equal(current.targetDigest, currentDigest);
  assert.deepEqual(await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: current.eventKey,
  }), { status: 'acknowledged', eventKey: current.eventKey });
  const earlierEventKey = `archify@${earlierDigest}`;
  assert.deepEqual(await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: earlierEventKey,
  }), { status: 'acknowledged', eventKey: earlierEventKey });

  const persisted = JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8'));
  assert.deepEqual(persisted.notification, {
    offeredDigests: [],
    acknowledgedDigests: [...exactHistory, currentDigest, earlierDigest],
  });
  assert.equal(fs.statSync(statePath(testFixture)).size, maxCacheStateBytes);
});

test('a near-capacity null schedule does not retry the network on every activation', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const state = cachedStateWithHistory({
    acknowledgedDigests: Array.from({ length: 883 }, (_, index) => historyDigest(index)),
  });
  state.check.nextCheckAt = null;
  assert.equal(Buffer.byteLength(compactStateSource(state)), 65_524);
  writeCompactCommittedState(testFixture, state);
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  const first = await checkForUpdate(options(testFixture, fetchImpl));
  const second = await checkForUpdate(options(testFixture, fetchImpl));

  assert.equal(first.status, 'update_available');
  assert.deepEqual(second, first);
  assert.equal(requests, 1, 'the first check must commit a bounded retry or reusable candidate');
});

test('a saturated exact acknowledgement history never returns an unacknowledgeable offer', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const targetDigest = `sha256:${'f'.repeat(64)}`;
  const state = cachedStateWithHistory();
  let projected = candidateStateForDigest(state, targetDigest);
  let index = 0;
  while (Buffer.byteLength(compactStateSource(projected)) <= maxCacheStateBytes) {
    state.notification.acknowledgedDigests.push(historyDigest(index));
    index += 1;
    projected = candidateStateForDigest(state, targetDigest);
  }
  assert.ok(Buffer.byteLength(compactStateSource(state)) <= maxCacheStateBytes);
  assert.ok(Buffer.byteLength(compactStateSource(projected)) > maxCacheStateBytes);
  const exactHistory = [...state.notification.acknowledgedDigests];
  writeCompactCommittedState(testFixture, state);
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteReleaseForVersion('2.16.0', 'f'.repeat(64)));
  }));
  const acknowledgement = result.status === 'update_available'
    ? await acknowledgeUpdate({
      releasePath: testFixture.releasePath,
      cacheDirectory: testFixture.cacheDirectory,
      eventKey: result.eventKey,
    })
    : null;

  assert.deepEqual({ result, acknowledgement }, {
    result: { status: 'silent', reason: 'cache-unavailable' },
    acknowledgement: null,
  });
  assert.equal(requests, 1);
  assert.ok(committedStateFiles(testFixture).every(
    (target) => fs.statSync(target).size <= maxCacheStateBytes,
  ));
  assert.deepEqual(
    JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8')).notification.acknowledgedDigests,
    exactHistory,
  );

  assert.deepEqual(await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteReleaseForVersion('2.16.0', 'f'.repeat(64)));
  })), { status: 'silent', reason: 'cache-valid' });
  assert.equal(requests, 1, 'capacity rejection should commit a bounded retry delay');
});

test('capacity backoff withdraws a stale candidate while preserving its late acknowledgement', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const staleDigest = `sha256:${'b'.repeat(64)}`;
  const replacementDigest = `sha256:${'c'.repeat(64)}`;
  const state = cachedStateWithHistory({ offeredDigests: [staleDigest] });
  state.candidate = {
    version: '2.16.0',
    targetDigest: staleDigest,
    severity: 'normal',
    releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0',
  };
  let replacement = candidateStateForDigest(state, replacementDigest);
  let index = 0;
  while (Buffer.byteLength(compactStateSource(replacement)) <= maxCacheStateBytes) {
    state.notification.acknowledgedDigests.push(historyDigest(index));
    index += 1;
    replacement = candidateStateForDigest(state, replacementDigest);
  }
  assert.ok(Buffer.byteLength(compactStateSource(state)) <= maxCacheStateBytes);
  assert.ok(Buffer.byteLength(compactStateSource(replacement)) > maxCacheStateBytes);
  const exactHistory = [...state.notification.acknowledgedDigests];
  writeCompactCommittedState(testFixture, state);
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteReleaseForVersion('2.16.0', 'c'.repeat(64)));
  };

  const refresh = await checkForUpdate(options(testFixture, fetchImpl));
  const cached = await checkForUpdate(options(testFixture, fetchImpl));
  const staleEventKey = `archify@${staleDigest}`;
  const lateAcknowledgement = await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: staleEventKey,
  });

  assert.deepEqual({ refresh, cached, lateAcknowledgement }, {
    refresh: { status: 'silent', reason: 'cache-unavailable' },
    cached: { status: 'silent', reason: 'cache-valid' },
    lateAcknowledgement: { status: 'acknowledged', eventKey: staleEventKey },
  });
  assert.equal(requests, 1);
  assert.ok(committedStateFiles(testFixture).every(
    (target) => fs.statSync(target).size <= maxCacheStateBytes,
  ));
  const persisted = JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8'));
  assert.equal(Object.hasOwn(persisted, 'candidate'), false);
  assert.deepEqual(persisted.notification, {
    offeredDigests: [],
    acknowledgedDigests: [...exactHistory, staleDigest],
  });
});

test('a 64 KiB state can be acknowledged but a 64 KiB plus one state is ignored', async () => {
  const targetDigest = `sha256:${'e'.repeat(64)}`;
  const exactState = cachedStateWithHistory({ offeredDigests: [targetDigest] });
  exactState.candidate = {
    version: '2.16.0',
    targetDigest,
    severity: 'normal',
    releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0',
  };
  const initialBytes = Buffer.byteLength(compactStateSource(exactState));
  exactState.check.nextCheckAt += 'x'.repeat(maxCacheStateBytes - initialBytes);
  assert.equal(Buffer.byteLength(compactStateSource(exactState)), maxCacheStateBytes);

  const exactFixture = fixture();
  try {
    writeCompactCommittedState(exactFixture, exactState);
    const eventKey = `archify@${targetDigest}`;
    assert.deepEqual(await acknowledgeUpdate({
      releasePath: exactFixture.releasePath,
      cacheDirectory: exactFixture.cacheDirectory,
      eventKey,
    }), { status: 'acknowledged', eventKey });
    assert.equal(fs.statSync(statePath(exactFixture)).size, maxCacheStateBytes);
    assert.deepEqual(
      JSON.parse(fs.readFileSync(statePath(exactFixture), 'utf8')).notification,
      { offeredDigests: [], acknowledgedDigests: [targetDigest] },
    );
  } finally {
    fs.rmSync(exactFixture.root, { recursive: true, force: true });
  }

  const oversizedFixture = fixture();
  try {
    const target = writeCompactCommittedState(oversizedFixture, exactState);
    fs.appendFileSync(target, ' ');
    assert.equal(fs.statSync(target).size, maxCacheStateBytes + 1);
    assert.deepEqual(await acknowledgeUpdate({
      releasePath: oversizedFixture.releasePath,
      cacheDirectory: oversizedFixture.cacheDirectory,
      eventKey: `archify@${targetDigest}`,
    }), { status: 'silent', reason: 'invalid-acknowledgement' });
  } finally {
    fs.rmSync(oversizedFixture.root, { recursive: true, force: true });
  }
});

test('a boundary offer remains acknowledgeable without pruning exact history', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const targetDigest = `sha256:${'d'.repeat(64)}`;
  const state = cachedStateWithHistory();
  let index = 0;
  while (true) {
    state.notification.acknowledgedDigests.push(historyDigest(index));
    if (Buffer.byteLength(compactStateSource(
      candidateStateForDigest(state, targetDigest),
    )) > maxCacheStateBytes) {
      state.notification.acknowledgedDigests.pop();
      break;
    }
    index += 1;
  }
  const projected = candidateStateForDigest(state, targetDigest);
  assert.ok(Buffer.byteLength(compactStateSource(projected)) <= maxCacheStateBytes);
  const oneMoreAcknowledgement = cachedStateWithHistory({
    acknowledgedDigests: [...state.notification.acknowledgedDigests, historyDigest(index)],
  });
  assert.ok(Buffer.byteLength(compactStateSource(
    candidateStateForDigest(oneMoreAcknowledgement, targetDigest),
  )) > maxCacheStateBytes);
  const exactHistory = [...state.notification.acknowledgedDigests];
  writeCompactCommittedState(testFixture, state);
  const fetchImpl = async () => response(remoteReleaseForVersion('2.16.0', 'd'.repeat(64)));

  const offered = await checkForUpdate(options(testFixture, fetchImpl));
  assert.equal(offered.status, 'update_available');
  assert.deepEqual(await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: offered.eventKey,
  }), { status: 'acknowledged', eventKey: offered.eventKey });

  assert.ok(committedStateFiles(testFixture).every(
    (target) => fs.statSync(target).size <= maxCacheStateBytes,
  ));
  assert.deepEqual(
    JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8')).notification,
    {
      offeredDigests: [],
      acknowledgedDigests: [...exactHistory, targetDigest],
    },
  );
  assert.deepEqual(await checkForUpdate(options(testFixture, fetchImpl)), {
    status: 'silent',
    reason: 'already-notified',
  });
});

test('opaque response validators are neither persisted nor replayed after the check TTL', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let requests = 0;
  const fetchImpl = async (_url, init) => {
    requests += 1;
    assert.equal(init.headers['if-none-match'], undefined);
    return response(remoteRelease(), { etag: `"per-client-${requests}"` });
  };

  const first = await checkForUpdate(options(testFixture, fetchImpl));
  assert.equal(first.status, 'update_available');

  const second = await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }));
  assert.equal(second.status, 'update_available');
  assert.equal(second.eventKey, first.eventKey);
  assert.equal(requests, 2);
  assert.equal(Object.hasOwn(JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8')).check, 'etag'), false);
});

test('an HTTP 304 is always a failed unconditional refresh', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    if (requests === 1) {
      return new Response(JSON.stringify(remoteRelease()), {
        status: 200,
        headers: { 'content-type': 'application/json' },
      });
    }
    return new Response(null, { status: 304 });
  };

  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
  const refresh = await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }));

  assert.deepEqual(refresh, { status: 'silent', reason: 'check-failed' });
  assert.equal(requests, 2);
});

test('a failed refresh preserves the last-good unacknowledged candidate', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    if (requests === 1) return response(remoteRelease());
    throw new Error('offline');
  };

  const first = await checkForUpdate(options(testFixture, fetchImpl));
  assert.equal(first.status, 'update_available');
  const failed = await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }));
  assert.deepEqual(failed, { status: 'silent', reason: 'check-failed' });

  const cached = await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (74 * 60 * 60 * 1_000),
  }));
  assert.equal(cached.status, 'update_available');
  assert.equal(cached.eventKey, first.eventKey);
  assert.equal(requests, 2);
});

test('failure backoff saturates safely instead of overflowing the cache counter', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  writeJson(statePath(testFixture), {
    schemaVersion: 1,
    skillId: 'archify',
    installedVersion: '2.15.0',
    check: {
      nextCheckAt: new Date(baseTime - 1_000).toISOString(),
      consecutiveFailures: Number.MAX_SAFE_INTEGER,
    },
    notification: {
      offeredDigests: [],
      acknowledgedDigests: [],
    },
  });
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    throw new Error('offline');
  };

  assert.deepEqual(await checkForUpdate(options(testFixture, fetchImpl)), {
    status: 'silent',
    reason: 'check-failed',
  });
  assert.deepEqual(await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (60 * 60 * 1_000),
  })), { status: 'silent', reason: 'cache-valid' });
  assert.equal(requests, 1);
  assert.equal(JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8')).check.consecutiveFailures, 2);
});

test('a committed cache FIFO is ignored without blocking the update check', (t) => {
  if (process.platform === 'win32') {
    t.skip('FIFO files are unavailable on Windows');
    return;
  }
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const cacheStatePath = statePath(testFixture);
  fs.mkdirSync(path.dirname(cacheStatePath), { recursive: true });
  if (!createFifo(cacheStatePath)) {
    t.skip('mkfifo is unavailable');
    return;
  }

  assertUnsafeCacheStateIsIgnored(testFixture);
});

test('a committed cache state symlink is ignored without following its target', (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const cacheStatePath = statePath(testFixture);
  const linkedTarget = path.join(testFixture.root, 'linked-state-target');
  fs.mkdirSync(path.dirname(cacheStatePath), { recursive: true });
  const targetIsFifo = process.platform !== 'win32' && createFifo(linkedTarget);
  if (!targetIsFifo) writeJson(linkedTarget, cachedUpdateState());
  fs.symlinkSync(linkedTarget, cacheStatePath, process.platform === 'win32' ? 'file' : undefined);

  assertUnsafeCacheStateIsIgnored(testFixture);
});

test('an oversized committed cache state is ignored without an unbounded read', (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const oversizedState = `${JSON.stringify(cachedUpdateState())}${' '.repeat(256 * 1_024)}`;
  const cacheStatePath = statePath(testFixture);
  fs.mkdirSync(path.dirname(cacheStatePath), { recursive: true });
  fs.writeFileSync(cacheStatePath, oversizedState);

  assertUnsafeCacheStateIsIgnored(testFixture);
});

test('a cache ancestor replaced before a cached read cannot inject a reminder', async (t) => {
  const testFixture = fixture();
  const originalReaddir = fsPromises.readdir;
  t.after(() => {
    fsPromises.readdir = originalReaddir;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  writeCompactCommittedState(testFixture, cachedStateWithHistory());
  const replacementRoot = path.join(testFixture.root, 'replacement-cache');
  writeJson(
    path.join(replacementRoot, path.basename(stateDirectory(testFixture)),
      path.basename(operationPath(testFixture, 'committed', 1n)), 'state.json'),
    cachedUpdateState(),
  );
  const detachedCache = path.join(testFixture.root, 'detached-cache');
  const probe = path.join(testFixture.root, 'symlink-probe');
  try {
    fs.symlinkSync(
      replacementRoot,
      probe,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
    fs.unlinkSync(probe);
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  let replaced = false;
  fsPromises.readdir = async (target, ...args) => {
    if (!replaced && path.resolve(target) === path.resolve(stateDirectory(testFixture))) {
      replaced = true;
      fs.renameSync(testFixture.cacheDirectory, detachedCache);
      fs.symlinkSync(
        replacementRoot,
        testFixture.cacheDirectory,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
    }
    return originalReaddir(target, ...args);
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
});

test('a committed directory replaced by a symlink cannot inject a reminder', async (t) => {
  const testFixture = fixture();
  const originalReaddir = fsPromises.readdir;
  t.after(() => {
    fsPromises.readdir = originalReaddir;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  writeCompactCommittedState(testFixture, cachedStateWithHistory());
  const committed = operationPath(testFixture, 'committed', 1n);
  const replacement = path.join(testFixture.root, 'replacement-committed');
  writeJson(path.join(replacement, 'state.json'), cachedUpdateState());
  const probe = path.join(testFixture.root, 'nested-symlink-probe');
  try {
    fs.symlinkSync(
      replacement,
      probe,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
    fs.unlinkSync(probe);
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  const detached = path.join(stateDirectory(testFixture), 'detached-committed');
  let replaced = false;
  fsPromises.readdir = async (target, ...args) => {
    const entries = await originalReaddir(target, ...args);
    if (!replaced && path.resolve(target) === path.resolve(stateDirectory(testFixture))) {
      replaced = true;
      fs.renameSync(committed, detached);
      fs.symlinkSync(
        replacement,
        committed,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
    }
    return entries;
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
});

test('a pending directory replaced by a symlink cannot be ignored as inactive', async (t) => {
  const testFixture = fixture();
  const originalReaddir = fsPromises.readdir;
  t.after(() => {
    fsPromises.readdir = originalReaddir;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const pending = writePendingOperation(testFixture);
  const replacement = path.join(testFixture.root, 'replacement-pending');
  writePendingOperation({
    ...testFixture,
    cacheDirectory: path.join(testFixture.root, 'replacement-cache'),
  }, { modifiedAt: new Date(Date.now() - 60_000) });
  const replacementPending = operationPath({
    ...testFixture,
    cacheDirectory: path.join(testFixture.root, 'replacement-cache'),
  }, 'pending', 1n);
  fs.renameSync(replacementPending, replacement);
  const probe = path.join(testFixture.root, 'pending-symlink-probe');
  try {
    fs.symlinkSync(
      replacement,
      probe,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
    fs.unlinkSync(probe);
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  const detached = path.join(stateDirectory(testFixture), 'detached-pending');
  let replaced = false;
  fsPromises.readdir = async (target, ...args) => {
    const entries = await originalReaddir(target, ...args);
    if (!replaced && path.resolve(target) === path.resolve(stateDirectory(testFixture))) {
      replaced = true;
      fs.renameSync(pending, detached);
      fs.symlinkSync(
        replacement,
        pending,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
    }
    return entries;
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
});

test('zero-inode cache metadata uses timestamp fallback to reject a replaced ancestor', async (t) => {
  const testFixture = fixture();
  const originalLstat = fsPromises.lstat;
  const originalReaddir = fsPromises.readdir;
  t.after(() => {
    fsPromises.lstat = originalLstat;
    fsPromises.readdir = originalReaddir;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  writeCompactCommittedState(testFixture, cachedStateWithHistory());
  const replacementRoot = path.join(testFixture.root, 'replacement-cache');
  writeJson(
    path.join(replacementRoot, path.basename(stateDirectory(testFixture)),
      path.basename(operationPath(testFixture, 'committed', 1n)), 'state.json'),
    cachedUpdateState(),
  );
  const detachedCache = path.join(testFixture.root, 'detached-cache');
  fsPromises.lstat = async (target, ...args) => {
    const metadata = await originalLstat(target, ...args);
    const resolved = path.resolve(target);
    const isCachePath = resolved === path.resolve(testFixture.cacheDirectory)
      || resolved.startsWith(`${path.resolve(testFixture.cacheDirectory)}${path.sep}`);
    if (args[0]?.bigint && isCachePath && metadata.isDirectory()) {
      return metadataWithOverrides(metadata, {
        ino: 0n,
        birthtimeNs: replaced ? 2_000n : 1_000n,
      });
    }
    return metadata;
  };
  let replaced = false;
  fsPromises.readdir = async (target, ...args) => {
    if (!replaced && path.resolve(target) === path.resolve(stateDirectory(testFixture))) {
      replaced = true;
      fs.renameSync(testFixture.cacheDirectory, detachedCache);
      fs.renameSync(replacementRoot, testFixture.cacheDirectory);
    }
    return originalReaddir(target, ...args);
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
});

test('zero-inode cache metadata still supports a normal check and acknowledgement', async (t) => {
  const testFixture = fixture();
  const originalLstat = fsPromises.lstat;
  const originalOpen = fsPromises.open;
  t.after(() => {
    fsPromises.lstat = originalLstat;
    fsPromises.open = originalOpen;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const cacheRoot = path.resolve(testFixture.cacheDirectory);
  const isCachePath = (target) => {
    const resolved = path.resolve(target);
    return resolved === cacheRoot || resolved.startsWith(`${cacheRoot}${path.sep}`);
  };
  fsPromises.lstat = async (target, ...args) => {
    const metadata = await originalLstat(target, ...args);
    if (args[0]?.bigint && isCachePath(target)) {
      return metadataWithOverrides(metadata, { ino: 0n, birthtimeNs: 1_000n });
    }
    return metadata;
  };
  fsPromises.open = async (target, ...args) => {
    const handle = await originalOpen(target, ...args);
    if (!isCachePath(target)) return handle;
    return new Proxy(handle, {
      get(fileHandle, property) {
        if (property === 'stat') {
          return async (...statArguments) => {
            const metadata = await fileHandle.stat(...statArguments);
            return statArguments[0]?.bigint
              ? metadataWithOverrides(metadata, { ino: 0n, birthtimeNs: 1_000n })
              : metadataWithOverrides(metadata, { ino: 0 });
          };
        }
        const value = Reflect.get(fileHandle, property, fileHandle);
        return typeof value === 'function' ? value.bind(fileHandle) : value;
      },
    });
  };
  let requests = 0;

  const offered = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));
  const acknowledged = await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: offered.eventKey,
  });

  assert.equal(offered.status, 'update_available');
  assert.deepEqual(acknowledged, { status: 'acknowledged', eventKey: offered.eventKey });
  assert.equal(requests, 1);
});

test('zero-inode cache metadata without birthtime fails closed', async (t) => {
  const testFixture = fixture();
  const originalLstat = fsPromises.lstat;
  t.after(() => {
    fsPromises.lstat = originalLstat;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const cacheRoot = path.resolve(testFixture.cacheDirectory);
  fsPromises.lstat = async (target, ...args) => {
    const metadata = await originalLstat(target, ...args);
    const resolved = path.resolve(target);
    if (args[0]?.bigint
      && (resolved === cacheRoot || resolved.startsWith(`${cacheRoot}${path.sep}`))) {
      return metadataWithOverrides(metadata, { ino: 0n, birthtimeNs: 0n });
    }
    return metadata;
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
});

test('a state write replaced before post-verification is never reported as committed', async (t) => {
  const testFixture = fixture();
  const originalOpen = fsPromises.open;
  const originalWriteFile = fsPromises.writeFile;
  t.after(() => {
    fsPromises.open = originalOpen;
    fsPromises.writeFile = originalWriteFile;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  let replaced = false;
  const replaceStateFile = (target) => {
    if (replaced) return;
    replaced = true;
    fs.renameSync(target, `${target}.written-by-checker`);
    fs.writeFileSync(target, '{}\n', { flag: 'wx', mode: 0o600 });
  };
  fsPromises.writeFile = async (target, ...args) => {
    const result = await originalWriteFile(target, ...args);
    if (path.basename(target).startsWith('.state.')) replaceStateFile(target);
    return result;
  };
  fsPromises.open = async (target, ...args) => {
    const handle = await originalOpen(target, ...args);
    if (!path.basename(target).startsWith('.state.')) return handle;
    return {
      stat: handle.stat.bind(handle),
      writeFile: handle.writeFile.bind(handle),
      close: async () => {
        const result = await handle.close();
        replaceStateFile(target);
        return result;
      },
    };
  };

  const result = await checkForUpdate(options(testFixture, async () => response(remoteRelease())));

  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  for (const committed of committedStateFiles(testFixture)) {
    assert.notDeepEqual(JSON.parse(fs.readFileSync(committed, 'utf8')), {});
  }
});

test('a pending directory replaced by a symlink cannot be reported as committed', async (t) => {
  const testFixture = fixture();
  const originalRename = fsPromises.rename;
  t.after(() => {
    fsPromises.rename = originalRename;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const probe = path.join(testFixture.root, 'symlink-probe');
  try {
    fs.mkdirSync(probe);
    const probeLink = `${probe}-link`;
    fs.symlinkSync(probe, probeLink, process.platform === 'win32' ? 'junction' : 'dir');
    fs.unlinkSync(probeLink);
    fs.rmdirSync(probe);
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  const detachedPending = path.join(testFixture.root, 'detached-pending');
  let pending = null;
  let replaced = false;
  fsPromises.rename = async (source, destination, ...args) => {
    if (!pending
      && path.basename(source).startsWith('.state.')
      && path.basename(destination) === 'state.json') {
      const result = await originalRename(source, destination, ...args);
      pending = path.dirname(destination);
      return result;
    }
    if (!replaced
      && pending
      && path.resolve(source) === path.resolve(pending)
      && path.basename(destination).startsWith('committed-')) {
      replaced = true;
      fs.renameSync(pending, detachedPending);
      fs.symlinkSync(
        detachedPending,
        pending,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
    }
    return originalRename(source, destination, ...args);
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 1);
  assert.equal(fs.existsSync(path.join(detachedPending, 'state.json')), true);
});

test('a cache trust failure while cancelling cannot return a cached reminder', async (t) => {
  const testFixture = fixture();
  const originalRename = fsPromises.rename;
  t.after(() => {
    fsPromises.rename = originalRename;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const stale = cachedUpdateState();
  stale.check.nextCheckAt = new Date(baseTime - 1_000).toISOString();
  const committed = writeCompactCommittedState(testFixture, stale);
  const detachedCache = path.join(testFixture.root, 'detached-cache');
  let refreshed = false;
  let replaced = false;
  fsPromises.rename = async (source, destination, ...args) => {
    const sourceName = path.basename(source);
    const destinationName = path.basename(destination);
    if (!refreshed && sourceName.startsWith('claim-') && destinationName === 'active-claim') {
      const result = await originalRename(source, destination, ...args);
      const fresh = cachedUpdateState();
      writeJson(committed, fresh);
      refreshed = true;
      return result;
    }
    if (!replaced && sourceName.startsWith('pending-') && destinationName.startsWith('cancelled-')) {
      replaced = true;
      fs.renameSync(testFixture.cacheDirectory, detachedCache);
      fs.symlinkSync(
        detachedCache,
        testFixture.cacheDirectory,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
    }
    return originalRename(source, destination, ...args);
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(refreshed, true);
  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
});

test('prepared claim cleanup never recursively deletes through a replaced cache ancestor', async (t) => {
  const testFixture = fixture();
  const originalOpen = fsPromises.open;
  const originalRm = fsPromises.rm;
  const originalWriteFile = fsPromises.writeFile;
  t.after(() => {
    fsPromises.open = originalOpen;
    fsPromises.rm = originalRm;
    fsPromises.writeFile = originalWriteFile;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const activeClaim = path.join(stateDirectory(testFixture), 'active-claim');
  let injectedActiveClaim = false;
  const injectActiveClaim = () => {
    if (injectedActiveClaim) return;
    injectedActiveClaim = true;
    fs.mkdirSync(activeClaim);
  };
  fsPromises.writeFile = async (target, ...args) => {
    const result = await originalWriteFile(target, ...args);
    if (path.basename(path.dirname(target)).startsWith('claim-')
      && path.basename(target) === 'owner.json') injectActiveClaim();
    return result;
  };
  fsPromises.open = async (target, ...args) => {
    const handle = await originalOpen(target, ...args);
    if (path.basename(path.dirname(target)).startsWith('claim-')
      && path.basename(target) === 'owner.json') {
      return {
        stat: handle.stat.bind(handle),
        writeFile: handle.writeFile.bind(handle),
        close: async () => {
          const result = await handle.close();
          injectActiveClaim();
          return result;
        },
      };
    }
    return handle;
  };
  const victimRoot = path.join(testFixture.root, 'victim-cache');
  const victimClaim = path.join(
    victimRoot,
    path.basename(stateDirectory(testFixture)),
    path.basename(operationPath(testFixture, 'claim', 1n)),
  );
  const victimFile = path.join(victimClaim, 'important.txt');
  fs.mkdirSync(victimClaim, { recursive: true });
  fs.writeFileSync(victimFile, 'must survive\n');
  const detachedCache = path.join(testFixture.root, 'detached-cache');
  let interceptedRemoval = false;
  fsPromises.rm = async (target, ...args) => {
    if (!interceptedRemoval && path.basename(target).startsWith('claim-')) {
      interceptedRemoval = true;
      fs.renameSync(testFixture.cacheDirectory, detachedCache);
      fs.symlinkSync(
        victimRoot,
        testFixture.cacheDirectory,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
    }
    return originalRm(target, ...args);
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(injectedActiveClaim, true);
  assert.equal(requests, 0);
  assert.equal(result.status, 'silent');
  assert.equal(interceptedRemoval, false);
  assert.equal(fs.readFileSync(victimFile, 'utf8'), 'must survive\n');
  const coordinationEntries = fs.readdirSync(stateDirectory(testFixture));
  assert.deepEqual(
    coordinationEntries.filter((name) => name.startsWith('claim-')),
    [],
  );
  const discardedClaims = coordinationEntries
    .filter((name) => name.startsWith('discarded-claim-'));
  assert.equal(discardedClaims.length, 1);
  const discardedOwnerPath = path.join(
    stateDirectory(testFixture),
    discardedClaims[0],
    'owner.json',
  );
  const discardedOwner = JSON.parse(fs.readFileSync(discardedOwnerPath, 'utf8'));
  assert.deepEqual(Object.keys(discardedOwner).sort(), ['generation', 'pid', 'token']);
  assert.equal(discardedOwner.generation, '1');
  assert.match(discardedOwner.token, /^[a-f0-9]{32}$/);
  assert.equal(discardedClaims[0].endsWith(`-${discardedOwner.token}`), true);
});

test('a symlink cache root cannot write into its target', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const protectedTarget = path.join(testFixture.root, 'simulated-project-config');
  fs.mkdirSync(protectedTarget);
  fs.writeFileSync(path.join(protectedTarget, 'settings.json'), '{"protected":true}\n');
  try {
    fs.symlinkSync(
      protectedTarget,
      testFixture.cacheDirectory,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
  assert.deepEqual(fs.readdirSync(protectedTarget), ['settings.json']);
});

test('a symlink cache ancestor cannot redirect cache writes', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const protectedTarget = path.join(testFixture.root, 'simulated-project-config');
  const linkedAncestor = path.join(testFixture.root, 'cache-parent');
  const cacheDirectory = path.join(linkedAncestor, 'nested', 'cache');
  fs.mkdirSync(protectedTarget);
  fs.writeFileSync(path.join(protectedTarget, 'settings.json'), '{"protected":true}\n');
  try {
    fs.symlinkSync(
      protectedTarget,
      linkedAncestor,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }, { cacheDirectory }));

  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
  assert.deepEqual(fs.readdirSync(protectedTarget), ['settings.json']);
});

test('a cache ancestor replaced during preparation cannot redirect later writes', async (t) => {
  const testFixture = fixture();
  const originalLstat = fsPromises.lstat;
  t.after(() => {
    fsPromises.lstat = originalLstat;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const protectedTarget = path.join(testFixture.root, 'simulated-project-config');
  const protectedVersion = path.join(protectedTarget, path.basename(stateDirectory(testFixture)));
  const detachedCache = path.join(testFixture.root, 'detached-cache');
  const probe = path.join(testFixture.root, 'symlink-probe');
  fs.mkdirSync(testFixture.cacheDirectory);
  fs.mkdirSync(protectedVersion, { recursive: true });
  fs.writeFileSync(path.join(protectedVersion, 'settings.json'), '{"protected":true}\n');
  try {
    fs.symlinkSync(
      protectedTarget,
      probe,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
    fs.unlinkSync(probe);
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  let replaced = false;
  const versionDirectoryName = path.basename(stateDirectory(testFixture));
  fsPromises.lstat = async (target, ...args) => {
    if (!replaced && path.basename(target) === versionDirectoryName) {
      replaced = true;
      fs.renameSync(testFixture.cacheDirectory, detachedCache);
      fs.symlinkSync(
        protectedTarget,
        testFixture.cacheDirectory,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
    }
    return originalLstat(target, ...args);
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
  assert.deepEqual(fs.readdirSync(protectedVersion), ['settings.json']);
});

test('a cache ancestor replaced before reservation creation fails closed', async (t) => {
  const testFixture = fixture();
  const originalMkdir = fsPromises.mkdir;
  t.after(() => {
    fsPromises.mkdir = originalMkdir;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const replacementRoot = path.join(testFixture.root, 'replacement-cache');
  const replacementVersion = path.join(
    replacementRoot,
    path.basename(stateDirectory(testFixture)),
  );
  const detachedCache = path.join(testFixture.root, 'detached-cache');
  const probe = path.join(testFixture.root, 'symlink-probe');
  fs.mkdirSync(replacementVersion, { recursive: true });
  fs.writeFileSync(path.join(replacementVersion, 'settings.json'), '{"protected":true}\n');
  try {
    fs.symlinkSync(
      replacementRoot,
      probe,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
    fs.unlinkSync(probe);
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  let replaced = false;
  fsPromises.mkdir = async (target, ...args) => {
    if (!replaced && path.basename(target).startsWith('reserved-')) {
      replaced = true;
      fs.renameSync(testFixture.cacheDirectory, detachedCache);
      fs.symlinkSync(
        replacementRoot,
        testFixture.cacheDirectory,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
    }
    return originalMkdir(target, ...args);
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(replaced, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
  const replacementEntries = fs.readdirSync(replacementVersion).sort();
  const reservations = replacementEntries.filter((entry) => entry.startsWith('reserved-'));
  assert.equal(reservations.length, 1);
  assert.deepEqual(
    replacementEntries.filter((entry) => !entry.startsWith('reserved-')),
    ['settings.json'],
  );
  assert.deepEqual(fs.readdirSync(path.join(replacementVersion, reservations[0])), []);
});

test('a cache ancestor replacement restored after reservation creation fails closed', async (t) => {
  const testFixture = fixture();
  const originalMkdir = fsPromises.mkdir;
  t.after(() => {
    fsPromises.mkdir = originalMkdir;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const replacementRoot = path.join(testFixture.root, 'replacement-cache');
  const replacementVersion = path.join(
    replacementRoot,
    path.basename(stateDirectory(testFixture)),
  );
  const detachedCache = path.join(testFixture.root, 'detached-cache');
  const probe = path.join(testFixture.root, 'symlink-probe');
  fs.mkdirSync(replacementVersion, { recursive: true });
  fs.writeFileSync(path.join(replacementVersion, 'settings.json'), '{"protected":true}\n');
  try {
    fs.symlinkSync(
      replacementRoot,
      probe,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
    fs.unlinkSync(probe);
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  let restored = false;
  fsPromises.mkdir = async (target, ...args) => {
    if (!restored && path.basename(target).startsWith('reserved-')) {
      fs.renameSync(testFixture.cacheDirectory, detachedCache);
      fs.symlinkSync(
        replacementRoot,
        testFixture.cacheDirectory,
        process.platform === 'win32' ? 'junction' : 'dir',
      );
      try {
        return await originalMkdir(target, ...args);
      } finally {
        fs.unlinkSync(testFixture.cacheDirectory);
        fs.renameSync(detachedCache, testFixture.cacheDirectory);
        restored = true;
      }
    }
    return originalMkdir(target, ...args);
  };
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(restored, true);
  assert.deepEqual(result, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 0);
  const replacementEntries = fs.readdirSync(replacementVersion).sort();
  const reservations = replacementEntries.filter((entry) => entry.startsWith('reserved-'));
  assert.equal(reservations.length, 1);
  assert.deepEqual(
    replacementEntries.filter((entry) => !entry.startsWith('reserved-')),
    ['settings.json'],
  );
  assert.deepEqual(fs.readdirSync(path.join(replacementVersion, reservations[0])), []);
});

test('a trusted cache prefix switched after validation cannot redirect later writes', async (t) => {
  const testFixture = fixture();
  const originalHomedir = os.homedir;
  const originalLstat = fsPromises.lstat;
  t.after(() => {
    os.homedir = originalHomedir;
    fsPromises.lstat = originalLstat;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const trustedAlias = path.join(testFixture.root, 'trusted-home');
  const canonicalHome = path.join(testFixture.root, 'canonical-home');
  const protectedTarget = path.join(testFixture.root, 'simulated-project-config');
  const cacheDirectory = path.join(trustedAlias, 'cache');
  fs.mkdirSync(canonicalHome);
  fs.mkdirSync(protectedTarget);
  fs.writeFileSync(path.join(protectedTarget, 'settings.json'), '{"protected":true}\n');
  try {
    fs.symlinkSync(
      canonicalHome,
      trustedAlias,
      process.platform === 'win32' ? 'junction' : 'dir',
    );
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`directory symlinks are unavailable: ${error.code}`);
      return;
    }
    throw error;
  }
  os.homedir = () => trustedAlias;
  const canonicalVersionDirectory = path.join(
    fs.realpathSync(canonicalHome),
    'cache',
    path.basename(stateDirectory(testFixture)),
  );
  let targetVisits = 0;
  let switched = false;
  fsPromises.lstat = async (target, ...args) => {
    const metadata = await originalLstat(target, ...args);
    if (!switched && path.resolve(target) === canonicalVersionDirectory) {
      targetVisits += 1;
      if (targetVisits === 2) {
        switched = true;
        fs.unlinkSync(trustedAlias);
        fs.symlinkSync(
          protectedTarget,
          trustedAlias,
          process.platform === 'win32' ? 'junction' : 'dir',
        );
      }
    }
    return metadata;
  };

  const result = await checkForUpdate(options(testFixture, async () => response(remoteRelease()), {
    cacheDirectory,
  }));

  assert.equal(switched, true);
  assert.equal(result.status, 'update_available');
  assert.deepEqual(fs.readdirSync(protectedTarget), ['settings.json']);
  assert.ok(fs.readdirSync(canonicalVersionDirectory).some(
    (entry) => entry.startsWith('committed-'),
  ));
});

test('concurrent checks use one writer and leave a valid cache', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let releaseFetch;
  let markStarted;
  const started = new Promise((resolve) => { markStarted = resolve; });
  const fetchImpl = async () => {
    markStarted();
    return new Promise((resolve) => { releaseFetch = resolve; });
  };

  const firstCheck = checkForUpdate(options(testFixture, fetchImpl));
  await started;
  const overlapping = await checkForUpdate(options(testFixture, fetchImpl));
  assert.deepEqual(overlapping, { status: 'silent', reason: 'check-in-progress' });

  releaseFetch(response(remoteRelease()));
  const first = await firstCheck;
  assert.equal(first.status, 'update_available');
  assert.doesNotThrow(() => JSON.parse(
    fs.readFileSync(statePath(testFixture), 'utf8'),
  ));
});

test('an empty precheck snapshot cannot start a second concurrent network request', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  fs.mkdirSync(stateDirectory(testFixture), { recursive: true });
  const pause = pauseReaddirSnapshot(stateDirectory(testFixture), 2);
  t.after(() => pause.restore());
  let releaseFetch;
  let markFetchStarted;
  let requests = 0;
  const fetchStarted = new Promise((resolve) => { markFetchStarted = resolve; });
  const fetchImpl = async () => {
    requests += 1;
    markFetchStarted();
    return new Promise((resolve) => { releaseFetch = resolve; });
  };

  const delayedPrecheck = checkForUpdate(options(testFixture, fetchImpl));
  await pause.reached;
  const claimedCheck = checkForUpdate(options(testFixture, fetchImpl));
  await fetchStarted;
  pause.release();
  assert.deepEqual(await delayedPrecheck, { status: 'silent', reason: 'check-in-progress' });
  assert.equal(requests, 1);

  releaseFetch(response(remoteRelease()));
  assert.equal((await claimedCheck).status, 'update_available');
  pause.restore();
  assert.equal(requests, 1);
});

test('two promoters cannot replace and then steal a stale empty active claim', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const activeClaim = path.join(stateDirectory(testFixture), 'active-claim');
  fs.mkdirSync(activeClaim, { recursive: true });
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(activeClaim, staleTime, staleTime);
  const pause = pauseWriteOnce('.retirement-guard');
  t.after(() => pause.restore());
  let releaseFetch;
  let markFetchStarted;
  let requests = 0;
  const fetchStarted = new Promise((resolve) => { markFetchStarted = resolve; });
  const fetchImpl = async () => {
    requests += 1;
    markFetchStarted();
    return new Promise((resolve) => { releaseFetch = resolve; });
  };

  const delayedRetirement = checkForUpdate(options(testFixture, fetchImpl));
  await pause.reached;
  fs.utimesSync(
    path.join(operationPath(testFixture, 'pending', 1n), 'owner.json'),
    staleTime,
    staleTime,
  );
  const claimedCheck = checkForUpdate(options(testFixture, fetchImpl));
  await fetchStarted;
  pause.release();
  assert.deepEqual(await delayedRetirement, { status: 'silent', reason: 'check-in-progress' });
  assert.equal(requests, 1);

  releaseFetch(response(remoteRelease()));
  assert.equal((await claimedCheck).status, 'update_available');
  pause.restore();
  assert.equal(requests, 1);
});

test('a delayed lower-generation promoter cannot retire a completed higher claim', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const pause = pauseRenameOnce(
    operationPath(testFixture, 'claim', 1n),
    path.join(stateDirectory(testFixture), 'active-claim'),
  );
  t.after(() => pause.restore());
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  const delayedLower = checkForUpdate(options(testFixture, fetchImpl));
  await pause.reached;
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(
    path.join(operationPath(testFixture, 'pending', 1n), 'owner.json'),
    staleTime,
    staleTime,
  );
  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
  assert.equal(requests, 1);

  pause.release();
  assert.equal((await delayedLower).status, 'update_available');
  pause.restore();
  assert.equal(requests, 1);
  assert.equal(
    JSON.parse(fs.readFileSync(
      path.join(stateDirectory(testFixture), 'active-claim', 'owner.json'),
      'utf8',
    )).generation,
    '2',
  );
});

test('an expired contender cannot fetch after its delayed claim promotion succeeds', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const pause = pauseRenameOnce(
    operationPath(testFixture, 'claim', 1n),
    path.join(stateDirectory(testFixture), 'active-claim'),
  );
  t.after(() => pause.restore());
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  const delayedExpired = checkForUpdate(options(testFixture, fetchImpl));
  await pause.reached;
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(
    path.join(operationPath(testFixture, 'pending', 1n), 'owner.json'),
    staleTime,
    staleTime,
  );
  pause.release();
  assert.deepEqual(await delayedExpired, { status: 'silent', reason: 'check-in-progress' });
  assert.equal(requests, 0);

  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
  pause.restore();
  assert.equal(requests, 1);
  assert.equal(
    JSON.parse(fs.readFileSync(
      path.join(stateDirectory(testFixture), 'active-claim', 'owner.json'),
      'utf8',
    )).generation,
    '2',
  );
});

test('a stale supersession snapshot cannot let a lower promoter retire a higher claim', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const generationOneClaim = path.basename(operationPath(testFixture, 'claim', 1n));
  const pause = pauseReaddirSnapshotOnce(
    stateDirectory(testFixture),
    (entries) => entries.some((entry) => entry.name === generationOneClaim)
      && !entries.some((entry) => /-(?:0{19}2)$/.test(entry.name)),
  );
  t.after(() => pause.restore());
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  const delayedLower = checkForUpdate(options(testFixture, fetchImpl));
  await pause.reached;
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(
    path.join(operationPath(testFixture, 'pending', 1n), 'owner.json'),
    staleTime,
    staleTime,
  );
  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
  assert.equal(requests, 1);

  pause.release();
  assert.equal((await delayedLower).status, 'update_available');
  pause.restore();
  assert.equal(requests, 1);
  assert.equal(
    JSON.parse(fs.readFileSync(
      path.join(stateDirectory(testFixture), 'active-claim', 'owner.json'),
      'utf8',
    )).generation,
    '2',
  );
});

test('an owner fenced while active ownership is read cannot issue a second GET', {
  timeout: parentCheckTimeoutMs,
}, async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const directory = stateDirectory(testFixture);
  const originalOpen = fsPromises.open;
  let releaseVerification;
  let markVerification;
  let intercepted = false;
  const verificationReached = new Promise((resolve) => { markVerification = resolve; });
  const verificationGate = new Promise((resolve) => { releaseVerification = resolve; });
  fsPromises.open = async (target, ...args) => {
    const handle = await originalOpen(target, ...args);
    if (intercepted
      || path.resolve(target) !== path.join(directory, 'active-claim', 'owner.json')) {
      return handle;
    }
    intercepted = true;
    return new Proxy(handle, {
      get(fileHandle, property) {
        if (property === 'close') {
          return async () => {
            const result = await fileHandle.close();
            markVerification();
            await verificationGate;
            return result;
          };
        }
        const value = Reflect.get(fileHandle, property, fileHandle);
        return typeof value === 'function' ? value.bind(fileHandle) : value;
      },
    });
  };
  t.after(() => {
    releaseVerification();
    fsPromises.open = originalOpen;
  });
  let requests = 0;
  const checkOptions = options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  });

  const suspendedOwner = checkForUpdate(checkOptions);
  await verificationReached;
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(
    path.join(operationPath(testFixture, 'pending', 1n), 'owner.json'),
    staleTime,
    staleTime,
  );
  assert.equal((await checkForUpdate(checkOptions)).status, 'update_available');
  assert.equal(requests, 1);

  releaseVerification();
  assert.deepEqual(await suspendedOwner, { status: 'silent', reason: 'cache-unavailable' });
  assert.equal(requests, 1);
});

test('an expired active owner cannot start a network request', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const pendingOne = path.basename(operationPath(testFixture, 'pending', 1n));
  const claimOne = path.basename(operationPath(testFixture, 'claim', 1n));
  const pause = pauseReaddirSnapshotOnce(
    stateDirectory(testFixture),
    (entries) => entries.some((entry) => entry.name === 'active-claim')
      && entries.some((entry) => entry.name === pendingOne)
      && !entries.some((entry) => entry.name === claimOne),
  );
  t.after(() => pause.restore());
  let requests = 0;

  const check = checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));
  await pause.reached;
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(
    path.join(operationPath(testFixture, 'pending', 1n), 'owner.json'),
    staleTime,
    staleTime,
  );
  pause.release();

  assert.deepEqual(await check, { status: 'silent', reason: 'check-in-progress' });
  pause.restore();
  assert.equal(requests, 0);
});

test('a fenced owner rechecks its active claim immediately before network access', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const pendingOne = path.basename(operationPath(testFixture, 'pending', 1n));
  const claimOne = path.basename(operationPath(testFixture, 'claim', 1n));
  const pause = pauseReaddirSnapshotOnce(
    stateDirectory(testFixture),
    (entries) => entries.some((entry) => entry.name === 'active-claim')
      && entries.some((entry) => entry.name === pendingOne)
      && !entries.some((entry) => entry.name === claimOne),
  );
  t.after(() => pause.restore());
  let requests = 0;
  let releaseFetch;
  let markFetchStarted;
  const fetchStarted = new Promise((resolve) => { markFetchStarted = resolve; });
  const fetchImpl = async () => {
    requests += 1;
    markFetchStarted();
    return new Promise((resolve) => { releaseFetch = resolve; });
  };

  const delayedOwner = checkForUpdate(options(testFixture, fetchImpl));
  await pause.reached;
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(
    path.join(operationPath(testFixture, 'pending', 1n), 'owner.json'),
    staleTime,
    staleTime,
  );
  const successor = checkForUpdate(options(testFixture, fetchImpl));
  await fetchStarted;
  assert.equal(requests, 1);

  pause.release();
  assert.deepEqual(await delayedOwner, { status: 'silent', reason: 'check-in-progress' });
  pause.restore();
  assert.equal(requests, 1);

  releaseFetch(response(remoteRelease()));
  assert.equal((await successor).status, 'update_available');
  assert.equal(requests, 1);
});

test('an overlapping check reads the last-good candidate while another process refreshes it', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let requests = 0;
  let releaseRefresh;
  let markRefreshStarted;
  const refreshStarted = new Promise((resolve) => { markRefreshStarted = resolve; });
  const fetchImpl = async () => {
    requests += 1;
    if (requests === 1) return response(remoteRelease());
    markRefreshStarted();
    return new Promise((resolve) => { releaseRefresh = resolve; });
  };

  const first = await checkForUpdate(options(testFixture, fetchImpl));
  const refreshOptions = options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  });
  const refresh = checkForUpdate(refreshOptions);
  await refreshStarted;

  const overlapping = await checkForUpdate(refreshOptions);
  assert.equal(overlapping.status, 'update_available');
  assert.equal(overlapping.eventKey, first.eventKey);
  assert.equal(requests, 2);

  releaseRefresh(response(remoteRelease()));
  assert.equal((await refresh).status, 'update_available');
});

test('acknowledgement waits briefly for an in-flight refresh instead of losing the notice', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const offered = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease()),
  ));
  let releaseRefresh;
  let markRefreshStarted;
  const refreshStarted = new Promise((resolve) => { markRefreshStarted = resolve; });
  const refresh = checkForUpdate(options(testFixture, async () => {
    markRefreshStarted();
    return new Promise((resolve) => { releaseRefresh = resolve; });
  }, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }));
  await refreshStarted;

  const acknowledgement = acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: offered.eventKey,
    now: () => baseTime + (73 * 60 * 60 * 1_000) + 1,
  });
  await new Promise((resolve) => setTimeout(resolve, 25));
  releaseRefresh(response(remoteRelease()));
  await refresh;

  assert.deepEqual(await acknowledgement, {
    status: 'acknowledged',
    eventKey: offered.eventKey,
  });
  assert.deepEqual(await checkForUpdate(options(testFixture, async () => response(remoteRelease()), {
    now: () => baseTime + (74 * 60 * 60 * 1_000),
  })), { status: 'silent', reason: 'already-notified' });
});

test('a last-good notice remains acknowledgeable after the refresh commits a new candidate', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const firstRelease = remoteReleaseForVersion('2.16.0', 'b'.repeat(64));
  const secondRelease = remoteReleaseForVersion('2.17.0', 'c'.repeat(64));
  const offered = await checkForUpdate(options(
    testFixture,
    async () => response(firstRelease),
  ));
  let releaseRefresh;
  let markRefreshStarted;
  const refreshStarted = new Promise((resolve) => { markRefreshStarted = resolve; });
  const refresh = checkForUpdate(options(testFixture, async () => {
    markRefreshStarted();
    return new Promise((resolve) => { releaseRefresh = resolve; });
  }, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }));
  await refreshStarted;

  const visibleDuringRefresh = await checkForUpdate(options(testFixture, async () => {
    assert.fail('an overlapping reader must not start another network request');
  }, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }));
  assert.equal(visibleDuringRefresh.eventKey, offered.eventKey);
  const acknowledgement = acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: visibleDuringRefresh.eventKey,
  });
  await new Promise((resolve) => setTimeout(resolve, 25));
  releaseRefresh(response(secondRelease));
  assert.equal((await refresh).latestVersion, '2.17.0');
  assert.deepEqual(await acknowledgement, {
    status: 'acknowledged',
    eventKey: offered.eventKey,
  });

  assert.deepEqual(await checkForUpdate(options(testFixture, async () => response(firstRelease), {
    now: () => baseTime + (146 * 60 * 60 * 1_000),
  })), { status: 'silent', reason: 'already-notified' });
});

test('acknowledgement retry budget uses a monotonic clock', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const offered = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease()),
  ));
  writePendingOperation(testFixture, { generation: 2n });
  const ticks = [100, 1_301];
  const started = Date.now();

  assert.deepEqual(await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: offered.eventKey,
    monotonicNow: () => ticks.shift() ?? 1_301,
  }), { status: 'silent', reason: 'check-in-progress' });
  assert.ok(Date.now() - started < 200, 'an exhausted monotonic budget should not sleep on wall time');
});

test('a delayed allocator cannot reuse a generation after its reservation name was taken', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const firstRelease = remoteReleaseForVersion('2.16.0', 'b'.repeat(64));
  const secondRelease = remoteReleaseForVersion('2.17.0', 'c'.repeat(64));
  const offered = await checkForUpdate(options(
    testFixture,
    async () => response(firstRelease),
  ));
  const pause = pauseMkdirOnce(operationPath(testFixture, 'reserved', 2n));
  t.after(() => pause.restore());

  const acknowledgement = acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: offered.eventKey,
  });
  await pause.reached;
  assert.equal((await checkForUpdate(options(testFixture, async () => response(secondRelease), {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }))).latestVersion, '2.17.0');

  pause.release();
  assert.deepEqual(await acknowledgement, { status: 'acknowledged', eventKey: offered.eventKey });
  pause.restore();
  assert.equal(fs.existsSync(operationPath(testFixture, 'reserved', 2n)), true);
  assert.equal(fs.existsSync(operationPath(testFixture, 'reserved', 3n)), true);
  assert.deepEqual(await checkForUpdate(options(testFixture, async () => response(firstRelease), {
    now: () => baseTime + (146 * 60 * 60 * 1_000),
  })), { status: 'silent', reason: 'already-notified' });
});

test('an oversized generation name cannot poison future allocation', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  fs.mkdirSync(stateDirectory(testFixture), { recursive: true });
  fs.mkdirSync(path.join(stateDirectory(testFixture), `reserved-${'9'.repeat(246)}`));
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
  assert.equal(requests, 1);
  assert.ok(fs.existsSync(operationPath(testFixture, 'reserved', 1n)));
});

test('a lower generation stalled after reservation cannot commit behind a newer generation', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const firstRelease = remoteReleaseForVersion('2.16.0', 'b'.repeat(64));
  const secondRelease = remoteReleaseForVersion('2.17.0', 'c'.repeat(64));
  const offered = await checkForUpdate(options(
    testFixture,
    async () => response(firstRelease),
  ));
  const pause = pauseMkdirOnce(operationPath(testFixture, 'pending', 2n));
  t.after(() => pause.restore());

  const acknowledgement = acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: offered.eventKey,
  });
  await pause.reached;
  assert.equal(fs.existsSync(operationPath(testFixture, 'reserved', 2n)), true);
  assert.equal((await checkForUpdate(options(testFixture, async () => response(secondRelease), {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }))).latestVersion, '2.17.0');

  pause.release();
  assert.deepEqual(await acknowledgement, { status: 'acknowledged', eventKey: offered.eventKey });
  pause.restore();
  assert.equal(fs.existsSync(operationPath(testFixture, 'committed', 2n)), false);
  assert.equal(fs.existsSync(operationPath(testFixture, 'cancelled', 2n)), true);
  assert.equal(fs.existsSync(operationPath(testFixture, 'reserved', 3n)), true);
  assert.equal(fs.existsSync(operationPath(testFixture, 'reserved', 4n)), true);
  assert.deepEqual(await checkForUpdate(options(testFixture, async () => response(firstRelease), {
    now: () => baseTime + (146 * 60 * 60 * 1_000),
  })), { status: 'silent', reason: 'already-notified' });
});

test('acknowledgement fences an expired refresh and the resumed owner cannot erase it', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const offered = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease()),
  ));

  let resumeRefresh;
  let markRefreshStarted;
  const refreshStarted = new Promise((resolve) => { markRefreshStarted = resolve; });
  const refresh = checkForUpdate(options(testFixture, async () => {
    markRefreshStarted();
    return new Promise((resolve) => { resumeRefresh = resolve; });
  }, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
    timeoutMs: 2_000,
  }));
  await refreshStarted;

  const refreshPending = operationPath(testFixture, 'pending', 2n);
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(path.join(refreshPending, 'owner.json'), staleTime, staleTime);
  assert.deepEqual(await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: offered.eventKey,
    now: () => baseTime + (73 * 60 * 60 * 1_000) + 1,
  }), { status: 'acknowledged', eventKey: offered.eventKey });

  resumeRefresh(response(remoteRelease()));
  assert.deepEqual(await refresh, { status: 'silent', reason: 'check-in-progress' });
  assert.deepEqual(await checkForUpdate(options(testFixture, async () => response(remoteRelease()), {
    now: () => baseTime + (71 * 60 * 60 * 1_000),
  })), { status: 'silent', reason: 'already-notified' });
});

for (const pausePoint of ['pending owner creation', 'state temp creation']) {
  test(`acknowledgement retries when fenced during ${pausePoint}`, async (t) => {
    const testFixture = fixture();
    t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
    const offered = await checkForUpdate(options(
      testFixture,
      async () => response(remoteRelease()),
    ));
    const pending = operationPath(testFixture, 'pending', 2n);
    const pause = pauseOpenOnce((target) => {
      if (pausePoint === 'pending owner creation') {
        return path.resolve(target) === path.join(pending, 'owner.json');
      }
      return path.dirname(path.resolve(target)) === pending
        && /^\.state\..+\.tmp$/.test(path.basename(target));
    });
    t.after(() => pause.restore());

    const acknowledgement = acknowledgeUpdate({
      releasePath: testFixture.releasePath,
      cacheDirectory: testFixture.cacheDirectory,
      eventKey: offered.eventKey,
    });
    await pause.reached;
    const staleTime = new Date(Date.now() - 60_000);
    fs.utimesSync(
      pausePoint === 'pending owner creation' ? pending : path.join(pending, 'owner.json'),
      staleTime,
      staleTime,
    );

    assert.equal((await checkForUpdate(options(
      testFixture,
      async () => response(remoteRelease()),
      { now: () => baseTime + (73 * 60 * 60 * 1_000) },
    ))).status, 'update_available');
    assert.equal(fs.existsSync(operationPath(testFixture, 'fenced', 2n)), true);

    pause.release();
    assert.deepEqual(await acknowledgement, {
      status: 'acknowledged',
      eventKey: offered.eventKey,
    });
    pause.restore();
  });
}

test('a stale corrupt operation is fenced without exposing a failure', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const staleTime = new Date(Date.now() - 60_000);
  const pending = writePendingOperation(testFixture, {
    owner: 'corrupt-owner',
    modifiedAt: staleTime,
  });

  const result = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease()),
  ));

  assert.equal(result.status, 'update_available');
  assert.equal(fs.existsSync(pending), false);
  assert.equal(fs.existsSync(operationPath(testFixture, 'fenced', 1n)), true);
});

test('malformed active-claim shapes recover after the hard lease', async () => {
  for (const shape of ['file', 'symlink', 'owner-directory']) {
    const testFixture = fixture();
    try {
      const directory = stateDirectory(testFixture);
      const activeClaim = path.join(directory, 'active-claim');
      fs.mkdirSync(directory, { recursive: true });
      if (shape === 'file') {
        fs.writeFileSync(activeClaim, 'corrupt claim');
      } else if (shape === 'symlink') {
        fs.symlinkSync(path.join(testFixture.root, 'missing-claim-target'), activeClaim);
      } else {
        fs.mkdirSync(path.join(activeClaim, 'owner.json'), { recursive: true });
      }
      let requests = 0;
      const fetchImpl = async () => {
        requests += 1;
        return response(remoteRelease());
      };

      assert.deepEqual(await checkForUpdate(options(testFixture, fetchImpl)), {
        status: 'silent',
        reason: 'check-in-progress',
      }, `${shape} should retain a fresh hard lease`);
      assert.equal(requests, 0);

      const staleTime = new Date(Date.now() - 60_000);
      if (shape === 'symlink') fs.lutimesSync(activeClaim, staleTime, staleTime);
      else if (shape === 'owner-directory') {
        fs.utimesSync(path.join(activeClaim, 'owner.json'), staleTime, staleTime);
      } else fs.utimesSync(activeClaim, staleTime, staleTime);

      assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
      assert.equal(requests, 1, `${shape} should be retired after its hard lease`);
    } finally {
      fs.rmSync(testFixture.root, { recursive: true, force: true });
    }
  }
});

test('a delayed corrupt-claim retirement cannot move a successor active claim', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const directory = stateDirectory(testFixture);
  const activeClaim = path.join(directory, 'active-claim');
  fs.mkdirSync(activeClaim, { recursive: true });
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(activeClaim, staleTime, staleTime);
  let firstRetirement = null;
  const pause = pauseRenameMatchingOnce((source, destination) => {
    if (path.resolve(source) !== activeClaim
      || !path.basename(path.dirname(destination)).startsWith('retired-claim-corrupt-')) {
      return false;
    }
    firstRetirement = destination;
    return true;
  });
  t.after(() => pause.restore());

  const delayed = checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease()),
  ));
  await pause.reached;
  fs.utimesSync(activeClaim, staleTime, staleTime);
  fs.utimesSync(
    path.join(operationPath(testFixture, 'pending', 1n), 'owner.json'),
    staleTime,
    staleTime,
  );

  assert.equal((await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease()),
  ))).status, 'update_available');
  assert.equal(
    JSON.parse(fs.readFileSync(path.join(activeClaim, 'owner.json'), 'utf8')).generation,
    '2',
  );

  pause.release();
  assert.equal((await delayed).status, 'update_available');
  pause.restore();
  assert.equal(firstRetirement !== null, true);
  assert.equal(fs.existsSync(activeClaim), true);
  assert.equal(
    JSON.parse(fs.readFileSync(path.join(activeClaim, 'owner.json'), 'utf8')).generation,
    '2',
  );
});

test('repeated corrupt claim contents receive distinct retirement identities', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const directory = stateDirectory(testFixture);
  const activeClaim = path.join(directory, 'active-claim');
  fs.mkdirSync(directory, { recursive: true });
  fs.writeFileSync(activeClaim, 'same corrupt claim');
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(activeClaim, staleTime, staleTime);
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
  fs.renameSync(activeClaim, path.join(directory, 'test-completed-active-claim'));
  fs.writeFileSync(activeClaim, 'same corrupt claim');
  fs.utimesSync(activeClaim, staleTime, staleTime);
  assert.equal((await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }))).status, 'update_available');

  assert.equal(requests, 2);
  assert.equal(
    fs.readdirSync(directory).filter((name) => name.startsWith('retired-claim-corrupt-')).length,
    2,
  );
});

test('corrupt claim retirement preserves distinct 64-bit inode identities', async (t) => {
  const testFixture = fixture();
  const originalLstat = fsPromises.lstat;
  t.after(() => {
    fsPromises.lstat = originalLstat;
    fs.rmSync(testFixture.root, { recursive: true, force: true });
  });
  const directory = stateDirectory(testFixture);
  const activeClaim = path.join(directory, 'active-claim');
  fs.mkdirSync(directory, { recursive: true });
  fs.writeFileSync(activeClaim, 'corrupt claim');
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(activeClaim, staleTime, staleTime);
  let instance = 1n;
  fsPromises.lstat = async (target, ...args) => {
    const metadata = await originalLstat(target, ...args);
    const resolved = path.resolve(target);
    const isCorruptClaimInstance = resolved === path.resolve(activeClaim)
      || (path.basename(resolved) === 'active'
        && path.basename(path.dirname(resolved)).startsWith('retired-claim-corrupt-'));
    if (!isCorruptClaimInstance || !metadata.isFile()) return metadata;
    const inode = 9_007_199_254_740_992n + (instance - 1n);
    return args[0]?.bigint
      ? metadataWithOverrides(metadata, {
        ino: inode,
        birthtimeNs: 1_000n,
        ctimeNs: 2_000n,
      })
      : metadataWithOverrides(metadata, {
        ino: Number(inode),
        birthtimeMs: 1,
        ctimeMs: 2,
      });
  };
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');
  fs.renameSync(activeClaim, path.join(directory, 'test-completed-active-claim'));
  fs.writeFileSync(activeClaim, 'corrupt claim');
  fs.utimesSync(activeClaim, staleTime, staleTime);
  instance = 2n;
  assert.equal((await checkForUpdate(options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  }))).status, 'update_available');

  assert.equal(requests, 2);
  assert.equal(
    fs.readdirSync(directory).filter((name) => name.startsWith('retired-claim-corrupt-')).length,
    2,
  );
});

test('a forged retirement symlink cannot move an active claim outside its cache partition', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };
  assert.equal((await checkForUpdate(options(testFixture, fetchImpl))).status, 'update_available');

  const outside = path.join(testFixture.root, 'outside');
  fs.mkdirSync(outside);
  fs.symlinkSync(outside, path.join(stateDirectory(testFixture), 'retired-claim-1'));
  const refreshOptions = options(testFixture, fetchImpl, {
    now: () => baseTime + (73 * 60 * 60 * 1_000),
  });
  assert.equal((await checkForUpdate(refreshOptions)).status, 'update_available');
  assert.deepEqual(fs.readdirSync(outside), []);
  assert.equal(requests, 1);

  fs.unlinkSync(path.join(stateDirectory(testFixture), 'retired-claim-1'));
  assert.equal((await checkForUpdate(refreshOptions)).status, 'update_available');
  assert.deepEqual(fs.readdirSync(outside), []);
  assert.equal(requests, 2);
});

test('an expired operation is fenced even when its PID was reused by a live process', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const staleTime = new Date(Date.now() - 60_000);
  const pending = writePendingOperation(testFixture, { modifiedAt: staleTime });
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(result.status, 'update_available');
  assert.equal(requests, 1);
  assert.equal(fs.existsSync(pending), false);
  assert.equal(fs.existsSync(operationPath(testFixture, 'fenced', 1n)), true);
});

test('a corrupt future-dated operation cannot suppress checks indefinitely', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const futureTime = new Date(Date.now() + (365 * 24 * 60 * 60 * 1_000));
  const pending = writePendingOperation(testFixture, {
    owner: 'corrupt-owner',
    modifiedAt: futureTime,
  });

  const result = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease()),
  ));

  assert.equal(result.status, 'update_available');
  assert.equal(fs.existsSync(pending), false);
  assert.equal(fs.existsSync(operationPath(testFixture, 'fenced', 1n)), true);
});

test('a newly-created operation with slight clock skew is never fenced as stale', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const nearFuture = new Date(Date.now() + 1_000);
  const pending = writePendingOperation(testFixture, {
    owner: 'writer-has-not-finished-the-record',
    modifiedAt: nearFuture,
  });
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.deepEqual(result, { status: 'silent', reason: 'check-in-progress' });
  assert.equal(requests, 0);
  assert.equal(fs.existsSync(pending), true);
});

test('a fenced stale owner cannot overwrite a newer committed candidate after resuming', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let resumeOldOwner;
  let markOldStarted;
  const oldStarted = new Promise((resolve) => { markOldStarted = resolve; });
  const oldCheck = checkForUpdate(options(testFixture, async () => {
    markOldStarted();
    return new Promise((resolve) => { resumeOldOwner = resolve; });
  }, { timeoutMs: 2_000 }));
  await oldStarted;

  const oldPending = operationPath(testFixture, 'pending', 1n);
  const staleTime = new Date(Date.now() - 60_000);
  fs.utimesSync(path.join(oldPending, 'owner.json'), staleTime, staleTime);

  const newer = await checkForUpdate(options(
    testFixture,
    async () => response(remoteReleaseForVersion('3.0.0', 'c'.repeat(64))),
  ));
  assert.equal(newer.status, 'update_available');
  assert.equal(newer.latestVersion, '3.0.0');

  resumeOldOwner(response(remoteReleaseForVersion('2.16.0', 'b'.repeat(64))));
  assert.deepEqual(await oldCheck, { status: 'silent', reason: 'check-in-progress' });
  const persisted = JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8'));
  assert.equal(persisted.candidate.version, '3.0.0');
  assert.equal(fs.existsSync(operationPath(testFixture, 'fenced', 1n)), true);
});

test('different installed versions share no acknowledgement or cache-reset state', async (t) => {
  const testFixture = fixture('2.15.0');
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const olderReleasePath = path.join(testFixture.root, 'older-skill-release.json');
  writeJson(olderReleasePath, localRelease('2.14.0'));
  let requests = 0;
  const fetchImpl = async () => {
    requests += 1;
    return response(remoteRelease());
  };

  const newerInstall = await checkForUpdate(options(testFixture, fetchImpl));
  assert.equal(newerInstall.status, 'update_available');
  assert.deepEqual(await acknowledgeUpdate({
    releasePath: testFixture.releasePath,
    cacheDirectory: testFixture.cacheDirectory,
    eventKey: newerInstall.eventKey,
    now: () => baseTime + 1_000,
  }), { status: 'acknowledged', eventKey: newerInstall.eventKey });

  const olderInstallOptions = options(testFixture, fetchImpl, { releasePath: olderReleasePath });
  const olderInstall = await checkForUpdate(olderInstallOptions);
  assert.equal(olderInstall.status, 'update_available');
  assert.equal(olderInstall.installedVersion, '2.14.0');

  assert.deepEqual(await checkForUpdate(options(testFixture, fetchImpl)), {
    status: 'silent',
    reason: 'already-notified',
  });
  const olderAgain = await checkForUpdate(olderInstallOptions);
  assert.equal(olderAgain.status, 'update_available');
  assert.equal(requests, 2, 'each installed version should retain its own fresh cache');
  assert.equal(fs.existsSync(statePath(testFixture, '2.15.0')), true);
  assert.equal(fs.existsSync(statePath(testFixture, '2.14.0')), true);
});

test('an identity mismatch is rejected silently and never cached as a candidate', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));

  const result = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease({ skillId: 'different-skill' })),
  ));

  assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' });
  const state = JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8'));
  assert.equal(Object.hasOwn(state, 'candidate'), false);
});

test('release notes must byte-match the exact trusted GitHub URL', async () => {
  const base = 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0';
  for (const releaseNotes of [
    'https://github.com:443/tt-a1i/archify/releases/tag/v2.16.0',
    'https://github.com:444/tt-a1i/archify/releases/tag/v2.16.0',
    `${base}?`,
    `${base}#`,
    `${base}?source=manifest`,
    'https://GITHUB.COM/tt-a1i/archify/releases/tag/v2.16.0',
  ]) {
    const testFixture = fixture();
    try {
      const result = await checkForUpdate(options(
        testFixture,
        async () => response(remoteRelease({ releaseNotes })),
      ));
      assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' }, releaseNotes);
    } finally {
      fs.rmSync(testFixture.root, { recursive: true, force: true });
    }
  }
});

test('malformed release notes are classified as an invalid manifest', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));

  const result = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease({ releaseNotes: 'not-a-url' })),
  ));

  assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' });
});

test('publishedAt must use canonical UTC seconds and a real calendar date', async () => {
  for (const publishedAt of [
    '2026-08-28T15:00:00+08:00',
    '2026-08-28',
    '2026-02-30T00:00:00Z',
  ]) {
    const testFixture = fixture();
    try {
      const result = await checkForUpdate(options(
        testFixture,
        async () => response(remoteRelease({ publishedAt })),
      ));
      assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' }, publishedAt);
    } finally {
      fs.rmSync(testFixture.root, { recursive: true, force: true });
    }
  }
});

test('stable candidates cannot use prerelease or build metadata', async () => {
  for (const version of ['2.16.0-dev.1', '2.16.0+build.1']) {
    const testFixture = fixture();
    try {
      const result = await checkForUpdate(options(
        testFixture,
        async () => response(remoteReleaseForVersion(version)),
      ));
      assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' }, version);
    } finally {
      fs.rmSync(testFixture.root, { recursive: true, force: true });
    }
  }
});

test('an invalid local identity fails before any network disclosure', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  writeJson(testFixture.releasePath, {
    ...localRelease(),
    source: { repository: 'https://example.com/untrusted/archify' },
  });
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.deepEqual(result, { status: 'silent', reason: 'invalid-local-release' });
  assert.equal(requests, 0);
});

test('a bounded timeout fails silently without retrying', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let requests = 0;
  const fetchImpl = (_url, init) => new Promise((_resolve, reject) => {
    requests += 1;
    init.signal.addEventListener('abort', () => {
      const error = new Error('aborted');
      error.name = 'AbortError';
      reject(error);
    }, { once: true });
  });

  const result = await checkForUpdate(options(testFixture, fetchImpl, { timeoutMs: 10 }));
  assert.deepEqual(result, { status: 'silent', reason: 'check-failed' });
  assert.equal(requests, 1);
});

test('non-success responses cancel their unread body before failing silently', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  let cancelled = false;

  const result = await checkForUpdate(options(testFixture, async () => ({
    status: 503,
    headers: new Headers(),
    body: {
      async cancel() {
        cancelled = true;
      },
    },
  })));

  assert.deepEqual(result, { status: 'silent', reason: 'check-failed' });
  assert.equal(cancelled, true);
});

test('media-type and declared-size rejection cancel unread response bodies', async () => {
  for (const headers of [
    { 'content-type': 'text/html' },
    { 'content-type': 'application/json', 'content-length': String((32 * 1024) + 1) },
  ]) {
    const testFixture = fixture();
    let cancelled = false;
    try {
      const result = await checkForUpdate(options(testFixture, async () => ({
        status: 200,
        headers: new Headers(headers),
        body: {
          async cancel() {
            cancelled = true;
          },
        },
      })));
      assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' });
      assert.equal(cancelled, true);
    } finally {
      fs.rmSync(testFixture.root, { recursive: true, force: true });
    }
  }
});

test('response stream failures are classified as network failures', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));

  const result = await checkForUpdate(options(testFixture, async () => ({
    status: 200,
    headers: new Headers({ 'content-type': 'application/json' }),
    body: {
      getReader() {
        return {
          async read() {
            throw new TypeError('socket closed while reading');
          },
          releaseLock() {},
        };
      },
    },
  })));

  assert.deepEqual(result, { status: 'silent', reason: 'check-failed' });
});

test('invalid UTF-8 is classified as an invalid manifest rather than a network failure', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const result = await checkForUpdate(options(
    testFixture,
    async () => new Response(new Uint8Array([0xff]), {
      status: 200,
      headers: { 'content-type': 'application/json' },
    }),
  ));
  assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' });
});

test('chunked responses above 32 KiB are rejected instead of fully trusted', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  const oversized = `${JSON.stringify(remoteRelease())}${' '.repeat(33 * 1024)}`;

  const result = await checkForUpdate(options(
    testFixture,
    async () => response(oversized),
  ));

  assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' });
});

test('a poisoned far-future cache timestamp cannot suppress checks indefinitely', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  await checkForUpdate(options(testFixture, async () => response(remoteRelease())));
  const cacheStatePath = statePath(testFixture);
  const state = JSON.parse(fs.readFileSync(cacheStatePath, 'utf8'));
  state.check.nextCheckAt = '9999-12-31T23:59:59.000Z';
  writeJson(cacheStatePath, state);
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteRelease());
  }));

  assert.equal(result.status, 'update_available');
  assert.equal(requests, 1);
});

test('a semantically corrupt cached candidate discards legacy validators and rebuilds unconditionally', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  await checkForUpdate(options(testFixture, async () => response(remoteRelease())));
  const cacheStatePath = statePath(testFixture);
  const state = JSON.parse(fs.readFileSync(cacheStatePath, 'utf8'));
  state.candidate.targetDigest = 'sha256:corrupt';
  state.check.etag = '"legacy-tracker"';
  state.check.nextCheckAt = '9999-12-31T23:59:59.000Z';
  writeJson(cacheStatePath, state);
  let conditionalHeader = 'not-observed';

  const result = await checkForUpdate(options(testFixture, async (_url, init) => {
    conditionalHeader = init.headers['if-none-match'];
    return response(remoteRelease());
  }));

  assert.equal(result.status, 'update_available');
  assert.equal(conditionalHeader, undefined);
  assert.equal(
    JSON.parse(fs.readFileSync(statePath(testFixture), 'utf8')).candidate.targetDigest,
    result.targetDigest,
  );
});

test('a newer cached candidate without offered or acknowledged provenance is rebuilt', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  writeJson(statePath(testFixture), {
    schemaVersion: 1,
    skillId: 'archify',
    installedVersion: '2.15.0',
    check: {
      nextCheckAt: new Date(baseTime + (24 * 60 * 60 * 1_000)).toISOString(),
      consecutiveFailures: 0,
    },
    notification: {
      offeredDigests: [],
      acknowledgedDigests: [],
    },
    candidate: {
      version: '2.16.0',
      targetDigest: `sha256:${'b'.repeat(64)}`,
      severity: 'normal',
      releaseNotes: 'https://github.com/tt-a1i/archify/releases/tag/v2.16.0',
    },
  });
  let requests = 0;

  const result = await checkForUpdate(options(testFixture, async () => {
    requests += 1;
    return response(remoteReleaseForVersion('2.15.0', 'd'.repeat(64)));
  }));

  assert.deepEqual(result, { status: 'silent', reason: 'current' });
  assert.equal(requests, 1, 'semantic corruption must not produce an unacknowledgeable cached notice');
});

test('corrupt cache is rebuilt without exposing an error to the user', async (t) => {
  const testFixture = fixture();
  t.after(() => fs.rmSync(testFixture.root, { recursive: true, force: true }));
  fs.mkdirSync(stateDirectory(testFixture), { recursive: true });
  fs.mkdirSync(path.dirname(statePath(testFixture)), { recursive: true });
  fs.writeFileSync(statePath(testFixture), '{not-json');

  const result = await checkForUpdate(options(
    testFixture,
    async () => response(remoteRelease({ version: '2.15.0' })),
  ));

  assert.deepEqual(result, { status: 'silent', reason: 'invalid-manifest' });
  assert.doesNotThrow(() => JSON.parse(
    fs.readFileSync(statePath(testFixture), 'utf8'),
  ));
});

test('disabled CLI returns one silent JSON line and never needs the network', () => {
  const result = spawnSync(process.execPath, [checkerPath], {
    cwd: skillRoot,
    encoding: 'utf8',
    env: { ...process.env, ARCHIFY_UPDATE_CHECK_DISABLED: '1' },
  });

  assert.equal(result.status, 0, result.stderr);
  assert.deepEqual(JSON.parse(result.stdout), { status: 'silent', reason: 'disabled' });
  assert.equal(result.stdout.trim().split('\n').length, 1);
});

test('CLI acknowledgement emits the documented one-line success schema', async (t) => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-update-cli-ack-'));
  t.after(() => fs.rmSync(root, { recursive: true, force: true }));
  const home = path.join(root, 'home');
  const xdg = path.join(root, 'xdg-cache');
  const localData = path.join(root, 'local-data');
  for (const directory of [home, xdg, localData]) fs.mkdirSync(directory, { recursive: true });
  const cacheDirectory = process.platform === 'win32'
    ? path.join(localData, 'archify-skill')
    : process.platform === 'darwin'
      ? path.join(home, 'Library', 'Caches', 'archify-skill')
      : path.join(xdg, 'archify-skill');
  const releasePath = path.join(skillRoot, 'skill-release.json');
  const installedRelease = JSON.parse(fs.readFileSync(releasePath, 'utf8'));
  const [major, minor, patch] = parseSemver(installedRelease.version).core;
  const candidateVersion = `${major}.${minor}.${BigInt(patch) + 1n}`;
  const offered = await checkForUpdate({
    releasePath,
    cacheDirectory,
    fetchImpl: async () => response(remoteReleaseForVersion(candidateVersion)),
    now: () => baseTime,
    random: () => 0.5,
    timeoutMs: 50,
  });
  assert.equal(offered.status, 'update_available');

  const acknowledgement = spawnSync(process.execPath, [checkerPath, '--ack', offered.eventKey], {
    cwd: skillRoot,
    encoding: 'utf8',
    env: {
      ...process.env,
      HOME: home,
      USERPROFILE: home,
      XDG_CACHE_HOME: xdg,
      LOCALAPPDATA: localData,
    },
  });
  assert.equal(acknowledgement.status, 0, acknowledgement.stderr);
  assert.deepEqual(JSON.parse(acknowledgement.stdout), {
    status: 'acknowledged',
    eventKey: offered.eventKey,
  });
  assert.equal(acknowledgement.stdout.trim().split('\n').length, 1);
});

test('CLI entry detection survives a realpath or symlink alias', (t) => {
  const aliasRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-update-cli-alias-'));
  t.after(() => fs.rmSync(aliasRoot, { recursive: true, force: true }));
  const aliasPath = path.join(aliasRoot, 'check-update-alias.mjs');
  try {
    fs.symlinkSync(checkerPath, aliasPath);
  } catch (error) {
    if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error?.code)) {
      t.skip(`symlinks unavailable: ${error.code}`);
      return;
    }
    throw error;
  }

  const result = spawnSync(process.execPath, [aliasPath], {
    cwd: aliasRoot,
    encoding: 'utf8',
    env: { ...process.env, ARCHIFY_UPDATE_CHECK_DISABLED: '1' },
  });
  assert.equal(result.status, 0, result.stderr);
  assert.deepEqual(JSON.parse(result.stdout), { status: 'silent', reason: 'disabled' });
});

test('notifier source has no process execution or remote-origin override surface', () => {
  const checkerSource = fs.readFileSync(checkerPath, 'utf8');
  const contractSource = fs.readFileSync(contractPath, 'utf8');
  const combinedSource = `${checkerSource}\n${contractSource}`;
  assert.doesNotMatch(combinedSource, /(?:node:)?child_process/);
  assert.doesNotMatch(combinedSource, /(?:^|[^\w.])(?:spawn|exec|execFile|fork)(?:Sync)?\s*\(/m);
  assert.doesNotMatch(contractSource, /\bfetch\s*\(/);
  const environmentReads = [...combinedSource.matchAll(/process\.env\.([A-Z0-9_]+)/g)]
    .map((match) => match[1])
    .sort();
  assert.deepEqual(environmentReads, [
    'ARCHIFY_UPDATE_CHECK_DISABLED',
    'LOCALAPPDATA',
    'XDG_CACHE_HOME',
  ]);
  assert.doesNotMatch(combinedSource, /updateCommand/);
});
```

## test/v1-compatibility.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-v1-compat-'));

function render(mode, doc) {
  const input = path.join(tmp, `${mode}.json`);
  const output = path.join(tmp, `${mode}.html`);
  fs.writeFileSync(input, JSON.stringify(doc));
  try {
    execFileSync('node', [
      path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      input,
      output,
    ], { stdio: ['ignore', 'ignore', 'pipe'] });
    return { code: 0, stderr: '', output };
  } catch (error) {
    return { code: error.status ?? 1, stderr: String(error.stderr || ''), output };
  }
}

function validate(mode, doc) {
  const input = path.join(tmp, `${mode}-validate.json`);
  fs.writeFileSync(input, JSON.stringify(doc));
  try {
    execFileSync('node', [
      path.join(skillRoot, 'bin/archify.mjs'),
      'validate',
      mode,
      input,
      '--json',
    ], { stdio: ['ignore', 'ignore', 'pipe'] });
    return { code: 0, stderr: '' };
  } catch (error) {
    return { code: error.status ?? 1, stderr: String(error.stderr || error.stdout || '') };
  }
}

function check(output) {
  const stdout = execFileSync('node', [
    path.join(skillRoot, 'scripts/check-render-output.mjs'),
    output,
  ], { encoding: 'utf8' });
  return JSON.parse(stdout);
}

function legacyDataflowDocument() {
  return {
    schema_version: 1,
    diagram_type: 'dataflow',
    meta: { title: 'Legacy data flow', viewBox: [1080, 760] },
    stages: [{ label: 'Sources' }, { label: 'Ingest' }],
    nodes: [
      { id: 'web', type: 'frontend', label: 'Web App', stage: 0, row: 0 },
      { id: 'edge', type: 'cloud', label: 'Edge API', stage: 1, row: 1 },
    ],
    flows: [
      {
        from: 'web',
        to: 'edge',
        label: 'clickstream',
        fromSide: 'right',
        toSide: 'left',
        via: [[184, 157], [184, 271]],
        labelAt: [204, 190],
      },
    ],
  };
}

const OFFICIAL_V1_EXAMPLES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

for (const [mode, filename] of Object.entries(OFFICIAL_V1_EXAMPLES)) {
  test(`official v1 ${mode} baseline remains renderable and valid`, () => {
    const doc = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures/v1-baseline', filename), 'utf8'));
    assert.equal(doc.schema_version, 1);
    assert.equal(doc.meta.quality_profile, undefined);
    const rendered = render(mode, doc);
    assert.equal(rendered.code, 0, rendered.stderr);
    assert.ok(fs.statSync(rendered.output).size > 0);
    const validated = validate(mode, doc);
    assert.equal(validated.code, 0, validated.stderr);
  });
}

test('quality-profile lifecycle keeps the checked-in authored via authoritative', () => {
  const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-run.lifecycle.json'), 'utf8'));
  const transition = doc.transitions.find(({ id }) => id === 'approval-cancelled');
  assert.deepEqual(transition.via, [[480, 336], [480, 432], [402, 432]]);

  const rendered = render('lifecycle', doc);
  assert.equal(rendered.code, 0, rendered.stderr);
  const html = fs.readFileSync(rendered.output, 'utf8');
  assert.match(
    html,
    /data-edge-id="approval-cancelled"[^>]*data-composition-points="[^"]*480,336;480,432;402,432[^"]*"/,
  );
  const validated = validate('lifecycle', doc);
  assert.equal(validated.code, 0, validated.stderr);
});

test('legacy v1 architecture auto viewBox accommodates all seven implicit auto legend kinds', () => {
  const types = ['frontend', 'backend', 'database', 'cloud', 'security', 'messagebus', 'external'];
  const doc = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Narrow legacy architecture' },
    components: types.map((type, index) => ({
      id: `component_${index}`,
      type,
      label: type,
      pos: [0, 40 + index * 76],
      size: [120, 52],
    })),
    connections: [],
  };

  const rendered = render('architecture', doc);
  assert.equal(rendered.code, 0, rendered.stderr);
  const html = fs.readFileSync(rendered.output, 'utf8');
  const svg = html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
  const viewBox = svg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/)?.slice(1).map(Number);
  const baselines = [...svg.matchAll(/data-legend-baseline="([\d.]+)"/g)].map((match) => Number(match[1]));
  assert.deepEqual([...svg.matchAll(/data-legend-semantic-kind="([^"]+)"/g)].map((match) => match[1]), types);
  assert.ok(viewBox && baselines.length === types.length);
  assert.ok(viewBox[0] > 160, 'auto viewBox should widen for its widest measured legend entry');
  assert.ok(Math.max(...baselines) < viewBox[1]);
  assert.ok(Math.min(...baselines) > 40 + (types.length - 1) * 76 + 52);

  const validated = validate('architecture', doc);
  assert.equal(validated.code, 0, validated.stderr);
});

function narrowExplicitViewBoxDocuments() {
  const componentTypes = ['frontend', 'backend', 'database', 'cloud', 'security', 'messagebus', 'external'];
  return {
    architecture: {
      schema_version: 1,
      diagram_type: 'architecture',
      meta: { title: 'Legacy narrow architecture', viewBox: [320, 800] },
      components: componentTypes.map((type, index) => ({
        id: `component_${index}`,
        type,
        label: type,
        pos: [40, 90 + index * 90],
        size: [120, 52],
      })),
      connections: [],
    },
    workflow: {
      schema_version: 1,
      diagram_type: 'workflow',
      meta: { title: 'Legacy narrow workflow', viewBox: [700, 400] },
      lanes: [{ id: 'first', label: 'First' }, { id: 'second', label: 'Second' }],
      nodes: componentTypes.map((type, index) => ({
        id: `node_${index}`,
        lane: index < 3 ? 'first' : 'second',
        col: index < 3 ? index * 2 : [0, 2, 4, 5][index - 3],
        type,
        label: type,
      })),
      edges: [],
    },
    sequence: {
      schema_version: 1,
      diagram_type: 'sequence',
      meta: { title: 'Legacy narrow sequence', viewBox: [480, 480] },
      participants: [
        { id: 'left', type: 'frontend', label: 'Left' },
        { id: 'right', type: 'backend', label: 'Right' },
      ],
      messages: ['emphasis', 'return', 'security', 'dashed', 'default'].map((variant, index) => ({
        from: index % 2 ? 'right' : 'left',
        to: index % 2 ? 'left' : 'right',
        y: 170 + index * 40,
        label: variant,
        variant,
      })),
    },
    dataflow: {
      schema_version: 1,
      diagram_type: 'dataflow',
      meta: { title: 'Legacy narrow dataflow', viewBox: [423, 720] },
      stages: [{ label: 'Input' }, { label: 'Output' }],
      nodes: [
        { id: 'in_0', type: 'backend', label: 'In 0', stage: 0, row: 0 },
        { id: 'out_0', type: 'database', label: 'Out 0', stage: 1, row: 0 },
        { id: 'in_1', type: 'backend', label: 'In 1', stage: 0, row: 1 },
        { id: 'out_1', type: 'backend', label: 'Out 1', stage: 1, row: 1 },
        { id: 'in_2', type: 'backend', label: 'In 2', stage: 0, row: 2 },
        { id: 'out_2', type: 'backend', label: 'Out 2', stage: 1, row: 2 },
        { id: 'in_3', type: 'backend', label: 'In 3', stage: 0, row: 3 },
        { id: 'out_3', type: 'backend', label: 'Out 3', stage: 1, row: 3 },
      ],
      flows: ['emphasis', 'security', 'dashed', 'default'].map((variant, index) => ({
        from: `in_${index}`,
        to: `out_${index}`,
        label: variant,
        variant,
        route: 'straight',
      })),
    },
    lifecycle: {
      schema_version: 1,
      diagram_type: 'lifecycle',
      meta: { title: 'Legacy narrow lifecycle', viewBox: [420, 800] },
      lanes: [{ id: 'main', label: 'Lifecycle' }],
      states: ['start', 'active', 'waiting', 'decision', 'success', 'failure', 'neutral', 'external'].map((type, index) => ({
        id: `state_${index}`,
        type,
        label: type,
        lane: 'main',
        col: index % 2,
        yOffset: Math.floor(index / 2) * 72,
      })),
      transitions: [],
    },
  };
}

test('legacy v1 explicit narrow viewBoxes never hard-fail on an implicit auto legend', () => {
  const expectedNodeCounts = { architecture: 7, workflow: 7, sequence: 2, dataflow: 8, lifecycle: 8 };
  for (const [mode, doc] of Object.entries(narrowExplicitViewBoxDocuments())) {
    assert.equal(doc.meta.legend, undefined);
    const rendered = render(mode, doc);
    assert.equal(rendered.code, 0, `${mode}: ${rendered.stderr}`);
    const svg = fs.readFileSync(rendered.output, 'utf8').match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
    assert.equal((svg.match(/data-node-id=/g) || []).length, expectedNodeCounts[mode], `${mode}: topology must remain intact`);
    if (mode === 'lifecycle') {
      assert.match(svg, />Legend</, 'a fitting implicit legend should remain visible');
      assert.equal((svg.match(/data-legend-semantic-kind=/g) || []).length, 8);
    } else {
      assert.doesNotMatch(svg, />Legend</, `${mode}: an unfit implicit legend should degrade without overlap`);
    }
    const validated = validate(mode, doc);
    assert.equal(validated.code, 0, `${mode}: ${validated.stderr}`);
  }
});

test('legacy v1 architecture geometry remains renderable without an explicit quality profile', () => {
  const doc = {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: { title: 'Legacy architecture' },
    components: [
      { id: 'auth', type: 'security', label: 'Auth Provider', pos: [40, 110], size: [120, 64] },
      { id: 'lb', type: 'cloud', label: 'Load Balancer', pos: [460, 300], size: [130, 60] },
      { id: 'api', type: 'backend', label: 'API Server', pos: [670, 300], size: [130, 60] },
    ],
    connections: [
      {
        from: 'auth',
        to: 'api',
        label: 'verify JWT',
        fromSide: 'right',
        toSide: 'left',
        via: [[620, 142], [620, 330]],
      },
    ],
  };

  const result = render('architecture', doc);
  assert.equal(result.code, 0, result.stderr);
  assert.ok(fs.statSync(result.output).size > 0);
});

test('legacy v1 data-flow geometry remains renderable without an explicit quality profile', () => {
  const result = render('dataflow', legacyDataflowDocument());
  assert.equal(result.code, 0, result.stderr);
  assert.ok(fs.statSync(result.output).size > 0);
});

test('legacy v1 data-flow artifact remains valid without an explicit quality profile', () => {
  const result = validate('dataflow', legacyDataflowDocument());
  assert.equal(result.code, 0, result.stderr);
});

test('legacy v1 composition findings remain visible as advisory warnings', () => {
  const rendered = render('dataflow', legacyDataflowDocument());
  assert.equal(rendered.code, 0, rendered.stderr);
  const receipt = check(rendered.output);
  assert.equal(receipt.ok, true);
  assert.equal(receipt.composition.metrics.containerBorderRuns, 1);
  assert.equal(receipt.composition.summary.errors, 0);
  assert.equal(receipt.composition.issues[0].severity, 'warning');
});

test('legacy v1 lifecycle geometry remains renderable without an explicit quality profile', () => {
  const doc = {
    schema_version: 1,
    diagram_type: 'lifecycle',
    meta: { title: 'Legacy lifecycle', viewBox: [980, 660] },
    lanes: [
      { id: 'main', label: 'Lifecycle phases' },
      { id: 'waiting', label: 'Interruptions' },
      { id: 'exceptions', label: 'Recovery loop' },
      { id: 'terminal', label: 'Terminal exits' },
    ],
    states: [
      { id: 'executing', type: 'active', label: 'Executing', lane: 'main', col: 2 },
      { id: 'approval', type: 'waiting', label: 'Needs Approval', lane: 'waiting', col: 0 },
      { id: 'failed', type: 'failure', label: 'Failed', lane: 'exceptions', col: 0, yOffset: 78 },
      { id: 'cancelled', type: 'failure', label: 'Cancelled', lane: 'terminal', col: 0 },
    ],
    transitions: [
      { from: 'executing', to: 'failed', fromSide: 'left', toSide: 'top', via: [[320, 157], [320, 342], [402, 342]] },
      { from: 'approval', to: 'cancelled', fromSide: 'bottom', toSide: 'top', via: [[320, 336], [320, 430], [402, 430]] },
    ],
  };

  const result = render('lifecycle', doc);
  assert.equal(result.code, 0, result.stderr);
  assert.ok(fs.statSync(result.output).size > 0);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/vertical-edge.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const bin = path.join(skillRoot, 'bin', 'archify.mjs');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-vertical-edge-'));

const REPRO = {
  schema_version: 1,
  diagram_type: 'dataflow',
  meta: { title: 'Vertical edge arrowhead repro', quality_profile: 'showcase' },
  stages: [{ label: 'Parse' }, { label: 'Store' }],
  nodes: [
    { id: 'extract', type: 'backend', label: 'Extract', stage: 0, row: 0 },
    { id: 'parse', type: 'backend', label: 'Parse', stage: 0, row: 3 },
    { id: 'store', type: 'database', label: 'Store', stage: 1, row: 1 },
  ],
  flows: [
    { id: 'vertical-edge', from: 'extract', to: 'parse', label: 'chunks', variant: 'emphasis', labelDy: 40 },
    { id: 'horizontal-edge', from: 'parse', to: 'store', label: 'rows', variant: 'emphasis' },
  ],
};

function render(d) {
  const inPath = path.join(tmp, 'in.dataflow.json');
  const outPath = path.join(tmp, 'out.html');
  fs.writeFileSync(inPath, JSON.stringify(d));
  execFileSync('node', [bin, 'render', 'dataflow', inPath, outPath, '--quality', 'showcase'], { encoding: 'utf8' });
  return fs.readFileSync(outPath, 'utf8');
}

function edgePath(html, edgeId) {
  const re = new RegExp(`data-edge-id="${edgeId}"[^>]*\\sd="([^"]*)"`);
  const m = html.match(re);
  return m ? m[1] : null;
}

function segments(d) {
  const pts = [...d.matchAll(/[ML] ([-\d.]+ [-\d.]+)/g)].map((m) => m[1]);
  const segs = [];
  for (let i = 1; i < pts.length; i += 1) segs.push(pts[i - 1] !== pts[i]);
  return segs;
}

test('vertical auto-routed dataflow edge has no zero-length final segment (#169)', () => {
  const html = render(REPRO);
  const d = edgePath(html, 'vertical-edge');
  assert.ok(d, 'vertical-edge path should exist');
  const segs = segments(d);
  // The final segment must have real length so marker-end orients correctly.
  assert.ok(segs.at(-1) === true, `last segment must be non-zero length, got d="${d}"`);
  // No segment may be zero-length (degenerate path confuses marker orientation).
  assert.ok(segs.every(Boolean), `no segment may be zero-length, got d="${d}"`);
});

test('horizontal edge still renders with a real final segment (#169)', () => {
  const html = render(REPRO);
  const d = edgePath(html, 'horizontal-edge');
  assert.ok(d, 'horizontal-edge path should exist');
  const segs = segments(d);
  assert.ok(segs.at(-1) === true, `last segment must be non-zero length, got d="${d}"`);
});
```

## test/viewer-camera-browser.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';

const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;
const cases = {
  architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

test('Camera preserves transactions, rendered state and real caller handoffs', {
  skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser camera checks.',
}, async (t) => {
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-camera-'));
  t.after(() => fs.rmSync(scratch, { recursive: true, force: true }));
  const evidence = process.env.ARCHIFY_CAMERA_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const records = [];
  t.after(() => {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(records, null, 2) + '\n');
  });
  const files = {};
  for (const [mode, example] of Object.entries(cases)) {
    files[mode] = path.join(scratch, `${mode}.html`);
    execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
      path.join(skillRoot, 'examples', example), files[mode]]);
  }
  const trace = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', cases.architecture), 'utf8'));
  trace.meta.animation = 'trace';
  fs.writeFileSync(path.join(scratch, 'trace.json'), JSON.stringify(trace));
  files.trace = path.join(scratch, 'trace.html');
  execFileSync(process.execPath, [path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
    path.join(scratch, 'trace.json'), files.trace]);
  const browser = new ChromeVisualBrowser(chrome);
  t.after(() => browser.close());
  const session = await browser.sessionPromise;
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
  async function run(expression, awaitPromise = false) {
    const result = await send('Runtime.evaluate', { expression, awaitPromise, returnByValue: true });
    assert.equal(result.exceptionDetails, undefined, result.exceptionDetails?.exception?.description);
    return result.result?.value;
  }
  await send('Page.addScriptToEvaluateOnNewDocument', { source: `
    window.cameraErrors = [];
    addEventListener('error', e => cameraErrors.push(e.message));
    addEventListener('unhandledrejection', e => cameraErrors.push(String(e.reason)));
    window.cameraWait = predicate => new Promise((resolve, reject) => {
      let frames = 0;
      function sample() {
        if (predicate()) return resolve();
        if (++frames > 300) return reject(new Error('Camera observation did not settle'));
        requestAnimationFrame(sample);
      }
      requestAnimationFrame(sample);
    });
  ` });
  async function viewport(width = 1440, height = 900) {
    await send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: false });
  }
  async function stable() {
    // Observe automatic camera/layout work without forcing sync or measurement.
    await run(`(async () => {
      await document.fonts.ready;
      let previous = '', equal = 0;
      await cameraWait(() => {
        const container = document.querySelector('.diagram-container');
        const svg = container.querySelector(':scope > svg');
        const rect = svg.getBoundingClientRect();
        const current = JSON.stringify([Archify.view.state(), getComputedStyle(svg).transform,
          svg.style.clipPath, container.scrollLeft, container.getAttribute('data-camera-transaction'),
          container.style.getPropertyValue('--archify-nav-reserve'), rect.x, rect.y, rect.width, rect.height]);
        equal = current === previous ? equal + 1 : 0;
        previous = current;
        return equal >= 8 && !container.hasAttribute('data-camera-transaction');
      });
    })()`, true);
  }
  async function load(mode = 'architecture', { width = 1440, height = 900, theme = 'dark', reduced = false } = {}) {
    await viewport(width, height);
    await send('Emulation.setEmulatedMedia', { media: '', features: [
      { name: 'prefers-reduced-motion', value: reduced ? 'reduce' : 'no-preference' },
    ] });
    const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
    await send('Page.navigate', { url: pathToFileURL(files[mode]).href + `?theme=${theme}` });
    await loaded;
    await stable();
  }
  const snapshotExpression = `(() => {
      const c = document.querySelector('.diagram-container'), svg = c.querySelector(':scope > svg');
      const rect = e => { const r = e.getBoundingClientRect(); return [r.x,r.y,r.width,r.height]; };
      return { state: Archify.view.state(), viewport: Archify.view.logicalViewport(),
        transform: getComputedStyle(svg).transform, clip: svg.style.clipPath,
        stage: rect(svg), nav: rect(c.querySelector('.diagram-nav')), scrollLeft: c.scrollLeft,
        reserve: c.style.getPropertyValue('--archify-nav-reserve'),
        transaction: c.getAttribute('data-camera-transaction'),
        mode: c.getAttribute('data-camera-mode'), detail: c.getAttribute('data-detail-level'),
        viewBox: svg.getAttribute('viewBox'), errors: cameraErrors,
        external: performance.getEntriesByType('resource').map(e => e.name).filter(n => /^https?:/.test(n)) };
    })()`;
  async function snapshot(label, captured) {
    const value = captured || await run(snapshotExpression);
    assert.deepEqual(value.errors, [], label);
    assert.deepEqual(value.external, [], label);
    records.push({ label, ...value });
    return value;
  }
  async function screenshot(name) {
    if (!evidence) return;
    const shot = await send('Page.captureScreenshot', { format: 'png' });
    fs.writeFileSync(path.join(evidence, `${name}.png`), Buffer.from(shot.data, 'base64'));
  }

  await t.test('five modes keep initial state, zoom limits and canonical geometry', async () => {
    for (const mode of Object.keys(cases)) {
      await load(mode);
      const initial = await snapshot(`${mode}-initial`);
      assert.deepEqual(initial.state, { scale: 1, x: 0, y: 0, mode: 'overview' });
      const limits = await run(`(() => {
        const copy = Archify.view.state(); copy.scale = 99;
        const independent = Archify.view.state().scale === 1;
        for (let i = 0; i < 12; i++) Archify.view.zoomIn();
        const max = Archify.view.state().scale;
        for (let i = 0; i < 12; i++) Archify.view.zoomOut();
        return { independent, max, min: Archify.view.state().scale };
      })()`);
      assert.deepEqual(limits, { independent: true, max: 3, min: 1 });
      await stable();
      assert.equal((await snapshot(`${mode}-limits`)).viewBox, initial.viewBox);
    }
  });

  await t.test('pointer cancellation ends dragging and controls do not begin a pan', async () => {
    await load();
    await run('Archify.view.zoomIn()');
    await stable();
    const result = await run(`(() => {
      const c = document.querySelector('.diagram-container'), svg = c.querySelector(':scope > svg');
      const geometry = () => [...svg.querySelectorAll('[data-node-id], [data-edge-id]')].map(n =>
        ['data-node-id','data-edge-id','transform','d','x','y','width','height'].map(a => n.getAttribute(a)));
      const beforeGeometry = JSON.stringify(geometry());
      const pointer = (type, x, y) => new PointerEvent(type, { bubbles: true, pointerId: 31, button: 0, clientX: x, clientY: y });
      const before = Archify.view.state();
      c.querySelector('.diagram-nav').dispatchEvent(pointer('pointerdown', 500, 400));
      c.dispatchEvent(pointer('pointermove', 450, 350));
      const controlExcluded = JSON.stringify(before) === JSON.stringify(Archify.view.state());
      c.dispatchEvent(pointer('pointerdown', 500, 400));
      c.dispatchEvent(pointer('pointermove', 450, 350));
      const dragged = c.classList.contains('is-panning');
      c.dispatchEvent(pointer('pointercancel', 450, 350));
      const cancelled = !c.classList.contains('is-panning');
      const ended = Archify.view.state();
      c.dispatchEvent(pointer('pointermove', 100, 100));
      return { step: before.scale, controlExcluded, dragged, cancelled,
        unchanged: JSON.stringify(ended) === JSON.stringify(Archify.view.state()),
        geometryUnchanged: beforeGeometry === JSON.stringify(geometry()) };
    })()`);
    assert.deepEqual(result, { step: 1.25, controlExcluded: true, dragged: true, cancelled: true, unchanged: true, geometryUnchanged: true });
    await stable();
    await snapshot('pointer-cancel');
  });

  await t.test('target selection, failure branches and instant options preserve their side effects', async () => {
    await load();
    const value = await run(`(async () => {
      const v = Archify.view, node = document.querySelector('[data-node-id="api"]');
      const original = node.getBBox;
      const empty = v.reveal([], { instant: true });
      const unknown = v.reveal(['missing'], { instant: true });
      node.getBBox = () => { throw new Error('test geometry'); };
      const failedBox = v.reveal(['api'], { instant: true });
      node.getBBox = original;
      const mixed = v.reveal(['missing', 'api'], { instant: true, maxScale: 1.5, padding: 64 });
      const mixedResult = await mixed.finished;
      const multi = v.reveal(['api', 'db'], { instant: true, includeNeighbors: true });
      await multi.finished;
      return { empty, unknown, failedBox, mixed: mixedResult.state, scale: mixed.target.scale,
        multi: multi.settled, badCenter: v.centerAt('invalid', 10) };
    })()`, true);
    assert.deepEqual(value, { empty: false, unknown: false, failedBox: false, mixed: 'complete', scale: 1.5, multi: true, badCenter: false });
    await stable();
    await snapshot('targets');
  });

  await t.test('running transactions complete, replace, cancel and yield to manual navigation', async () => {
    for (const theme of ['dark', 'light']) {
      await load('architecture', { theme });
      // Start and observe in one page evaluation: CDP round trips may outlast
      // the animation, so the middle snapshot must be captured in its frame.
      const animation = await run(`(async () => {
        const camera = Archify.view.reveal(['api'], { duration: 520 });
        const samples = [];
        let middle = null;
        function sampleCamera() {
          const svg = document.querySelector('.diagram-container > svg');
          const state = Archify.view.state();
          samples.push({ state, transform: getComputedStyle(svg).transform, clip: svg.style.clipPath, settled: camera.settled });
          if (!middle && !camera.settled && state.scale > 1.15) middle = ${snapshotExpression};
          if (!camera.settled) requestAnimationFrame(sampleCamera);
        }
        requestAnimationFrame(sampleCamera);
        const outcome = await camera.finished;
        return { middle, samples, outcome };
      })()`, true);
      assert.ok(animation.middle, 'the running animation must yield a middle snapshot');
      const middle = await snapshot(`animation-middle-${theme}`, animation.middle);
      assert.ok(middle.state.scale > 1 && middle.state.scale < 2.15);
      assert.equal(animation.outcome.state, 'complete');
      assert.ok(animation.samples.filter(s => !s.settled && s.clip).length > 1);
      if (evidence) fs.writeFileSync(path.join(evidence, `animation-${theme}.json`), JSON.stringify(animation.samples, null, 2));
      await stable();
      await snapshot(`animation-final-${theme}`);
      await screenshot(`animation-final-${theme}`);
    }
    const results = await run(`(async () => {
      const v = Archify.view, results = [];
      for (const action of ['replace', 'cancel', 'commit', 'manual', 'reset']) {
        v.reset({ automatic: true });
        const first = v.reveal(['api'], { duration: 520 });
        await cameraWait(() => !first.settled && v.state().scale > 1.05);
        const before = v.state();
        let second;
        if (action === 'replace') second = v.reveal(['db'], { instant: true });
        else if (action === 'cancel' || action === 'commit') first.cancel('test-stop', action === 'commit');
        else if (action === 'manual') v.zoomOut();
        else v.reset({ automatic: true });
        const outcome = await first.finished;
        const repeated = first.cancel('again', true);
        const ended = v.state();
        await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
        results.push({ action, outcome: outcome.state, repeated, settled: first.settled,
          before, ended, target: first.target,
          unchanged: JSON.stringify(ended) === JSON.stringify(v.state()),
          next: second ? (await second.finished).state : null });
      }
      return results;
    })()`, true);
    assert.deepEqual(results.map(r => r.outcome), ['replaced', 'test-stop', 'test-stop', 'manual', 'reset']);
    for (const r of results) {
      assert.equal(r.repeated, false); assert.equal(r.settled, true); assert.equal(r.unchanged, true);
      if (r.action === 'cancel' || r.action === 'commit') {
        assert.notDeepEqual(r.before, r.target, 'cancellation must occur before reaching the target');
        assert.deepEqual(r.ended, r.action === 'commit' ? r.target : r.before, r.action);
      }
    }
    records.push({ label: 'transaction-results', results });
    await stable();
  });

  await t.test('mobile branches, automatic scroll guard and scrollTo fallback stay distinct', async () => {
    for (const width of [719, 720, 721]) {
      await load('architecture', { width });
      const result = await run(`(async () => {
        const receipt = Archify.view.reveal(['db'], { instant: true });
        return { outcome: (await receipt.finished).state, scrollTarget: 'scrollLeft' in receipt.target };
      })()`, true);
      assert.equal(result.scrollTarget, width <= 720);
      await stable();
      await snapshot(`width-${width}`);
    }
    await load('architecture', { width: 720 });
    const value = await run(`(async () => {
      const c = document.querySelector('.diagram-container'), v = Archify.view;
      const empty = v.reveal([]);
      const emptyMode = v.state().mode;
      c.removeAttribute('data-wide-diagram');
      const contained = v.reveal([]);
      const containedOutcome = (await contained.finished).state;
      c.setAttribute('data-wide-diagram', 'true');
      const original = c.scrollTo;
      c.scrollTo = () => { throw new Error('test scroll fallback'); };
      const fallback = v.reveal(['db'], { instant: true });
      await fallback.finished;
      // Even the assignment fallback participates in the container's existing
      // smooth-scroll CSS. Receipt completion alone is not scroll convergence.
      await cameraWait(() => Math.abs(c.scrollLeft - fallback.target.scrollLeft) < 1);
      const reached = Math.abs(c.scrollLeft - fallback.target.scrollLeft) < 1;
      c.scrollTo = original;
      v.reset({ automatic: true });
      const started = Date.now(), moving = v.reveal(['users']);
      c.dispatchEvent(new Event('scroll'));
      const protectedMode = v.state().mode;
      const outcome = await moving.finished;
      await cameraWait(() => Date.now() - started > 500);
      c.dispatchEvent(new Event('scroll'));
      return { empty, emptyMode, containedOutcome, reached, protectedMode, outcome: outcome.state, manualMode: v.state().mode };
    })()`, true);
    assert.deepEqual(value, { empty: false, emptyMode: 'semantic', containedOutcome: 'complete', reached: true,
      protectedMode: 'semantic', outcome: 'complete', manualMode: 'manual' });
    await stable();
    await snapshot('mobile-scroll');
  });

  await t.test('reduced motion and call-time hidden state keep immediate completion semantics', async () => {
    await load('architecture', { theme: 'light', reduced: true });
    assert.equal(await run(`Archify.view.reveal(['api']).finished.then(r => r.state)`, true), 'reduced-motion');
    await stable();
    await snapshot('reduced-motion');
    await load();
    // A local capability fixture exercises the call-time branch. It does not
    // claim to emulate background-tab frame throttling or visibility events.
    const hidden = await run(`(async () => {
      Object.defineProperty(document, 'hidden', { configurable: true, value: true });
      try { return (await Archify.view.reveal(['api']).finished).state; }
      finally { delete document.hidden; }
    })()`, true);
    assert.equal(hidden, 'hidden');
    await stable();
    await snapshot('hidden-call-fixture');
  });

  await t.test('actual Story, Route, Finder and Radar callers retain camera ownership', async () => {
    await load('trace');
    const story = await run(`(async () => {
      Archify.motionGovernor.resume();
      Archify.guidedViews.activate('request-path');
      await cameraWait(() => !Archify.guidedViews.handoff());
      Archify.guidedViews.activate('identity-and-cache');
      await cameraWait(() => Archify.guidedViews.handoff()?.mode === 'settling');
      const wasHandoff = !!Archify.guidedViews.handoff();
      Archify.view.zoomIn();
      const cleared = Archify.guidedViews.handoff() === null;
      const played = Archify.guidedViews.play();
      const wasPlaying = Archify.guidedViews.isPlaying();
      Archify.view.zoomOut();
      return { wasHandoff, cleared, played, wasPlaying, paused: !Archify.guidedViews.isPlaying() };
    })()`, true);
    assert.deepEqual(story, { wasHandoff: true, cleared: true, played: true, wasPlaying: true, paused: true });
    await stable();
    await snapshot('story-takeover');
    await load('trace');
    const route = await run(`(() => {
      Archify.motionGovernor.resume();
      Archify.routeProbe.begin({ source: 'users' });
      Archify.routeProbe.choose('db');
      const played = Archify.routeProbe.playJourney();
      const before = Archify.routeProbe.result();
      Archify.view.zoomIn();
      const after = Archify.routeProbe.result();
      return { played, before, after };
    })()`);
    assert.equal(route.played, true); assert.equal(route.before.playing, true); assert.equal(route.after.playing, false);
    assert.deepEqual(route.after.nodes, route.before.nodes);
    assert.equal(route.after.journey, route.before.journey);
    await stable();
    await snapshot('route-takeover');
    await load('architecture', { height: 600 });
    await run(`Archify.finder.select('api')`);
    await stable();
    assert.equal(await run(`Archify.focus.active()`), 'api');
    await snapshot('finder-low-height');
    await run(`window.scrollTo(0, 160); Archify.radar.open(); Archify.radar.focus('db')`);
    await stable();
    assert.equal(await run(`Archify.focus.active()`), 'db');
    await snapshot('radar-focus');
    await viewport(1280, 720);
    await stable();
    await snapshot('automatic-resize');
    await run(`location.hash = 'focus=api'`);
    await run(`cameraWait(() => Archify.focus.active() === 'api')`, true);
    await stable();
    await snapshot('automatic-hashchange');
  });

  await t.test('export removes camera transforms without mutating the live camera', async () => {
    await load();
    await run(`Archify.view.reveal(['api'], { instant: true })`);
    await stable();
    const exported = await run(`(async () => {
      const svg = document.querySelector('.diagram-container > svg'), before = svg.outerHTML;
      const state = JSON.stringify(Archify.view.state()), original = URL.createObjectURL;
      let blob;
      URL.createObjectURL = value => { if (value.type.startsWith('image/svg+xml')) blob = value; return original.call(URL, value); };
      let after;
      try { const pending = Archify.exportMenu.run('svg'); after = svg.outerHTML; await pending; }
      finally { URL.createObjectURL = original; }
      const text = await blob.text();
      const root = new DOMParser().parseFromString(text, 'image/svg+xml').documentElement;
      return { text, unchanged: before === after && state === JSON.stringify(Archify.view.state()),
        clean: !root.hasAttribute('data-view-scale') && !root.style.transform && !root.style.clipPath,
        geometry: root.getAttribute('viewBox') === svg.getAttribute('viewBox'),
        ids: [...root.querySelectorAll('[data-node-id]')].map(n => n.getAttribute('data-node-id')).join() === [...svg.querySelectorAll('[data-node-id]')].map(n => n.getAttribute('data-node-id')).join() };
    })()`, true);
    assert.equal(exported.unchanged, true); assert.equal(exported.clean, true); assert.equal(exported.geometry, true); assert.equal(exported.ids, true);
    if (evidence) fs.writeFileSync(path.join(evidence, 'camera-export.svg'), exported.text);
    await snapshot('export');
  });
});
```

## test/viewer-chrome-layout.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

import { ChromeVisualBrowser, findChrome } from '../bin/visual-check.mjs';
import { MIN_PROJECTED_NODE_TEXT_PX } from '../renderers/shared/desktop-readability.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-viewer-chrome-layout-'));
const chromePath = process.env.ARCHIFY_CHROME ? findChrome() : null;

const CASES = {
  architecture: 'web-app.architecture.json',
  workflow: 'agent-tool-call.workflow.json',
  sequence: 'cache-miss-request.sequence.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
};

function render(mode, example) {
  const output = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, 'bin', 'archify.mjs'),
    'render',
    mode,
    path.join(skillRoot, 'examples', example),
    output,
  ]);
  return output;
}

function renderWithoutLegend() {
  const source = JSON.parse(fs.readFileSync(
    path.join(skillRoot, 'examples', CASES.architecture),
    'utf8',
  ));
  source.meta = { ...source.meta, legend: { mode: 'hidden' } };
  const input = path.join(tmp, 'architecture-no-legend.json');
  const output = path.join(tmp, 'architecture-no-legend.html');
  fs.writeFileSync(input, `${JSON.stringify(source, null, 2)}\n`);
  execFileSync(process.execPath, [
    path.join(skillRoot, 'bin', 'archify.mjs'),
    'render',
    'architecture',
    input,
    output,
  ]);
  return output;
}

function canonicalSvg(html) {
  return html.match(/<svg\b[\s\S]*?<\/svg>/)?.[0] || '';
}

async function evaluate(browser, sessionId, expression, awaitPromise = false) {
  const response = await browser.cdp.send('Runtime.evaluate', {
    expression,
    awaitPromise,
    returnByValue: true,
  }, sessionId);
  if (response.exceptionDetails) {
    throw new Error(response.exceptionDetails.exception?.description
      || response.exceptionDetails.text
      || 'Runtime.evaluate failed');
  }
  return response.result?.value;
}

async function waitForLayout(browser, sessionId) {
  return evaluate(browser, sessionId, `(function () {
    var fontsReady = document.fonts && document.fonts.ready
      ? document.fonts.ready.catch(function () {})
      : Promise.resolve();
    return fontsReady.then(function () {
      return new Promise(function (resolve, reject) {
        var previous = '';
        var stableFrames = 0;
        var sampledFrames = 0;
        function rect(element) {
          if (!element) return 'missing';
          var value = element.getBoundingClientRect();
          return [value.left, value.top, value.right, value.bottom].map(function (entry) {
            return Math.round(entry * 100) / 100;
          }).join(',');
        }
        function sample() {
          sampledFrames += 1;
          var container = document.querySelector('.diagram-container');
          var current = [
            rect(container),
            rect(container && container.querySelector(':scope > svg')),
            rect(document.querySelector('.diagram-nav')),
            rect(document.querySelector('[data-legend]')),
            rect(document.getElementById('semantic-lens')),
            rect(document.getElementById('overview-map')),
            container ? getComputedStyle(container).getPropertyValue('--archify-nav-reserve') : ''
          ].join('|');
          if (current === previous) stableFrames += 1;
          else {
            previous = current;
            stableFrames = 0;
          }
          /* Final-artifact tests cannot inspect private scheduler flags. Eight
             equal frames cover the public three-frame contract plus any
             reader/viewer handoff queued after a ResizeObserver callback. */
          if (stableFrames >= 8) {
            resolve({ stable: true, snapshot: current, sampledFrames: sampledFrames });
            return;
          }
          if (sampledFrames >= 240) {
            reject(new Error('Final Viewer geometry did not stabilize.'));
            return;
          }
          requestAnimationFrame(sample);
        }
        requestAnimationFrame(sample);
      });
    });
  })()`, true);
}

async function finalGeometry(browser, sessionId) {
  return evaluate(browser, sessionId, `(function () {
    function area(a, b) {
      if (!a || !b || !a.width || !a.height || !b.width || !b.height) return 0;
      return Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left)) *
        Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top));
    }
    var container = document.querySelector('.diagram-container');
    var legend = document.querySelector('[data-legend]');
    var nav = document.querySelector('.diagram-nav');
    var svg = container && container.querySelector(':scope > svg');
    var lens = document.getElementById('semantic-lens');
    var radar = document.getElementById('overview-map');
    var passport = document.getElementById('focus-chip');
    var chromeReceipt = window.Archify && Archify.viewerChromeLayout
      && typeof Archify.viewerChromeLayout.receipt === 'function'
      ? Archify.viewerChromeLayout.receipt()
      : null;
    var viewBox = svg && svg.viewBox && svg.viewBox.baseVal;
    var projectedScale = svg && viewBox && viewBox.width > 0
      ? Math.min(1, svg.getBoundingClientRect().width / viewBox.width)
      : 0;
    var minimumProjectedNodeTextPx = null;
    if (svg && projectedScale > 0) {
      Array.from(svg.querySelectorAll('text[data-node-label], text[data-boundary-label], text[data-detail="context"]')).forEach(function (text) {
        if (text.hasAttribute('data-detail') && !text.closest('[data-node-id]')) return;
        var sourceFontPx = parseFloat(text.getAttribute('font-size') || '');
        if (!Number.isFinite(sourceFontPx)) return;
        var projectedFontPx = sourceFontPx * projectedScale;
        if (minimumProjectedNodeTextPx == null || projectedFontPx < minimumProjectedNodeTextPx) {
          minimumProjectedNodeTextPx = projectedFontPx;
        }
      });
    }
    var legendRect = legend && getComputedStyle(legend).display !== 'none' ? legend.getBoundingClientRect() : null;
    var navRect = nav && getComputedStyle(nav).display !== 'none' ? nav.getBoundingClientRect() : null;
    var stageRect = window.Archify && Archify.viewerChromeLayout
      && typeof Archify.viewerChromeLayout.stageRect === 'function'
      ? Archify.viewerChromeLayout.stageRect()
      : null;
    var lensRect = lens && !lens.hidden && getComputedStyle(lens).display !== 'none' ? lens.getBoundingClientRect() : null;
    var radarRect = radar && !radar.hidden && getComputedStyle(radar).display !== 'none' ? radar.getBoundingClientRect() : null;
    var passportRect = passport && !passport.hidden && getComputedStyle(passport).display !== 'none' ? passport.getBoundingClientRect() : null;
    var stageDockIntersectionArea = area(stageRect, navRect);
    var semanticDockIntersectionArea = stageDockIntersectionArea > 0 && navRect && svg
      ? Array.from(svg.querySelectorAll('[data-node-id]')).reduce(function (maximum, node) {
          return Math.max(maximum, area(node.getBoundingClientRect(), navRect));
        }, 0)
      : 0;
    return {
      reserve: parseFloat(getComputedStyle(container).getPropertyValue('--archify-nav-reserve')) || 0,
      receiptReserve: chromeReceipt ? chromeReceipt.reserve : null,
      receiptEligible: chromeReceipt ? chromeReceipt.eligible : null,
      receiptStageIntersectionArea: chromeReceipt ? chromeReceipt.stageIntersectionArea : null,
      minimumProjectedNodeTextPx: minimumProjectedNodeTextPx,
      stageGap: navRect && stageRect ? navRect.top - stageRect.bottom : null,
      dockStageIntersectionArea: stageDockIntersectionArea,
      legendDockIntersectionArea: stageDockIntersectionArea > 0 ? area(legendRect, navRect) : 0,
      semanticDockIntersectionArea: semanticDockIntersectionArea,
      legendLensIntersectionArea: area(legendRect, lensRect),
      navLensIntersectionArea: area(navRect, lensRect),
      legendRadarIntersectionArea: area(legendRect, radarRect),
      navRadarIntersectionArea: area(navRect, radarRect),
      legendPassportIntersectionArea: area(legendRect, passportRect),
      navPassportIntersectionArea: area(navRect, passportRect),
      radarPassportIntersectionArea: area(radarRect, passportRect),
      legendRect: legendRect ? { left: legendRect.left, right: legendRect.right, top: legendRect.top, bottom: legendRect.bottom } : null,
      navRect: navRect ? { left: navRect.left, right: navRect.right, top: navRect.top, bottom: navRect.bottom } : null,
      passportRect: passportRect ? { left: passportRect.left, right: passportRect.right, top: passportRect.top, bottom: passportRect.bottom } : null,
      radarRect: radarRect ? { left: radarRect.left, right: radarRect.right, top: radarRect.top, bottom: radarRect.bottom } : null,
      hasLegend: Boolean(legendRect && legendRect.width && legendRect.height),
      scrollWidth: document.documentElement.scrollWidth,
      scrollHeight: document.documentElement.scrollHeight,
      innerWidth: window.innerWidth,
      innerHeight: window.innerHeight,
      containerBottom: container ? container.getBoundingClientRect().bottom : null,
      containerHeight: container ? container.clientHeight : null,
      receiptGap: chromeReceipt ? chromeReceipt.gap : null,
      navBottom: navRect ? navRect.bottom : null
    };
  })()`);
}

async function edgePaintHitsUnderDock(browser, sessionId, selector) {
  return evaluate(browser, sessionId, `(function () {
    var edge = document.querySelector(${JSON.stringify(selector)});
    var nav = document.querySelector('.diagram-nav');
    if (!edge || !nav || typeof edge.getTotalLength !== 'function') return null;
    var navRect = nav.getBoundingClientRect();
    var matrix = edge.getScreenCTM();
    var length = edge.getTotalLength();
    var hits = [];
    for (var offset = 0; offset <= length; offset += 0.25) {
      var point = edge.getPointAtLength(offset).matrixTransform(matrix);
      if (point.x < navRect.left || point.x > navRect.right || point.y < navRect.top || point.y > navRect.bottom) continue;
      if (document.elementsFromPoint(point.x, point.y).includes(edge)) {
        hits.push({ x: point.x, y: point.y });
        if (hits.length >= 5) break;
      }
    }
    return hits;
  })()`);
}

async function load(browser, artifactPath, { width = 1440, height = 900, query = '' } = {}) {
  const sessionId = await browser.sessionPromise;
  await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
    width,
    height,
    deviceScaleFactor: 1,
    mobile: false,
  }, sessionId);
  const loaded = browser.cdp.waitFor('Page.loadEventFired', sessionId);
  const navigation = await browser.cdp.send('Page.navigate', {
    url: pathToFileURL(artifactPath).href + query,
  }, sessionId);
  if (navigation.errorText) throw new Error(`Chrome navigation failed: ${navigation.errorText}`);
  await loaded;
  await evaluate(browser, sessionId, `document.documentElement.setAttribute('data-motion', 'still')`);
  await waitForLayout(browser, sessionId);
  return sessionId;
}

test('the public CLI gives all typed renderers one final Viewer contract', () => {
  const directChildSvg = /<div class="diagram-container"[^>]*>\s*<svg\b/;
  assert.doesNotMatch(
    '<div class="diagram-container"><section></section><svg>',
    directChildSvg,
    'the direct-child assertion must not cross an intervening wrapper',
  );
  for (const [mode, example] of Object.entries(CASES)) {
    const output = render(mode, example);
    const html = fs.readFileSync(output, 'utf8');
    assert.match(html, /class="[^"]*\bdiagram-nav\b[^"]*"/, mode);
    assert.match(html, /data-legend/, mode);
    assert.match(html, directChildSvg, `${mode} keeps the SVG as a direct child`);
    assert.doesNotMatch(html, /class="diagram-stage"/, `${mode} does not re-nest the exported SVG`);
    assert.doesNotMatch(canonicalSvg(html), /nav-safe-rail|archify-nav-reserve|viewerChromeLayout/, mode);
    execFileSync(process.execPath, [path.join(skillRoot, 'bin', 'archify.mjs'), 'check', output]);
  }
});

test('Viewer chrome remains outside the canonical SVG export boundary', () => {
  const html = fs.readFileSync(render('architecture', CASES.architecture), 'utf8');
  const svg = canonicalSvg(html);
  assert.match(svg, /data-legend/);
  assert.doesNotMatch(svg, /diagram-nav|data-nav-stage-rail|viewerChromeLayout/);
});

test('Dock Safe Rail keeps typed renderers clear across themes, Presentation, and low-height desktops', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  const matrix = Object.keys(CASES).flatMap((mode) => [
    { mode, theme: 'light', width: 1440, height: 820, present: false },
    { mode, theme: 'dark', width: 1440, height: 900, present: true },
  ]);
  try {
    for (const entry of matrix) {
      const query = `?theme=${entry.theme}${entry.present ? '&present=1' : ''}`;
      const sessionId = await load(browser, render(entry.mode, CASES[entry.mode]), {
        width: entry.width,
        height: entry.height,
        query,
      });
      const receipt = await finalGeometry(browser, sessionId);
      const message = `${entry.mode}: ${JSON.stringify({ entry, receipt })}`;
      assert.ok(receipt.reserve > 0, message);
      assert.ok(receipt.stageGap >= 9, message);
      assert.equal(receipt.dockStageIntersectionArea, 0, message);
      assert.ok(receipt.scrollWidth <= receipt.innerWidth, message);
      assert.ok(receipt.navBottom <= receipt.containerBottom + 0.5, message);
      /* Normal artifacts intentionally keep supporting cards in document
         flow on low-height pages. Presentation removes that document scroll
         while every mode keeps the Viewer itself vertically contained. */
      if (entry.present) {
        assert.ok(receipt.scrollHeight <= receipt.innerHeight, message);
      }
      assert.ok(receipt.minimumProjectedNodeTextPx >= MIN_PROJECTED_NODE_TEXT_PX, message);
    }
  } finally {
    await browser.close();
  }
});

test('an artifact with no Legend still receives the desktop stage rail', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, renderWithoutLegend());
    const receipt = await finalGeometry(browser, sessionId);

    assert.equal(receipt.hasLegend, false, JSON.stringify(receipt));
    assert.ok(receipt.reserve > 0, JSON.stringify(receipt));
    assert.ok(receipt.stageGap >= 9, JSON.stringify(receipt));
    assert.ok(receipt.navBottom <= receipt.containerBottom + 0.5, JSON.stringify(receipt));
  } finally {
    await browser.close();
  }
});

test('Dock Safe Rail resolves a forced Legend collision across the shared diagram viewer', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    for (const [mode, example] of Object.entries(CASES)) {
      const sessionId = await load(browser, render(mode, example));
      const setup = await evaluate(browser, sessionId, `(function () {
        var nav = document.querySelector('.diagram-nav');
        var legendElement = document.querySelector('[data-legend]');
        if (!legendElement) {
          return { noLegend: true };
        }
        var initialLegend = legendElement.getBoundingClientRect();
        var containerRect = document.querySelector('.diagram-container').getBoundingClientRect();
        nav.style.right = 'auto';
        nav.style.left = Math.max(0, initialLegend.left - containerRect.left) + 'px';
        nav.style.bottom = Math.max(0, containerRect.bottom - initialLegend.bottom) + 'px';
        nav.style.width = Math.max(240, initialLegend.width) + 'px';
        window.dispatchEvent(new Event('resize'));
        return { noLegend: false };
      })()`);

      if (setup.noLegend) {
        const receipt = await finalGeometry(browser, sessionId);
        assert.ok(receipt.reserve > 0, mode);
        assert.ok(receipt.stageGap >= 9, mode);
        continue;
      }
      await waitForLayout(browser, sessionId);
      const receipt = await finalGeometry(browser, sessionId);
      assert.ok(receipt.reserve > 0, `${mode}: ${JSON.stringify(receipt)}`);
      assert.equal(receipt.legendDockIntersectionArea, 0, `${mode}: ${JSON.stringify(receipt)}`);
      assert.ok(receipt.stageGap >= 9, `${mode}: ${JSON.stringify(receipt)}`);
    }
  } finally {
    await browser.close();
  }
});

test('Maka remains collision-free at the reported Retina-equivalent viewport', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const output = path.join(tmp, 'maka-architecture.html');
  execFileSync(process.execPath, [
    path.join(skillRoot, 'bin', 'archify.mjs'),
    'render',
    'architecture',
    path.resolve(skillRoot, '..', 'examples', 'maka-architecture.architecture.json'),
    output,
  ]);
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, output, { width: 1484, height: 724 });
    const receipt = await evaluate(browser, sessionId, `(function () {
      var legend = document.querySelector('[data-legend]').getBoundingClientRect();
      var dock = document.querySelector('.diagram-nav').getBoundingClientRect();
      var width = Math.max(0, Math.min(legend.right, dock.right) - Math.max(legend.left, dock.left));
      var height = Math.max(0, Math.min(legend.bottom, dock.bottom) - Math.max(legend.top, dock.top));
      return {
        intersectionArea: width * height,
        scrollWidth: document.documentElement.scrollWidth,
        innerWidth: window.innerWidth
      };
    })()`);

    assert.equal(receipt.intersectionArea, 0, JSON.stringify(receipt));
    assert.ok(receipt.scrollWidth <= receipt.innerWidth, JSON.stringify(receipt));
  } finally {
    await browser.close();
  }
});

test('a real 5px Legend gap keeps the stage rail and Legend clear', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture));
    await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      var legend = document.querySelector('[data-legend]');
      var nav = document.querySelector('.diagram-nav');
      var legendRect = legend.getBoundingClientRect();
      var containerRect = container.getBoundingClientRect();
      nav.style.right = 'auto';
      nav.style.left = (legendRect.right - containerRect.left + 5) + 'px';
      nav.style.bottom = Math.max(0, containerRect.bottom - legendRect.bottom) + 'px';
      window.dispatchEvent(new Event('resize'));
    })()`);
    await waitForLayout(browser, sessionId);
    const receipt = await finalGeometry(browser, sessionId);

    assert.equal(receipt.legendDockIntersectionArea, 0, JSON.stringify(receipt));
    assert.ok(receipt.reserve > 0, JSON.stringify(receipt));
    assert.ok(receipt.stageGap >= 9, JSON.stringify(receipt));
  } finally {
    await browser.close();
  }
});

test('Presentation keeps its visible Dock clear of a colliding Legend', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture), { query: '?present=1' });
    await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      var legend = document.querySelector('[data-legend]');
      var nav = document.querySelector('.diagram-nav');
      var legendRect = legend.getBoundingClientRect();
      var containerRect = container.getBoundingClientRect();
      nav.style.right = 'auto';
      nav.style.left = Math.max(0, legendRect.left - containerRect.left) + 'px';
      nav.style.bottom = Math.max(0, containerRect.bottom - legendRect.bottom) + 'px';
      nav.style.width = Math.max(240, legendRect.width) + 'px';
      window.dispatchEvent(new Event('resize'));
    })()`);
    await waitForLayout(browser, sessionId);
    const receipt = await finalGeometry(browser, sessionId);

    assert.equal(receipt.legendDockIntersectionArea, 0, JSON.stringify(receipt));
    assert.ok(receipt.reserve > 0, JSON.stringify(receipt));
  } finally {
    await browser.close();
  }
});

test('manual zoom and pan reschedules Legend and Dock collision measurement', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture));
    await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      var zoomIn = document.querySelector('[data-view="in"]');
      for (var index = 0; index < 8; index += 1) zoomIn.click();
      var svg = container.querySelector(':scope > svg');
      var rect = svg.getBoundingClientRect();
      var pointer = { bubbles: true, pointerId: 7, button: 0 };
      var startX = rect.left + rect.width / 2;
      var startY = rect.top + rect.height / 2;
      container.dispatchEvent(new PointerEvent('pointerdown', Object.assign({ clientX: startX, clientY: startY }, pointer)));
      container.dispatchEvent(new PointerEvent('pointermove', Object.assign({ clientX: startX + 210, clientY: startY - 500 }, pointer)));
      container.dispatchEvent(new PointerEvent('pointerup', Object.assign({ clientX: startX + 210, clientY: startY - 500 }, pointer)));
    })()`);
    await waitForLayout(browser, sessionId);
    const receipt = await finalGeometry(browser, sessionId);

    assert.equal(receipt.legendDockIntersectionArea, 0, JSON.stringify(receipt));
    assert.equal(receipt.semanticDockIntersectionArea, 0, JSON.stringify(receipt));
    assert.ok(receipt.reserve > 0, JSON.stringify(receipt));
  } finally {
    await browser.close();
  }
});

test('camera pan clips authored relationship paint at the protected stage boundary', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture));
    await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      var zoomIn = document.querySelector('[data-view="in"]');
      for (var index = 0; index < 8; index += 1) zoomIn.click();
      var svg = container.querySelector(':scope > svg');
      var rect = svg.getBoundingClientRect();
      var pointer = { bubbles: true, pointerId: 11, button: 0 };
      var startX = rect.left + rect.width / 2;
      var startY = rect.top + rect.height / 2;
      container.dispatchEvent(new PointerEvent('pointerdown', Object.assign({ clientX: startX, clientY: startY }, pointer)));
      container.dispatchEvent(new PointerEvent('pointermove', Object.assign({ clientX: startX, clientY: startY + 500 }, pointer)));
      container.dispatchEvent(new PointerEvent('pointerup', Object.assign({ clientX: startX, clientY: startY + 500 }, pointer)));
    })()`);
    await waitForLayout(browser, sessionId);
    const hits = await edgePaintHitsUnderDock(
      browser,
      sessionId,
      'path[data-edge-id="jwt-verification"][data-edge-from="auth"][data-edge-to="api"]',
    );

    assert.deepEqual(hits, [], JSON.stringify(hits));
  } finally {
    await browser.close();
  }
});

test('live camera transitions keep authored relationship paint outside the Dock on every frame', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  const artifact = render('architecture', CASES.architecture);
  const scenarios = [
    { name: 'zoom-in', setupZoomClicks: 7, action: 'in', duration: 260, clearsClip: false },
    { name: 'zoom-out', setupZoomClicks: 8, action: 'out', duration: 260, clearsClip: false },
    { name: 'reset', setupZoomClicks: 8, action: 'reset', duration: 420, clearsClip: true },
    { name: 'interrupted zoom', setupZoomClicks: 8, action: 'interrupt', duration: 420, clearsClip: false },
  ];
  try {
    for (const scenario of scenarios) {
      const sessionId = await load(browser, artifact);
      const result = await evaluate(browser, sessionId, `(function () {
        var scenario = ${JSON.stringify(scenario)};
        var container = document.querySelector('.diagram-container');
        var svg = container.querySelector(':scope > svg');
        var zoomIn = document.querySelector('[data-view="in"]');
        var zoomOut = document.querySelector('[data-view="out"]');
        for (var index = 0; index < scenario.setupZoomClicks; index += 1) zoomIn.click();

        var rect = svg.getBoundingClientRect();
        var pointer = { bubbles: true, pointerId: 17, button: 0 };
        var x = rect.left + rect.width / 2;
        var y = rect.top + rect.height / 2;
        container.dispatchEvent(new PointerEvent('pointerdown', Object.assign({ clientX: x, clientY: y }, pointer)));
        container.dispatchEvent(new PointerEvent('pointermove', Object.assign({ clientX: x, clientY: y + 500 }, pointer)));
        container.dispatchEvent(new PointerEvent('pointerup', Object.assign({ clientX: x, clientY: y + 500 }, pointer)));

        document.documentElement.removeAttribute('data-motion');
        return new Promise(function (resolve) {
          requestAnimationFrame(function () {
            if (scenario.action === 'in') zoomIn.click();
            else if (scenario.action === 'out') zoomOut.click();
            else if (scenario.action === 'reset') document.querySelector('[data-view="reset"]').click();
            else {
              zoomOut.click();
              requestAnimationFrame(function () { zoomIn.click(); });
            }
            var edge = document.querySelector(
              'path[data-edge-id="jwt-verification"][data-edge-from="auth"][data-edge-to="api"]'
            );
            var hits = [];
            var started = performance.now();
            function sample(now) {
              var dock = document.querySelector('.diagram-nav').getBoundingClientRect();
              var matrix = edge.getScreenCTM();
              var length = edge.getTotalLength();
              for (var offset = 0; offset <= length; offset += 0.25) {
                var point = edge.getPointAtLength(offset).matrixTransform(matrix);
                if (
                  point.x >= dock.left && point.x <= dock.right &&
                  point.y >= dock.top && point.y <= dock.bottom &&
                  document.elementsFromPoint(point.x, point.y).includes(edge)
                ) {
                  hits.push({ ms: now - started, x: point.x, y: point.y });
                  break;
                }
              }
              if (now - started < scenario.duration) requestAnimationFrame(sample);
              else resolve({ hits: hits, clipPath: svg.style.getPropertyValue('clip-path') });
            }
            requestAnimationFrame(sample);
          });
        });
      })()`, true);

      assert.deepEqual(result.hits, [], `${scenario.name}: ${JSON.stringify(result.hits)}`);
      if (scenario.clearsClip) {
        assert.equal(result.clipPath, '', `${scenario.name} retains runtime clip-path`);
      }
    }
  } finally {
    await browser.close();
  }
});

test('zoom keeps the desktop rail stable and reports protected stage geometry', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture));
    const baseline = await finalGeometry(browser, sessionId);
    await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      var zoomIn = document.querySelector('[data-view="in"]');
      for (var index = 0; index < 8; index += 1) zoomIn.click();
      var svg = container.querySelector(':scope > svg');
      var rect = svg.getBoundingClientRect();
      var pointer = { bubbles: true, pointerId: 13, button: 0 };
      var startX = rect.left + rect.width / 2;
      var startY = rect.top + rect.height / 2;
      container.dispatchEvent(new PointerEvent('pointerdown', Object.assign({ clientX: startX, clientY: startY }, pointer)));
      container.dispatchEvent(new PointerEvent('pointermove', Object.assign({ clientX: startX + 210, clientY: startY - 500 }, pointer)));
      container.dispatchEvent(new PointerEvent('pointerup', Object.assign({ clientX: startX + 210, clientY: startY - 500 }, pointer)));
    })()`);
    await waitForLayout(browser, sessionId);
    const zoomed = await finalGeometry(browser, sessionId);

    assert.ok(Math.abs(zoomed.reserve - baseline.reserve) <= 1, JSON.stringify({ baseline, zoomed }));
    assert.equal(zoomed.dockStageIntersectionArea, 0, JSON.stringify({ baseline, zoomed }));
    assert.equal(zoomed.receiptStageIntersectionArea, 0, JSON.stringify({ baseline, zoomed }));
  } finally {
    await browser.close();
  }
});

test('Reset followed immediately by zoom and pan retains a collision-free desktop rail', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture));
    const baseline = await finalGeometry(browser, sessionId);
    await evaluate(browser, sessionId, `(function () {
      var container = document.querySelector('.diagram-container');
      document.querySelector('[data-view="reset"]').click();
      var zoomIn = document.querySelector('[data-view="in"]');
      for (var index = 0; index < 8; index += 1) zoomIn.click();
      var svg = container.querySelector(':scope > svg');
      var rect = svg.getBoundingClientRect();
      var pointer = { bubbles: true, pointerId: 11, button: 0 };
      var startX = rect.left + rect.width / 2;
      var startY = rect.top + rect.height / 2;
      container.dispatchEvent(new PointerEvent('pointerdown', Object.assign({ clientX: startX, clientY: startY }, pointer)));
      container.dispatchEvent(new PointerEvent('pointermove', Object.assign({ clientX: startX, clientY: startY + 500 }, pointer)));
      container.dispatchEvent(new PointerEvent('pointerup', Object.assign({ clientX: startX, clientY: startY + 500 }, pointer)));
    })()`);
    await waitForLayout(browser, sessionId);
    const receipt = await finalGeometry(browser, sessionId);

    assert.ok(receipt.reserve > 0, JSON.stringify({ baseline, receipt }));
    assert.ok(receipt.reserve <= baseline.reserve + 1, JSON.stringify({ baseline, receipt }));
    assert.equal(receipt.semanticDockIntersectionArea, 0, JSON.stringify({ baseline, receipt }));
  } finally {
    await browser.close();
  }
});

test('zoomed camera restores its bounded desktop rail after crossing the mobile breakpoint', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture));
    const baseline = await finalGeometry(browser, sessionId);
    await evaluate(browser, sessionId, `(function () {
      var zoomIn = document.querySelector('[data-view="in"]');
      for (var index = 0; index < 8; index += 1) zoomIn.click();
    })()`);
    await waitForLayout(browser, sessionId);
    const zoomed = await finalGeometry(browser, sessionId);

    await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
      width: 720,
      height: 900,
      deviceScaleFactor: 1,
      mobile: false,
    }, sessionId);
    await waitForLayout(browser, sessionId);
    const mobile = await finalGeometry(browser, sessionId);
    assert.equal(mobile.reserve, 0, JSON.stringify(mobile));

    await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
      width: 1440,
      height: 900,
      deviceScaleFactor: 1,
      mobile: false,
    }, sessionId);
    await waitForLayout(browser, sessionId);
    const restored = await finalGeometry(browser, sessionId);

    assert.ok(Math.abs(restored.reserve - baseline.reserve) <= 1, JSON.stringify({ baseline, zoomed, restored }));
    assert.equal(restored.receiptReserve, restored.reserve, JSON.stringify(restored));
    assert.equal(restored.receiptEligible, true, JSON.stringify(restored));
    assert.ok(restored.scrollHeight <= restored.innerHeight, JSON.stringify(restored));
  } finally {
    await browser.close();
  }
});

test('localized multiline Legends remain clear across required viewports, themes, and presets', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  const viewports = [[1440, 900], [1600, 1000], [1920, 1080], [2048, 1320]];
  const cases = viewports.flatMap(([width, height]) => (
    ['light', 'dark'].flatMap((theme) => (
      ['classic', 'signal-flow', 'blueprint', 'editorial'].map((preset) => ({
        width,
        height,
        theme,
        preset,
      }))
    ))
  ));
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture), { width: 1920, height: 1080 });
    await evaluate(browser, sessionId, `(function () {
      var text = document.querySelector('[data-legend] text');
      var x = text.getAttribute('x') || '0';
      var namespace = 'http://www.w3.org/2000/svg';
      text.textContent = '';
      var first = document.createElementNS(namespace, 'tspan');
      first.setAttribute('x', x);
      first.textContent = '应用与运行时编排服务（本地化长标签）';
      var second = document.createElementNS(namespace, 'tspan');
      second.setAttribute('x', x);
      second.setAttribute('dy', '14');
      second.textContent = '第二行语义说明';
      text.appendChild(first);
      text.appendChild(second);
    })()`);

    for (const entry of cases) {
      await browser.cdp.send('Emulation.setDeviceMetricsOverride', {
        width: entry.width,
        height: entry.height,
        deviceScaleFactor: 1,
        mobile: false,
      }, sessionId);
      await evaluate(browser, sessionId, `(function () {
        var html = document.documentElement;
        var nav = document.querySelector('.diagram-nav');
        html.setAttribute('data-preset', ${JSON.stringify(entry.preset)});
        html.setAttribute('data-theme', ${JSON.stringify(entry.theme)});
        document.querySelector('[data-view="reset"]').click();
        nav.removeAttribute('style');
        window.dispatchEvent(new Event('resize'));
      })()`);
      await waitForLayout(browser, sessionId);
      let receipt = null;
      for (let attempt = 0; attempt < 3; attempt += 1) {
        await evaluate(browser, sessionId, `(function () {
          var container = document.querySelector('.diagram-container');
          var legend = document.querySelector('[data-legend]').getBoundingClientRect();
          var nav = document.querySelector('.diagram-nav');
          var containerRect = container.getBoundingClientRect();
          nav.style.right = '0';
          nav.style.left = '0';
          nav.style.bottom = Math.max(0, containerRect.bottom - legend.bottom) + 'px';
          nav.style.width = 'auto';
          window.dispatchEvent(new Event('resize'));
        })()`);
        await waitForLayout(browser, sessionId);
        receipt = await finalGeometry(browser, sessionId);
        if (receipt.reserve > 0) break;
      }
      assert.equal(receipt.legendDockIntersectionArea, 0, JSON.stringify({ ...entry, receipt }));
      assert.ok(receipt.reserve > 0, JSON.stringify({ ...entry, receipt }));
      assert.equal(receipt.dockStageIntersectionArea, 0, JSON.stringify({ ...entry, receipt }));
      assert.ok(receipt.stageGap >= 9, JSON.stringify({ ...entry, receipt }));
    }
  } finally {
    await browser.close();
  }
});

test('Semantic Lens and Radar protect the final Legend and Dock rectangles', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const sessionId = await load(browser, render('architecture', CASES.architecture), { width: 1440, height: 900 });
    await evaluate(browser, sessionId, `document.getElementById('btn-semantic-lens').click()`);
    await waitForLayout(browser, sessionId);
    let receipt = await finalGeometry(browser, sessionId);
    assert.equal(receipt.legendLensIntersectionArea, 0, JSON.stringify(receipt));
    assert.equal(receipt.navLensIntersectionArea, 0, JSON.stringify(receipt));

    await evaluate(browser, sessionId, `(function () {
      document.getElementById('btn-semantic-lens').click();
      document.getElementById('btn-overview-map').click();
    })()`);
    await waitForLayout(browser, sessionId);
    receipt = await finalGeometry(browser, sessionId);
    assert.equal(receipt.legendRadarIntersectionArea, 0, JSON.stringify(receipt));
    assert.equal(receipt.navRadarIntersectionArea, 0, JSON.stringify(receipt));
  } finally {
    await browser.close();
  }
});

test('Radar, Passport, Legend, and Dock remain mutually clear on desktop and narrow viewports', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    const artifact = render('architecture', CASES.architecture);
    for (const viewport of [
      { width: 1440, height: 900, label: 'desktop' },
      { width: 390, height: 600, label: 'narrow' },
    ]) {
      const sessionId = await load(browser, artifact, viewport);
      await evaluate(browser, sessionId, `(function () {
        var container = document.querySelector('.diagram-container');
        window.scrollTo(0, Math.max(0, container.offsetTop));
        Archify.focus.set('lb', { toggle: false });
        Archify.radar.open();
        window.dispatchEvent(new Event('resize'));
      })()`);
      await waitForLayout(browser, sessionId);
      const receipt = await finalGeometry(browser, sessionId);
      const message = viewport.label + ': ' + JSON.stringify(receipt);

      assert.equal(receipt.legendDockIntersectionArea, 0, message);
      assert.equal(receipt.legendPassportIntersectionArea, 0, message);
      assert.equal(receipt.navPassportIntersectionArea, 0, message);
      assert.equal(receipt.legendRadarIntersectionArea, 0, message);
      assert.equal(receipt.navRadarIntersectionArea, 0, message);
      assert.equal(receipt.radarPassportIntersectionArea, 0, message);
    }
  } finally {
    await browser.close();
  }
});

test('mobile, embed, and print keep zero reserve while hidden Legends retain the stage rail', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async () => {
  const browser = new ChromeVisualBrowser(chromePath);
  try {
    let sessionId = await load(browser, render('architecture', CASES.architecture), { width: 720, height: 900 });
    let receipt = await finalGeometry(browser, sessionId);
    assert.equal(receipt.reserve, 0, `mobile: ${JSON.stringify(receipt)}`);
    assert.equal(receipt.legendDockIntersectionArea, 0, `mobile: ${JSON.stringify(receipt)}`);

    sessionId = await load(browser, render('architecture', CASES.architecture), { query: '?embed=1' });
    receipt = await finalGeometry(browser, sessionId);
    assert.equal(receipt.reserve, 0, `embed: ${JSON.stringify(receipt)}`);

    sessionId = await load(browser, render('architecture', CASES.architecture));
    await evaluate(browser, sessionId, `(function () {
      document.querySelector('[data-legend]').hidden = true;
      window.dispatchEvent(new Event('resize'));
    })()`);
    await waitForLayout(browser, sessionId);
    receipt = await finalGeometry(browser, sessionId);
    assert.ok(receipt.reserve > 0, `hidden: ${JSON.stringify(receipt)}`);
    assert.ok(receipt.stageGap >= 9, `hidden: ${JSON.stringify(receipt)}`);

    await browser.cdp.send('Emulation.setEmulatedMedia', { media: 'print' }, sessionId);
    await waitForLayout(browser, sessionId);
    receipt = await finalGeometry(browser, sessionId);
    assert.equal(receipt.reserve, 0, `print: ${JSON.stringify(receipt)}`);
  } finally {
    await browser.close();
  }
});


test('Chrome Layout preserves scheduling, mode restoration and Reader handoffs', {
  skip: chromePath ? false : 'Set ARCHIFY_CHROME to run the real browser regression.',
}, async (t) => {
  const browser = new ChromeVisualBrowser(chromePath);
  t.after(() => browser.close());
  const evidence = process.env.ARCHIFY_CHROME_LAYOUT_EVIDENCE;
  if (evidence) fs.mkdirSync(evidence, { recursive: true });
  const observations = [];
  const file = render('architecture', CASES.architecture);
  const session = await browser.sessionPromise;
  const send = (method, params = {}) => browser.cdp.send(method, params, session);
  const run = (expression, awaitPromise = false) => evaluate(browser, session, expression, awaitPromise);
  await send('Page.addScriptToEvaluateOnNewDocument', {
    source: `window.chromeLayoutErrors = [];
      addEventListener('error', e => chromeLayoutErrors.push(e.message));
      addEventListener('unhandledrejection', e => chromeLayoutErrors.push(String(e.reason)));`,
  });
  async function resize(width, height = 900) {
    await send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: false });
    await waitForLayout(browser, session);
  }
  async function state(label) {
    // Read DOM first, without receipt(), measure() or public stability methods.
    // This ensures those methods cannot make a missed automatic event pass.
    const raw = await run(`(() => {
      const html = document.documentElement;
      const container = document.querySelector('.diagram-container');
      const svg = container.querySelector(':scope > svg');
      return {
        reserve: parseFloat(container.style.getPropertyValue('--archify-nav-reserve')) || 0,
        rootRail: html.getAttribute('data-nav-stage-rail'), rail: container.getAttribute('data-nav-stage-rail'),
        reader: html.getAttribute('data-reader-layout'), width: innerWidth,
        viewBox: svg.getAttribute('viewBox'),
        errors: chromeLayoutErrors,
        external: performance.getEntriesByType('resource').map(e => e.name).filter(n => /^https?:/.test(n))
      };
    })()`);
    assert.deepEqual(raw.errors, [], label);
    assert.deepEqual(raw.external, [], label);
    const geometry = await finalGeometry(browser, session);
    assert.equal(raw.reserve, geometry.receiptReserve, `${label}: observation must not repair stale state`);
    assert.equal(raw.rootRail, raw.reserve ? 'true' : null, label);
    assert.equal(raw.rail, raw.rootRail, label);
    observations.push({ label, ...raw, geometry });
    return { ...raw, geometry };
  }
  function zero(value) {
    assert.equal(value.reserve, 0);
    assert.equal(value.rail, null);
    assert.equal(value.rootRail, null);
  }
  function clearStage(value) {
    assert.equal(value.geometry.dockStageIntersectionArea, 0, JSON.stringify(value));
    assert.ok(value.geometry.stageGap >= 9, JSON.stringify(value));
  }
  function variant(name, source) {
    const original = fs.readFileSync(file, 'utf8');
    const html = original.replace('  <script>\n    var Archify = {};',
      () => `  <script>${source}</script>\n  <script>\n    var Archify = {};`);
    assert.notEqual(html, original);
    const output = path.join(tmp, `chrome-${name}.html`);
    fs.writeFileSync(output, html);
    return output;
  }
  try {
    await t.test('automatic resize crosses Chrome and Reader thresholds without reserve accumulation', async () => {
      await load(browser, file);
      const initial = await state('threshold-initial');
      for (const width of [719, 720, 721, 1023, 1024, 1025, 720, 1440]) {
        await resize(width);
        const current = await state(`threshold-${width}-${observations.length}`);
        assert.equal(current.geometry.receiptEligible, width > 720);
        assert.equal(current.reader, width >= 1024 ? 'adaptive' : null);
        assert.equal(current.viewBox, initial.viewBox);
        if (width <= 720) zero(current);
        else clearStage(current);
        if (width === 1440) assert.equal(current.reserve, initial.reserve);
      }
    });

    await t.test('automatic observer callbacks handle navigation size, visibility and content changes', async () => {
      await load(browser, file);
      const initial = await state('observer-initial');
      await run(`document.querySelector('.diagram-nav').style.display = 'none'`);
      await waitForLayout(browser, session);
      zero(await state('observer-nav-hidden'));
      await run(`document.querySelector('.diagram-nav').style.display = ''`);
      await waitForLayout(browser, session);
      const restored = await state('observer-nav-restored');
      assert.equal(restored.reserve, initial.reserve);
      await run(`document.querySelector('.diagram-nav').style.height = '120px'`);
      await waitForLayout(browser, session);
      const taller = await state('observer-nav-taller');
      assert.ok(taller.reserve > initial.reserve);
      clearStage(taller);
      // A legend mutation drives the existing MutationObserver. Font/preset
      // rules remain the production rules; no public layout method is invoked.
      await run(`document.querySelector('[data-legend]').setAttribute('transform', 'translate(0,-20)')`);
      await waitForLayout(browser, session);
      clearStage(await state('observer-legend-changed'));
      await run(`document.querySelector('.diagram-nav').style.height = ''; window.dispatchEvent(new Event('resize'))`);
      await waitForLayout(browser, session);
      assert.equal((await state('observer-returned')).reserve, initial.reserve);
      await run(`document.querySelector('.diagram-nav').style.bottom = '-200px'; window.dispatchEvent(new Event('resize'))`);
      await waitForLayout(browser, session);
      zero(await state('no-reserve-needed'));
    });

    await t.test('embed and print restore a zoomed rail, while Presentation remains eligible', async () => {
      for (const mode of ['embed', 'print']) {
        if (mode === 'print') await send('Emulation.setEmulatedMedia', { media: 'print' });
        await load(browser, file, { query: mode === 'embed' ? '?embed=1' : '' });
        zero(await state(`${mode}-initial`));
        await send('Emulation.setEmulatedMedia', { media: '' });
        await load(browser, file);
        const initial = await state(`${mode}-ordinary`);
        await run('Archify.view.zoomIn()');
        await waitForLayout(browser, session);
        for (let cycle = 0; cycle < 2; cycle += 1) {
          if (mode === 'embed') await run(`document.documentElement.setAttribute('data-embed', 'true')`);
          else {
            await send('Emulation.setEmulatedMedia', { media: 'print' });
            await run(`window.dispatchEvent(new Event('beforeprint'))`);
          }
          await waitForLayout(browser, session);
          zero(await state(`${mode}-enter-${cycle}`));
          if (mode === 'embed') await run(`document.documentElement.removeAttribute('data-embed')`);
          else {
            await send('Emulation.setEmulatedMedia', { media: '' });
            await run(`window.dispatchEvent(new Event('afterprint'))`);
          }
          await waitForLayout(browser, session);
          const restored = await state(`${mode}-return-${cycle}`);
          assert.equal(restored.reserve, initial.reserve);
          clearStage(restored);
        }
      }
      for (const theme of ['dark', 'light']) {
        await send('Emulation.setEmulatedMedia', { media: '', features: [
          { name: 'prefers-reduced-motion', value: theme === 'light' ? 'reduce' : 'no-preference' },
        ] });
        await load(browser, file, { query: `?theme=${theme}&present=1` });
        const present = await state(`presentation-${theme}`);
        assert.equal(present.geometry.receiptEligible, true);
        clearStage(present);
        if (evidence) {
          const shot = await send('Page.captureScreenshot', { format: 'png' });
          fs.writeFileSync(path.join(evidence, `presentation-${theme}.png`), Buffer.from(shot.data, 'base64'));
        }
        await run('Archify.presentation.exit()');
        await waitForLayout(browser, session);
        clearStage(await state(`presentation-return-${theme}`));
      }
      await send('Emulation.setEmulatedMedia', { features: [] });
    });

    await t.test('pending Reader probes coalesce and retain the rail when the camera changes', async () => {
      for (const reject of [false, true]) {
        await load(browser, file);
        const initial = await state(`probe-initial-${reject}`);
        assert.ok(initial.reserve > 0);
        const pending = await run(`(() => {
          const reader = Archify.readerLayout;
          const original = reader.whenStable;
          let release;
          const gate = new Promise((resolve, reject) => { release = ${reject} ? () => reject(new Error('test reader rejection')) : resolve; });
          reader.whenStable = () => gate;
          const first = Archify.viewerChromeLayout.reprobe();
          const second = Archify.viewerChromeLayout.reprobe();
          const measuring = Archify.viewerChromeLayout.measure();
          const reserve = document.querySelector('.diagram-container').style.getPropertyValue('--archify-nav-reserve');
          Archify.view.zoomIn();
          window.dispatchEvent(new Event('resize'));
          window.finishChromeProbe = async () => {
            reader.whenStable = original;
            release();
            const result = await first;
            delete window.finishChromeProbe;
            return result;
          };
          return { same: first === second, measuring, reserve };
        })()`);
        assert.deepEqual(pending, { same: true, measuring: null, reserve: '' });
        assert.equal(await run('finishChromeProbe()', true), true);
        await waitForLayout(browser, session);
        const restored = await state(`probe-restored-${reject}`);
        assert.equal(restored.reserve, initial.reserve);
        clearStage(restored);
        await run(`for (let i = 0; i < 20; i++) { Archify.viewerChromeLayout.schedule(); Archify.viewerChromeLayout.reprobe(); window.dispatchEvent(new Event('resize')); }`);
        await waitForLayout(browser, session);
        const repeated = await state(`probe-burst-${reject}`);
        assert.equal(repeated.reserve, initial.reserve);
        assert.deepEqual(repeated.geometry, restored.geometry);
      }
    });

    await t.test('font readiness and optional observers preserve their existing fallbacks', async () => {
      const noObservers = variant('no-observers', 'window.ResizeObserver = undefined; window.MutationObserver = undefined;');
      await load(browser, noObservers);
      const initial = await state('fallback-initial');
      await resize(720);
      zero(await state('fallback-mobile'));
      await resize(1440);
      assert.equal((await state('fallback-return')).reserve, initial.reserve);

      const delayedFonts = variant('fonts', `window.originalChromeFonts = document.fonts;
        Object.defineProperty(document, 'fonts', { configurable: true, value: { ready: new Promise(resolve => { window.releaseChromeFonts = resolve; }) } });`);
      // Bypass load's font/stability wait to observe the deliberately pending gate.
      const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
      await send('Page.navigate', { url: pathToFileURL(delayedFonts).href });
      await loaded;
      const result = await run(`(async () => {
        let resolved = false;
        const stable = Archify.viewerChromeLayout.whenStable().then(() => { resolved = true; });
        document.querySelector('.diagram-nav').style.height = '100px';
        await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
        const beforeReady = resolved;
        releaseChromeFonts();
        await stable;
        Object.defineProperty(document, 'fonts', { configurable: true, value: originalChromeFonts });
        return { beforeReady, resolved };
      })()`, true);
      assert.deepEqual(result, { beforeReady: false, resolved: true });
      await waitForLayout(browser, session);
      clearStage(await state('font-ready'));
    });
  } finally {
    if (evidence) fs.writeFileSync(path.join(evidence, 'observations.json'), JSON.stringify(observations, null, 2) + '\n');
  }
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/visual-check.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { EventEmitter } from 'node:events';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { PassThrough } from 'node:stream';
import { fileURLToPath } from 'node:url';

import {
  ChromeVisualBrowser,
  VISUAL_CHECK_VIEWPORTS,
  chromeVisualBrowserArgs,
  runVisualCheck,
  sidecarPaths,
} from '../bin/visual-check.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-visual-check-'));
const png = Buffer.from('89504e470d0a1a0a', 'hex');

function artifact(name = 'diagram.html') {
  const file = path.join(tmp, name);
  fs.writeFileSync(file, '<!doctype html><html><body>checked artifact</body></html>');
  return file;
}

function sha256(file) {
  return createHash('sha256').update(fs.readFileSync(file)).digest('hex');
}

function fakeBrowser({ overflowAt, unreadableAt, chromeCollisionAt, stageCollisionAt, stageGapAt, screenshotFailure } = {}) {
  const calls = [];
  return {
    calls,
    async inspect({ width, height, theme, screenshotPath }) {
      calls.push({ width, height, theme, screenshotPath });
      if (screenshotPath && screenshotFailure?.({ width, height, theme })) {
        throw new Error('synthetic screenshot failure');
      }
      if (screenshotPath) fs.writeFileSync(screenshotPath, png);
      const overflow = overflowAt?.({ width, height, theme }) || false;
      const unreadable = unreadableAt?.({ width, height, theme }) || false;
      const chromeCollision = chromeCollisionAt?.({ width, height, theme }) || false;
      const stageCollision = stageCollisionAt?.({ width, height, theme }) || false;
      const dockStageGap = stageGapAt?.({ width, height, theme }) ?? (stageCollision ? -12 : 10);
      const stageClearanceFailure = stageCollision || dockStageGap < 10;
      return {
        innerWidth: width,
        innerHeight: height,
        scrollWidth: width + (overflow ? 1 : 0),
        scrollHeight: height,
        resolvedTheme: theme,
        readerWidth: 960,
        diagramWidth: 930,
        viewBoxWidth: 1300,
        minimumProjectedNodeTextPx: unreadable ? 5.72 : 6.44,
        minimumProjectedNodeText: unreadable ? 'Compact node' : 'Readable node',
        minimumProjectedNodeTextDetail: unreadable ? 'primary' : 'context',
        hasLegend: true,
        hasNavigationDock: true,
        legendDockIntersectionArea: chromeCollision ? 42 : 0,
        dockStageIntersectionArea: stageCollision ? 84 : 0,
        dockStageGap,
        viewerChromeRequiredGap: 10,
        viewerChromeReserve: chromeCollision || stageClearanceFailure ? 0 : 44,
        viewerChromeActive: !chromeCollision && !stageClearanceFailure,
      };
    },
    async close() {},
  };
}

function fakeChromeChild() {
  const child = new EventEmitter();
  child.exitCode = null;
  child.signalCode = null;
  child.stderr = new PassThrough();
  child.stdio = [null, null, child.stderr, new PassThrough(), new PassThrough()];
  child.kill = (signal) => {
    child.signalCode = signal;
    queueMicrotask(() => {
      child.emit('exit', null, signal);
      child.emit('close', null, signal);
    });
    return true;
  };
  return child;
}

test('visual-check disables the Chrome sandbox only for root or an explicit environment opt-in', () => {
  const profileRoot = path.join(tmp, 'chrome-profile');
  const ordinary = chromeVisualBrowserArgs(profileRoot, { env: {}, getuid: () => 1001 });
  const optedIn = chromeVisualBrowserArgs(profileRoot, {
    env: { ARCHIFY_CHROME_NO_SANDBOX: '1' },
    getuid: () => 1001,
  });
  const root = chromeVisualBrowserArgs(profileRoot, { env: {}, getuid: () => 0 });

  assert.equal(ordinary.includes('--no-sandbox'), false);
  assert.equal(optedIn.includes('--no-sandbox'), true);
  assert.equal(root.includes('--no-sandbox'), true);
});

test('visual-check converts a Chrome DevTools pipe reset and captured stderr into a structured failure', async () => {
  const input = artifact('chrome-pipe-reset.html');
  const child = fakeChromeChild();

  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => {
      const browser = new ChromeVisualBrowser('/fake/chrome', {
        env: { ARCHIFY_CHROME_NO_SANDBOX: '1' },
        getuid: () => 1001,
        spawnImpl: () => child,
      });
      setImmediate(() => {
        child.stderr.write('Chrome sandbox initialization failed\n');
        const error = new Error('read ECONNRESET');
        error.code = 'ECONNRESET';
        child.stdio[4].emit('error', error);
      });
      return browser;
    },
  });

  assert.equal(result.exitCode, 1);
  assert.equal(result.receipt.status, 'fail');
  assert.match(result.receipt.error, /Chrome DevTools read pipe failed/);
  assert.match(result.receipt.error, /ECONNRESET/);
  assert.match(result.receipt.error, /Chrome sandbox initialization failed/);
  assert.equal(result.receipt.diagnostics[0]?.code, 'viewer/visual-check-runtime');
  assert.match(result.receipt.diagnostics[0]?.evidence?.reason || '', /ECONNRESET/);
  assert.equal(fs.existsSync(sidecarPaths(input).receipt), true);
});

test('visual-check reports Chrome early exit status and stderr without an uncaught exception', async () => {
  const input = artifact('chrome-early-exit.html');
  const child = fakeChromeChild();

  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => {
      const browser = new ChromeVisualBrowser('/fake/chrome', {
        env: { ARCHIFY_CHROME_NO_SANDBOX: '1' },
        getuid: () => 1001,
        spawnImpl: () => child,
      });
      setImmediate(() => {
        child.stderr.write('Chrome rejected its launch flags\n');
        child.exitCode = 23;
        child.emit('close', 23, null);
      });
      return browser;
    },
  });

  assert.equal(result.exitCode, 1);
  assert.equal(result.receipt.status, 'fail');
  assert.match(result.receipt.error, /Chrome DevTools process exit failed/);
  assert.match(result.receipt.error, /exit code 23/);
  assert.match(result.receipt.error, /Chrome rejected its launch flags/);
  assert.equal(result.receipt.diagnostics[0]?.code, 'viewer/visual-check-runtime');
});

test('visual-check records four containment viewports and four endpoint theme captures', async () => {
  const input = artifact('passing.html');
  const before = sha256(input);
  const browser = fakeBrowser();
  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => browser,
  });

  assert.equal(result.exitCode, 0);
  assert.equal(result.receipt.status, 'pass');
  assert.equal(result.receipt.evidenceKind, 'automated-browser');
  assert.deepEqual(result.receipt.diagnostics, []);
  assert.equal(result.receipt.visualReview, 'pending');
  assert.equal(result.receipt.viewerChrome.status, 'pass');
  assert.equal(result.receipt.containment.viewports.length, VISUAL_CHECK_VIEWPORTS.length);
  assert.equal(result.receipt.containment.viewports.every((entry) => entry.ok), true);
  assert.deepEqual(
    result.receipt.captures.screenshots.map(({ width, height, theme }) => [width, height, theme]),
    [
      [1440, 900, 'light'],
      [1440, 900, 'dark'],
      [2048, 1320, 'light'],
      [2048, 1320, 'dark'],
    ],
  );
  assert.equal(result.receipt.artifact.sha256, before);
  assert.equal(sha256(input), before, 'visual-check mutated the delivered artifact');

  const outputs = sidecarPaths(input);
  assert.equal(fs.existsSync(outputs.receipt), true);
  assert.equal(fs.existsSync(outputs.contactSheet), true);
  assert.equal(outputs.screenshots.every((entry) => fs.existsSync(entry.path)), true);
  const contactSheet = fs.readFileSync(outputs.contactSheet, 'utf8');
  assert.match(contactSheet, /Automated browser evidence/);
  assert.match(contactSheet, /perceptual visual review pending/);
  for (const screenshot of outputs.screenshots) {
    assert.match(contactSheet, new RegExp(path.basename(screenshot.path).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
    assert.doesNotMatch(contactSheet, new RegExp(screenshot.path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
  }
});

test('visual-check returns 1 and preserves evidence when any viewport overflows', async () => {
  const input = artifact('overflow.html');
  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => fakeBrowser({
      overflowAt: ({ width, theme }) => width === 1600 && theme === 'light',
    }),
  });

  assert.equal(result.exitCode, 1);
  assert.equal(result.receipt.status, 'fail');
  assert.equal(result.receipt.containment.status, 'fail');
  assert.deepEqual(
    result.receipt.containment.viewports.filter((entry) => !entry.ok).map((entry) => [entry.width, entry.height]),
    [[1600, 1000]],
  );
  const diagnostic = result.receipt.diagnostics.find(
    (entry) => entry.code === 'viewer/viewport-overflow',
  );
  assert.deepEqual(diagnostic?.subject, {
    artifact: input,
    viewport: { width: 1600, height: 1000, theme: 'light' },
  });
  assert.equal(diagnostic?.evidence?.scrollWidth, 1601);
  assert.equal(fs.existsSync(sidecarPaths(input).contactSheet), true);
});

test('visual-check returns 1 when the real reader projects node text below 6px', async () => {
  const input = artifact('unreadable.html');
  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => fakeBrowser({
      unreadableAt: ({ width, height, theme }) => width === 1440 && height === 900 && theme === 'light',
    }),
  });

  assert.equal(result.exitCode, 1);
  assert.equal(result.receipt.status, 'fail');
  assert.equal(result.receipt.readability.status, 'fail');
  const desktop = result.receipt.readability.viewports.find(
    (entry) => entry.width === 1440 && entry.height === 900,
  );
  assert.equal(desktop?.diagramWidth, 930);
  assert.equal(desktop?.minimumProjectedNodeText, 'Compact node');
  assert.equal(desktop?.minimumProjectedNodeTextDetail, 'primary');
  assert.equal(desktop?.readabilityOk, false);
  const diagnostic = result.receipt.diagnostics.find(
    (entry) => entry.code === 'viewer/projected-text-readability',
  );
  assert.equal(diagnostic?.evidence?.text, 'Compact node');
  assert.equal(diagnostic?.evidence?.minimumProjectedNodeTextPx, 5.72);
  assert.equal(diagnostic?.evidence?.minimumRequiredNodeTextPx, 6);
});

test('visual-check returns 1 when the navigation dock obscures the SVG legend', async () => {
  const input = artifact('viewer-chrome-collision.html');
  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => fakeBrowser({
      chromeCollisionAt: ({ width, height, theme }) => (
        width === 1920 && height === 1080 && theme === 'light'
      ),
    }),
  });

  assert.equal(result.exitCode, 1);
  assert.equal(result.receipt.status, 'fail');
  assert.equal(result.receipt.viewerChrome.status, 'fail');
  const desktop = result.receipt.viewerChrome.viewports.find(
    (entry) => entry.width === 1920 && entry.height === 1080,
  );
  assert.equal(desktop?.legendDockIntersectionArea, 42);
  assert.equal(desktop?.viewerChromeOk, false);
  const diagnostic = result.receipt.diagnostics.find(
    (entry) => entry.code === 'viewer/chrome-legend-clearance',
  );
  assert.equal(diagnostic?.evidence?.legendDockIntersectionArea, 42);
});

test('visual-check returns 1 when the navigation dock enters the SVG stage', async () => {
  const input = artifact('viewer-stage-collision.html');
  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => fakeBrowser({
      stageCollisionAt: ({ width, height, theme }) => (
        width === 1920 && height === 1080 && theme === 'light'
      ),
    }),
  });

  assert.equal(result.exitCode, 1);
  assert.equal(result.receipt.status, 'fail');
  assert.equal(result.receipt.viewerChrome.status, 'fail');
  const desktop = result.receipt.viewerChrome.viewports.find(
    (entry) => entry.width === 1920 && entry.height === 1080,
  );
  assert.equal(desktop?.dockStageIntersectionArea, 84);
  assert.equal(desktop?.dockStageGap, -12);
  assert.equal(desktop?.requiredDockStageGap, 10);
  assert.equal(desktop?.viewerChromeStageOk, false);
  assert.equal(desktop?.viewerChromeOk, false);
  const diagnostic = result.receipt.diagnostics.find(
    (entry) => entry.code === 'viewer/chrome-stage-clearance',
  );
  assert.deepEqual(diagnostic?.subject, {
    artifact: input,
    viewport: { width: 1920, height: 1080, theme: 'light' },
  });
  assert.deepEqual(diagnostic?.evidence, {
    dockStageIntersectionArea: 84,
    dockStageGap: -12,
    requiredDockStageGap: 10,
  });
  assert.match(diagnostic?.message || '', /enters the protected SVG stage/);
  assert.ok(diagnostic?.supportedFixes.some((fix) => fix.includes('dockStageGap')));
  assert.equal(diagnostic?.supportedFixes.some((fix) => fix.includes('regenerate')), false);
});

test('visual-check describes insufficient stage clearance without claiming an overlap', async () => {
  const input = artifact('viewer-stage-low-gap.html');
  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => fakeBrowser({
      stageGapAt: ({ width, height, theme }) => (
        width === 1920 && height === 1080 && theme === 'light' ? 5 : 10
      ),
    }),
  });

  assert.equal(result.exitCode, 1);
  const diagnostic = result.receipt.diagnostics.find(
    (entry) => entry.code === 'viewer/chrome-stage-clearance',
  );
  assert.equal(diagnostic?.evidence?.dockStageIntersectionArea, 0);
  assert.equal(diagnostic?.evidence?.dockStageGap, 5);
  assert.match(diagnostic?.message || '', /clearance.*below the required gap/i);
  assert.doesNotMatch(diagnostic?.message || '', /enters/i);
});

test('visual-check returns 1 and removes misleading capture sidecars on screenshot failure', async () => {
  const input = artifact('capture-failure.html');
  const outputs = sidecarPaths(input);
  fs.writeFileSync(outputs.contactSheet, 'stale');
  for (const screenshot of outputs.screenshots) fs.writeFileSync(screenshot.path, png);

  const result = await runVisualCheck({
    artifactPath: input,
    chromePath: '/fake/chrome',
    browserFactory: async () => fakeBrowser({
      screenshotFailure: ({ theme }) => theme === 'dark',
    }),
  });

  assert.equal(result.exitCode, 1);
  assert.equal(result.receipt.status, 'fail');
  assert.equal(result.receipt.captures.status, 'fail');
  assert.match(result.receipt.error, /synthetic screenshot failure/);
  assert.equal(result.receipt.diagnostics[0]?.code, 'viewer/visual-check-runtime');
  assert.match(result.receipt.diagnostics[0]?.evidence?.reason || '', /synthetic screenshot failure/);
  assert.equal(fs.existsSync(outputs.contactSheet), false);
  assert.equal(outputs.screenshots.some((entry) => fs.existsSync(entry.path)), false);
  assert.equal(fs.existsSync(outputs.receipt), true);
});

test('visual-check returns 2 with a truthful skipped receipt when Chrome is unavailable', async () => {
  const input = artifact('no-chrome.html');
  const result = await runVisualCheck({
    artifactPath: input,
    resolveChrome: () => null,
  });

  assert.equal(result.exitCode, 2);
  assert.equal(result.receipt.status, 'skipped');
  assert.equal(result.receipt.containment.status, 'skipped');
  assert.equal(result.receipt.viewerChrome.status, 'skipped');
  assert.equal(result.receipt.captures.status, 'skipped');
  assert.equal(result.receipt.visualReview, 'pending');
  assert.equal(result.receipt.diagnostics[0]?.code, 'viewer/chrome-unavailable');
  assert.ok(result.receipt.diagnostics[0]?.supportedFixes.some((fix) => fix.includes('ARCHIFY_CHROME')));
  assert.equal(fs.existsSync(sidecarPaths(input).receipt), true);
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/webm-artifact.smoke.mjs

```js
import assert from 'node:assert/strict';
import { execFileSync, spawn } from 'node:child_process';
import { once } from 'node:events';
import fs from 'node:fs';
import net from 'node:net';
import os from 'node:os';
import path from 'node:path';
import { pathToFileURL, fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-webm-artifact-'));
const externalReachSource = process.env.ARCHIFY_REACH_CARD_SOURCE
  ? path.resolve(process.env.ARCHIFY_REACH_CARD_SOURCE)
  : '';
const externalReachOutput = process.env.ARCHIFY_REACH_CARD_OUTPUT
  ? path.resolve(process.env.ARCHIFY_REACH_CARD_OUTPUT)
  : '';
assert.equal(Boolean(externalReachSource), Boolean(externalReachOutput), 'ARCHIFY_REACH_CARD_SOURCE and ARCHIFY_REACH_CARD_OUTPUT must be set together');

function executable(candidates) {
  for (const candidate of candidates) {
    if (!candidate) continue;
    if (candidate.includes(path.sep)) {
      if (fs.existsSync(candidate)) return candidate;
      continue;
    }
    try {
      return execFileSync('sh', ['-c', `command -v "$1"`, 'archify-which', candidate], { encoding: 'utf8' }).trim();
    } catch (_) {
      // Try the next platform-specific name.
    }
  }
  return '';
}

const chrome = executable([
  process.env.ARCHIFY_CHROME,
  'google-chrome',
  'google-chrome-stable',
  'chromium',
  'chromium-browser',
  '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
]);
const ffmpeg = executable([process.env.ARCHIFY_FFMPEG, 'ffmpeg']);

assert.ok(chrome, 'Chrome/Chromium is required for the WebM artifact smoke test (or set ARCHIFY_CHROME)');
assert.ok(ffmpeg, 'ffmpeg is required for the WebM artifact smoke test (or set ARCHIFY_FFMPEG)');

const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
source.meta.animation = 'trace';
source.meta.visual_preset = 'signal-flow';

const input = path.join(tmp, 'motion.architecture.json');
const output = path.join(tmp, 'motion.html');
fs.writeFileSync(input, JSON.stringify(source));
execFileSync(process.execPath, [
  path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
  input,
  output,
], { stdio: ['ignore', 'ignore', 'pipe'] });

const sequenceOutput = path.join(tmp, 'sequence.html');
execFileSync(process.execPath, [
  path.join(skillRoot, 'renderers/sequence/render-sequence.mjs'),
  path.join(skillRoot, 'examples/cache-miss-request.sequence.json'),
  sequenceOutput,
], { stdio: ['ignore', 'ignore', 'pipe'] });

const routeOutputs = {
  architecture: output,
  sequence: sequenceOutput,
};
for (const [mode, example] of Object.entries({
  workflow: 'agent-tool-call.workflow.json',
  dataflow: 'product-analytics.dataflow.json',
  lifecycle: 'agent-run.lifecycle.json',
})) {
  const rendered = path.join(tmp, `${mode}.html`);
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    path.join(skillRoot, 'examples', example),
    rendered,
  ], { stdio: ['ignore', 'ignore', 'pipe'] });
  routeOutputs[mode] = rendered;
}

function renderLegendFixture(mode, name, document) {
  const inputPath = path.join(tmp, `${name}.${mode}.json`);
  const outputPath = path.join(tmp, `${name}.${mode}.html`);
  fs.writeFileSync(inputPath, JSON.stringify(document));
  execFileSync(process.execPath, [
    path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`),
    inputPath,
    outputPath,
  ], { stdio: ['ignore', 'ignore', 'pipe'] });
  return outputPath;
}

const legendOutputs = {
  dataflow: renderLegendFixture('dataflow', 'issue-52-default-flow', {
    schema_version: 1,
    diagram_type: 'dataflow',
    meta: { title: 'Default Flow With Store' },
    stages: [{ label: 'Input' }, { label: 'Output' }],
    nodes: [
      { id: 'input', type: 'backend', label: 'Input', stage: 0, row: 0 },
      { id: 'output', type: 'database', label: 'Output Store', stage: 1, row: 0 },
    ],
    flows: [{ from: 'input', to: 'output', label: 'request', route: 'straight' }],
  }),
  lifecycle: renderLegendFixture('lifecycle', 'issue-52-no-waiting', {
    schema_version: 1,
    diagram_type: 'lifecycle',
    meta: { title: 'No Waiting or Failure', viewBox: [720, 566] },
    lanes: [{ id: 'main', label: 'Lifecycle' }],
    states: [
      { id: 'started', type: 'start', label: 'Started', lane: 'main', col: 0 },
      { id: 'running', type: 'active', label: 'Running', lane: 'main', col: 1 },
      { id: 'completed', type: 'success', label: 'Completed', lane: 'main', col: 2 },
    ],
    transitions: [{ from: 'started', to: 'running' }, { from: 'running', to: 'completed' }],
  }),
  custom: renderLegendFixture('architecture', 'issue-52-custom-label', {
    schema_version: 1,
    diagram_type: 'architecture',
    meta: {
      title: 'Custom Legend Label',
      viewBox: [720, 420],
      legend: {
        entries: {
          frontend: { label: 'Reader <UI> & ops' },
          external: { label: 'Future integration', visible: true },
        },
      },
      views: [{ id: 'main', label: 'Main', focus: ['ui', 'store'] }],
    },
    components: [
      { id: 'ui', type: 'frontend', label: 'UI', pos: [60, 90] },
      { id: 'store', type: 'database', label: 'Store', pos: [300, 90] },
    ],
    connections: [],
  }),
  hidden: renderLegendFixture('dataflow', 'issue-52-hidden', {
    schema_version: 1,
    diagram_type: 'dataflow',
    meta: { title: 'Hidden Legend', legend: { mode: 'hidden', entries: { database: { visible: true } } } },
    stages: [{ label: 'Input' }, { label: 'Output' }],
    nodes: [
      { id: 'input', type: 'backend', label: 'Input', stage: 0, row: 0 },
      { id: 'output', type: 'backend', label: 'Output', stage: 1, row: 0 },
    ],
    flows: [{ from: 'input', to: 'output', label: 'request', route: 'straight' }],
  }),
};

const parallelSource = JSON.parse(JSON.stringify(source));
parallelSource.meta.title = 'Parallel Route Identity';
parallelSource.connections[0].label = '';
parallelSource.connections.splice(1, 0, {
  id: 'users-to-cdn-alternate',
  from: 'users',
  to: 'cdn',
  label: '',
});
const parallelInput = path.join(tmp, 'parallel.architecture.json');
const parallelOutput = path.join(tmp, 'parallel.html');
fs.writeFileSync(parallelInput, JSON.stringify(parallelSource));
execFileSync(process.execPath, [
  path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
  parallelInput,
  parallelOutput,
], { stdio: ['ignore', 'ignore', 'pipe'] });

const specialSourceLabel = '入口 <script>window.__routeLabelExecuted = true</script> 汉字 🚀 with an intentionally very long endpoint label';
const specialTargetLabel = '终点服务 ✅ emoji + CJK + a second deliberately long endpoint label for header fitting';
const specialComponents = Array.from({ length: 11 }, (_, index) => ({
  id: index === 0 ? 'Route_Source-01' : index === 10 ? 'Route_Target-10' : `route_step-${index}`,
  type: index === 0 ? 'external' : index === 10 ? 'database' : 'backend',
  label: index === 0 ? specialSourceLabel : index === 10 ? specialTargetLabel : `步骤 ${index} · service_${index} ⚙️`,
  sublabel: `hop ${index}`,
  pos: [40 + index * 820, 280],
  size: [index === 0 || index === 10 ? 780 : 220, 72],
}));
const specialRouteSource = {
  schema_version: 1,
  diagram_type: 'architecture',
  meta: {
    title: '多语言 Route Share Card 🚀 with a deliberately long original diagram title that must fit safely',
    subtitle: 'Ten exact authored hops',
    animation: 'trace',
  },
  components: specialComponents,
  boundaries: [],
  connections: specialComponents.slice(0, -1).map((component, index) => ({
    id: `route_edge-${index + 1}`,
    from: component.id,
    to: specialComponents[index + 1].id,
    label: '',
  })),
  cards: [],
};
const specialRouteInput = path.join(tmp, 'special-route.architecture.json');
const specialRouteOutput = path.join(tmp, 'special-route.html');
fs.writeFileSync(specialRouteInput, JSON.stringify(specialRouteSource));
execFileSync(process.execPath, [
  path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'),
  specialRouteInput,
  specialRouteOutput,
], { stdio: ['ignore', 'ignore', 'pipe'] });

assert.equal(typeof WebSocket, 'function', 'Node.js 22+ is required for the Chrome DevTools smoke harness');

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function waitForExit(child, timeoutMs) {
  if (child.exitCode !== null || child.signalCode !== null) return true;
  return Promise.race([
    once(child, 'exit').then(() => true),
    delay(timeoutMs).then(() => false),
  ]);
}

async function removeTempTree(directory) {
  const transientCodes = new Set(['EBUSY', 'ENOTEMPTY', 'EPERM']);
  let lastError;
  for (let attempt = 0; attempt < 8; attempt += 1) {
    try {
      fs.rmSync(directory, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
      return;
    } catch (error) {
      if (!transientCodes.has(error?.code)) throw error;
      lastError = error;
      await delay(100 * (attempt + 1));
    }
  }
  console.warn(`warning: temporary Chrome profile cleanup deferred (${lastError?.code || 'unknown'}): ${directory}`);
}

async function withTimeout(promise, ms, label) {
  let timer;
  try {
    return await Promise.race([
      promise,
      new Promise((_, reject) => {
        timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
      }),
    ]);
  } finally {
    clearTimeout(timer);
  }
}

async function freePort() {
  const server = net.createServer();
  await new Promise((resolve, reject) => {
    server.once('error', reject);
    server.listen(0, '127.0.0.1', resolve);
  });
  const port = server.address().port;
  await new Promise((resolve) => server.close(resolve));
  return port;
}

async function devtoolsEndpoint(port, chromeProcess, diagnostics) {
  for (let attempt = 0; attempt < 100; attempt += 1) {
    try {
      const response = await fetch(`http://127.0.0.1:${port}/json/version`);
      if (response.ok) return (await response.json()).webSocketDebuggerUrl;
    } catch (_) {
      // Chrome may need a moment to bind the debugging port.
    }
    if (chromeProcess.exitCode !== null) break;
    await delay(50);
  }
  const stderr = diagnostics().trim();
  const exit = chromeProcess.exitCode === null ? 'still running' : `exited with code ${chromeProcess.exitCode}`;
  throw new Error(`Chrome did not expose a DevTools endpoint (${exit})${stderr ? `:\n${stderr}` : ''}`);
}

async function connectCdp(webSocketUrl) {
  const socket = new WebSocket(webSocketUrl);
  await new Promise((resolve, reject) => {
    socket.addEventListener('open', resolve, { once: true });
    socket.addEventListener('error', reject, { once: true });
  });
  let nextId = 0;
  const pending = new Map();
  socket.addEventListener('message', (event) => {
    const message = JSON.parse(String(event.data));
    if (!message.id || !pending.has(message.id)) return;
    const request = pending.get(message.id);
    pending.delete(message.id);
    if (message.error) request.reject(new Error(message.error.message));
    else request.resolve(message.result);
  });
  socket.addEventListener('close', () => {
    for (const request of pending.values()) request.reject(new Error('Chrome DevTools connection closed'));
    pending.clear();
  });
  return {
    socket,
    send(method, params = {}, sessionId) {
      const id = ++nextId;
      return new Promise((resolve, reject) => {
        pending.set(id, { resolve, reject });
        socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }));
      });
    },
  };
}

async function evaluate(cdp, sessionId, expression, awaitPromise = false) {
  const response = await cdp.send('Runtime.evaluate', {
    expression,
    awaitPromise,
    returnByValue: true,
  }, sessionId);
  if (response.exceptionDetails) {
    throw new Error(response.exceptionDetails.exception?.description || response.exceptionDetails.text || 'browser evaluation failed');
  }
  return response.result?.value;
}

const port = await freePort();
let chromeStderr = '';
const chromeProcess = spawn(chrome, [
  '--headless=new',
  '--disable-gpu',
  '--disable-dev-shm-usage',
  '--no-sandbox',
  '--allow-file-access-from-files',
  '--autoplay-policy=no-user-gesture-required',
  '--log-level=3',
  `--user-data-dir=${path.join(tmp, 'chrome-profile')}`,
  '--remote-debugging-address=127.0.0.1',
  `--remote-debugging-port=${port}`,
  'about:blank',
], { stdio: ['ignore', 'ignore', 'pipe'] });
chromeProcess.stderr.setEncoding('utf8');
chromeProcess.stderr.on('data', (chunk) => {
  chromeStderr = `${chromeStderr}${chunk}`.slice(-64 * 1024);
});

let cdp;
let targetId;

try {
  cdp = await connectCdp(await devtoolsEndpoint(port, chromeProcess, () => chromeStderr));
  ({ targetId } = await cdp.send('Target.createTarget', { url: 'about:blank' }));
  await cdp.send('Target.activateTarget', { targetId });
  const { sessionId } = await cdp.send('Target.attachToTarget', { targetId, flatten: true });
  await cdp.send('Page.enable', {}, sessionId);
  await cdp.send('Runtime.enable', {}, sessionId);

  async function navigateReady(file, condition, label) {
    const url = file instanceof URL ? file.href : pathToFileURL(file).href;
    await cdp.send('Page.navigate', { url }, sessionId);
    await cdp.send('Page.bringToFront', {}, sessionId);
    await cdp.send('Emulation.setFocusEmulationEnabled', { enabled: true }, sessionId);
    let ready = false;
    for (let attempt = 0; attempt < 100 && !ready; attempt += 1) {
      ready = await evaluate(cdp, sessionId, `document.readyState === "complete" && (${condition})`);
      if (!ready) await delay(50);
    }
    assert.equal(ready, true, `${label} did not expose its browser export surface`);
  }

  async function verifyResolvedLegendContract(outputs) {
    async function inspectKinds(file, expectedKinds, theme) {
      const url = new URL(pathToFileURL(file).href);
      url.searchParams.set('theme', theme);
      await navigateReady(url, '!!(window.Archify && Archify.semanticLens)', `legend ${theme}`);
      const result = await evaluate(cdp, sessionId, `(() => {
        var svg = document.querySelector('.diagram-container > svg');
        var entries = Array.from(svg.querySelectorAll('[data-legend-semantic-kind]'));
        var vb = svg.viewBox.baseVal;
        return {
          theme: document.documentElement.getAttribute('data-theme'),
          kinds: entries.map(function (entry) { return entry.getAttribute('data-legend-semantic-kind'); }),
          bridge: !!svg.querySelector('[data-legend-bridge]'),
          roles: entries.map(function (entry) { return entry.getAttribute('role'); }),
          aria: entries.map(function (entry) { return entry.getAttribute('aria-label'); }),
          counts: entries.map(function (entry) { return entry.getAttribute('data-legend-count'); }),
          tabStops: entries.filter(function (entry) { return entry.getAttribute('tabindex') === '0'; }).length,
          inside: entries.every(function (entry) {
            var box = entry.getBBox();
            return box.x >= vb.x && box.y >= vb.y && box.x + box.width <= vb.x + vb.width && box.y + box.height <= vb.y + vb.height;
          })
        };
      })()`);
      assert.equal(result.theme, theme);
      assert.deepEqual(result.kinds, expectedKinds);
      assert.equal(result.inside, true);
      return result;
    }

    for (const theme of ['dark', 'light']) {
      const dataflow = await inspectKinds(outputs.dataflow, ['database', 'default'], theme);
      assert.equal(dataflow.bridge, true);
      assert.deepEqual(dataflow.roles, ['button', null]);
      assert.deepEqual(dataflow.aria, ['Inspect data store, 1 node', null]);
      assert.deepEqual(dataflow.counts, ['1', null]);
      assert.equal(dataflow.tabStops, 1);
      const lifecycle = await inspectKinds(outputs.lifecycle, ['start', 'active', 'success'], theme);
      assert.equal(lifecycle.bridge, true);
    }

    await navigateReady(outputs.dataflow, '!!(window.Archify && Archify.semanticLens && Archify.exportMenu)', 'Dataflow database legend runtime');
    const databaseRuntime = await evaluate(cdp, sessionId, String.raw`(async function () {
      var entry = document.querySelector('[data-legend-kind="database"]');
      entry.focus();
      entry.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
      var originalCreateObjectURL = URL.createObjectURL;
      var originalAnchorClick = HTMLAnchorElement.prototype.click;
      var captured;
      URL.createObjectURL = function (blob) {
        captured = blob.text();
        return 'blob:archify-dataflow-legend-smoke';
      };
      HTMLAnchorElement.prototype.click = function () {};
      try {
        await Archify.exportMenu.run('svg');
        var exportedText = await captured;
        var exported = new DOMParser().parseFromString(exportedText, 'image/svg+xml').documentElement;
        return {
          selected: Archify.semanticLens.active(),
          lensOpen: Archify.semanticLens.isOpen(),
          exportedKinds: Array.from(exported.querySelectorAll('[data-legend-semantic-kind]')).map(function (item) { return item.getAttribute('data-legend-semantic-kind'); }),
          exportedBridgeResidue: exported.querySelectorAll('[data-legend-bridge], [data-legend-kind], [data-legend-label], [data-legend-count], [data-legend-bridge-runtime]').length
        };
      } finally {
        URL.createObjectURL = originalCreateObjectURL;
        HTMLAnchorElement.prototype.click = originalAnchorClick;
      }
    })()`, true);
    assert.deepEqual(databaseRuntime.selected, ['database']);
    assert.equal(databaseRuntime.lensOpen, true);
    assert.deepEqual(databaseRuntime.exportedKinds, ['database', 'default']);
    assert.equal(databaseRuntime.exportedBridgeResidue, 0);

    await navigateReady(outputs.custom, '!!(window.Archify && Archify.semanticLens && Archify.exportMenu)', 'custom legend runtime');
    const runtime = await evaluate(cdp, sessionId, String.raw`(async function () {
      var svg = document.querySelector('.diagram-container > svg');
      var entries = Array.from(svg.querySelectorAll('[data-legend-semantic-kind]'));
      var interactive = entries.filter(function (entry) { return entry.hasAttribute('data-legend-kind'); });
      var first = interactive[0];
      var second = interactive[1];
      first.focus();
      first.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
      var arrowMoved = document.activeElement === second;
      second.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
      var selected = Archify.semanticLens.active();
      var guidedActivated = Archify.guidedViews.activate('main', { updateUrl: false });
      var guidedActive = Archify.guidedViews.active();
      var visualMatrix = [];
      for (var preset of ['classic', 'signal-flow', 'blueprint', 'editorial']) {
        if (!Archify.preset.apply(preset)) throw new Error('could not apply preset ' + preset);
        for (var theme of ['dark', 'light']) {
          document.documentElement.setAttribute('data-theme', theme);
          visualMatrix.push({
            preset: preset,
            theme: theme,
            kinds: entries.map(function (entry) { return entry.getAttribute('data-legend-semantic-kind'); }),
            labels: entries.map(function (entry) { return entry.querySelector('text').textContent; })
          });
        }
      }

      var originalCreateObjectURL = URL.createObjectURL;
      var originalAnchorClick = HTMLAnchorElement.prototype.click;
      var captured;
      URL.createObjectURL = function (blob) {
        captured = blob.text();
        return 'blob:archify-legend-smoke';
      };
      HTMLAnchorElement.prototype.click = function () {};
      try {
        await Archify.exportMenu.run('svg');
        var exportedText = await captured;
        var exported = new DOMParser().parseFromString(exportedText, 'image/svg+xml').documentElement;
        return {
          labels: interactive.map(function (entry) { return entry.getAttribute('aria-label'); }),
          roles: entries.map(function (entry) { return entry.getAttribute('role'); }),
          counts: interactive.map(function (entry) { return entry.getAttribute('data-legend-count'); }),
          tabStops: interactive.filter(function (entry) { return entry.getAttribute('tabindex') === '0'; }).length,
          arrowMoved: arrowMoved,
          selected: selected,
          lensOpen: Archify.semanticLens.isOpen(),
          guidedActivated: guidedActivated,
          guidedActive: guidedActive,
          visualMatrix: visualMatrix,
          forcedUnusedInteractive: entries.at(-1).hasAttribute('data-legend-kind'),
          exportedKinds: Array.from(exported.querySelectorAll('[data-legend-semantic-kind]')).map(function (entry) { return entry.getAttribute('data-legend-semantic-kind'); }),
          exportedBridgeResidue: exported.querySelectorAll('[data-legend-bridge], [data-legend-kind], [data-legend-label], [data-legend-count], [data-legend-bridge-runtime]').length,
          exportedLabels: Array.from(exported.querySelectorAll('[data-legend-semantic-kind] text')).map(function (text) { return text.textContent; })
        };
      } finally {
        URL.createObjectURL = originalCreateObjectURL;
        HTMLAnchorElement.prototype.click = originalAnchorClick;
      }
    })()`, true);
    assert.deepEqual(runtime.labels, ['Inspect Reader <UI> & ops, 1 node', 'Inspect Database, 1 node']);
    assert.deepEqual(runtime.roles, ['button', 'button', null]);
    assert.deepEqual(runtime.counts, ['1', '1']);
    assert.equal(runtime.tabStops, 1);
    assert.equal(runtime.arrowMoved, true);
    assert.deepEqual(runtime.selected, ['database']);
    assert.equal(runtime.lensOpen, false);
    assert.equal(runtime.guidedActivated, true);
    assert.equal(runtime.guidedActive, 'main');
    assert.equal(runtime.visualMatrix.length, 8);
    for (const entry of runtime.visualMatrix) {
      assert.deepEqual(entry.kinds, ['frontend', 'database', 'external']);
      assert.deepEqual(entry.labels, ['Reader <UI> & ops', 'Database', 'Future integration']);
    }
    assert.equal(runtime.forcedUnusedInteractive, false);
    assert.deepEqual(runtime.exportedKinds, ['frontend', 'database', 'external']);
    assert.equal(runtime.exportedBridgeResidue, 0);
    assert.deepEqual(runtime.exportedLabels, ['Reader <UI> & ops', 'Database', 'Future integration']);

    await cdp.send('Emulation.setEmulatedMedia', { media: 'print' }, sessionId);
    const printState = await evaluate(cdp, sessionId, `(() => ({
      legendDisplay: getComputedStyle(document.querySelector('[data-legend]')).display,
      runtimeBadgesHidden: Array.from(document.querySelectorAll('[data-legend-bridge-runtime]')).every(function (entry) { return getComputedStyle(entry).display === 'none'; })
    }))()`);
    assert.notEqual(printState.legendDisplay, 'none');
    assert.equal(printState.runtimeBadgesHidden, true);
    await cdp.send('Emulation.setEmulatedMedia', { media: '' }, sessionId);

    const embedUrl = new URL(pathToFileURL(outputs.custom).href);
    embedUrl.searchParams.set('embed', '1');
    await navigateReady(embedUrl, '!!(window.Archify && Archify.semanticLens)', 'embedded legend');
    const embed = await evaluate(cdp, sessionId, `(() => ({
      roles: document.querySelectorAll('[data-legend-kind][role]').length,
      runtime: document.querySelectorAll('[data-legend-bridge-runtime]').length,
      kinds: Array.from(document.querySelectorAll('[data-legend-semantic-kind]')).map(function (entry) { return entry.getAttribute('data-legend-semantic-kind'); })
    }))()`);
    assert.deepEqual(embed, { roles: 0, runtime: 0, kinds: ['frontend', 'database', 'external'] });

    await navigateReady(outputs.hidden, '!!(window.Archify && Archify.semanticLens)', 'hidden legend');
    const hidden = await evaluate(cdp, sessionId, `(() => ({
      root: !!document.querySelector('[data-legend]'),
      bridge: !!document.querySelector('[data-legend-bridge]'),
      title: Array.from(document.querySelectorAll('.diagram-container svg text')).some(function (text) { return text.textContent.trim() === 'Legend'; })
    }))()`);
    assert.deepEqual(hidden, { root: false, bridge: false, title: false });
    console.log('ok legend runtime: labels, counts, keyboard, export, print, embed, hidden, and dual themes');
  }

  async function verifySemanticPassportDismissal(file) {
    await navigateReady(file, '!!(window.Archify && Archify.focus && document.querySelector("#btn-focus-clear"))', 'Semantic Passport dismissal');
    const result = await evaluate(cdp, sessionId, `(() => {
      var chip = document.querySelector('#focus-chip');
      var close = document.querySelector('#btn-focus-clear');
      var container = document.querySelector('.diagram-container');
      var svg = container.querySelector(':scope > svg');
      var origin = svg.querySelector('[data-node-id="clients"]');
      var neighbor = svg.querySelector('[data-node-id]:not([data-node-id="clients"])');
      if (!origin || !neighbor) return { ok: false, error: 'missing smoke-test nodes' };
      function state() {
        return { hidden: chip.hidden, active: Archify.focus.active() };
      }

      Archify.focus.set('clients', { toggle: false, updateUrl: false });
      var cardRect = chip.getBoundingClientRect();
      var closeRect = close.getBoundingClientRect();
      var layout = {
        topGap: Math.round(closeRect.top - cardRect.top),
        rightGap: Math.round(cardRect.right - closeRect.right),
        label: close.getAttribute('aria-label'),
        title: close.getAttribute('title'),
        text: close.textContent.trim()
      };
      close.click();
      var afterClose = state();
      var restoredFocus = document.activeElement && document.activeElement.getAttribute('data-node-id');

      Archify.focus.set('clients', { toggle: false, updateUrl: false });
      chip.querySelector('.relationship-lens-head').dispatchEvent(new MouseEvent('click', { bubbles: true }));
      var afterInside = state();
      container.dispatchEvent(new MouseEvent('click', { bubbles: true }));
      var afterOutside = state();

      Archify.focus.set('clients', { toggle: false, updateUrl: false });
      neighbor.dispatchEvent(new MouseEvent('click', { bubbles: true }));
      var afterNode = state();
      return {
        ok: true,
        layout: layout,
        afterClose: afterClose,
        restoredFocus: restoredFocus,
        afterInside: afterInside,
        afterOutside: afterOutside,
        afterNode: afterNode,
        neighborId: neighbor.getAttribute('data-node-id')
      };
    })()`);
    assert.equal(result?.ok, true, result?.error || 'Semantic Passport smoke failed');
    assert.equal(result.layout.label, 'Close semantic passport');
    assert.equal(result.layout.title, 'Close');
    assert.equal(result.layout.text, '×');
    assert.ok(result.layout.topGap >= 0 && result.layout.topGap <= 16, `Semantic Passport close top gap ${result.layout.topGap}px`);
    assert.ok(result.layout.rightGap >= 0 && result.layout.rightGap <= 16, `Semantic Passport close right gap ${result.layout.rightGap}px`);
    assert.deepEqual(result.afterClose, { hidden: true, active: null });
    assert.equal(result.restoredFocus, 'clients');
    assert.deepEqual(result.afterInside, { hidden: false, active: 'clients' });
    assert.deepEqual(result.afterOutside, { hidden: true, active: null });
    assert.deepEqual(result.afterNode, { hidden: false, active: result.neighborId });
    console.log('ok Semantic Passport: close control, focus return, inside preservation, and outside dismissal');
  }

  async function verifyArchitectureDeltaNavigator(file) {
    await cdp.send('Emulation.setEmulatedMedia', {
      media: 'screen',
      features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }],
    }, sessionId);
    const readyCondition = '!!document.querySelector("#review-play") && !document.querySelector("#review-play").disabled';
    async function waitForSelected(changeKey, label) {
      for (let attempt = 0; attempt < 80; attempt += 1) {
        if (await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey === ${JSON.stringify(changeKey)}`)) return;
        await delay(50);
      }
      const observed = await evaluate(cdp, sessionId, `({
        selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey || null,
        pressed: document.querySelector('#review-play')?.getAttribute('aria-pressed'),
        label: document.querySelector('#review-play')?.textContent,
        status: document.querySelector('#review-status')?.textContent,
        reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
        hidden: document.hidden
      })`);
      assert.fail(`${label} did not select ${changeKey} within the bounded wait: ${JSON.stringify(observed)}`);
    }
    async function waitForReviewFinished(label) {
      for (let attempt = 0; attempt < 400; attempt += 1) {
        const done = await evaluate(cdp, sessionId, `document.querySelector('#review-play').getAttribute('aria-pressed') === 'false' && document.querySelector('#review-play').textContent === 'Replay'`);
        if (done) return;
        await delay(50);
      }
      assert.fail(`${label} did not finish within the bounded wait`);
    }
    await navigateReady(file, readyCondition, 'architecture-delta navigator');
    const initial = await evaluate(cdp, sessionId, `({
      status: document.querySelector('#review-status').textContent,
      selected: document.querySelectorAll('.change-row[aria-current="step"]').length,
      current: document.querySelectorAll('[data-delta-review-current]').length,
      rows: document.querySelectorAll('.change-row').length
    })`);
    assert.deepEqual(initial, { status: 'Overview · 11 authored changes', selected: 0, current: 0, rows: 11 });

    await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`);
    await waitForSelected('relationship:fraud-check', 'architecture-delta initial playback');
    const advanced = await evaluate(cdp, sessionId, `({
      selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
      pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
    })`);
    assert.deepEqual(advanced, { selected: 'relationship:fraud-check', pressed: 'true' });

    await evaluate(cdp, sessionId, `document.querySelector('[role="tab"][data-target="base"]').click()`);
    const pausedKey = await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`);
    await delay(1550);
    assert.equal(await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`), pausedKey);
    assert.equal(await evaluate(cdp, sessionId, `document.querySelector('#review-play').getAttribute('aria-pressed')`), 'false');

    const overview = await evaluate(cdp, sessionId, `(() => {
      document.querySelector('[role="tab"][data-target="delta"]').click();
      document.querySelector('#review-overview').click();
      return {
        active: document.querySelector('[data-view="delta"]').hasAttribute('data-delta-review-active'),
        current: document.querySelectorAll('[data-delta-review-current]').length,
        selected: document.querySelectorAll('.change-row[aria-current="step"]').length
      };
    })()`);
    assert.deepEqual(overview, { active: false, current: 0, selected: 0 });

    await navigateReady(file, readyCondition, 'architecture-delta manual lifecycle navigator');
    await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`);
    await waitForSelected('relationship:fraud-check', 'architecture-delta previous control');
    const previousPause = await evaluate(cdp, sessionId, `(() => {
      document.querySelector('#review-previous').click();
      return {
        selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
        pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
      };
    })()`);
    assert.deepEqual(previousPause, { selected: 'component:fraud', pressed: 'false' });
    await delay(1450);
    assert.equal(await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`), 'component:fraud');

    await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`);
    const nextPause = await evaluate(cdp, sessionId, `(() => {
      document.querySelector('#review-next').click();
      return {
        selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
        pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
      };
    })()`);
    assert.deepEqual(nextPause, { selected: 'relationship:fraud-check', pressed: 'false' });

    await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`);
    const rowPause = await evaluate(cdp, sessionId, `(() => {
      document.querySelector('.change-row[data-change-key="component:queue"]').click();
      return {
        selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
        pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
      };
    })()`);
    assert.deepEqual(rowPause, { selected: 'component:queue', pressed: 'false' });

    await navigateReady(file, readyCondition, 'architecture-delta focus lifecycle navigator');
    const focusedPause = await evaluate(cdp, sessionId, `(() => {
      document.querySelector('#review-play').click();
      document.querySelector('#review-next').focus();
      return {
        selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
        pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
      };
    })()`);
    assert.deepEqual(focusedPause, { selected: 'component:fraud', pressed: 'false' });
    await delay(1450);
    assert.equal(await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`), 'component:fraud');

    await navigateReady(file, readyCondition, 'architecture-delta hidden lifecycle navigator');
    const hiddenPause = await evaluate(cdp, sessionId, `(() => {
      document.querySelector('#review-play').click();
      Object.defineProperty(document, 'hidden', { configurable: true, get: function () { return true; } });
      document.dispatchEvent(new Event('visibilitychange'));
      return {
        hidden: document.hidden,
        selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
        pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
      };
    })()`);
    assert.deepEqual(hiddenPause, { hidden: true, selected: 'component:fraud', pressed: 'false' });
    await delay(1450);
    assert.equal(await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`), 'component:fraud');

    await navigateReady(file, readyCondition, 'architecture-delta print lifecycle navigator');
    const printPause = await evaluate(cdp, sessionId, `(() => {
      document.querySelector('#review-play').click();
      window.dispatchEvent(new Event('beforeprint'));
      return {
        status: document.querySelector('#review-status').textContent,
        selected: document.querySelectorAll('.change-row[aria-current="step"]').length,
        pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
      };
    })()`);
    assert.deepEqual(printPause, { status: 'Overview · 11 authored changes', selected: 0, pressed: 'false' });
    await delay(1450);
    assert.equal(await evaluate(cdp, sessionId, `document.querySelectorAll('.change-row[aria-current="step"]').length`), 0);

    const keyboard = await evaluate(cdp, sessionId, `(() => {
      document.querySelector('details').open = true;
      var first = document.querySelector('.change-row');
      first.click();
      first.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
      var focused = document.activeElement.dataset.changeKey;
      document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
      return { focused: focused, selected: document.querySelector('.change-row[aria-current="step"]').dataset.changeKey };
    })()`);
    assert.deepEqual(keyboard, { focused: 'relationship:fraud-check', selected: 'relationship:fraud-check' });

    await cdp.send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] }, sessionId);
    await navigateReady(file, readyCondition, 'architecture-delta reduced-motion navigator');
    await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`);
    await delay(1550);
    const reduced = await evaluate(cdp, sessionId, `({
      selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
      pressed: document.querySelector('#review-play').getAttribute('aria-pressed'),
      nextDisabled: document.querySelector('#review-next').disabled
    })`);
    assert.deepEqual(reduced, { selected: 'component:fraud', pressed: 'false', nextDisabled: false });
    await evaluate(cdp, sessionId, `document.querySelector('#review-next').click()`);

    await evaluate(cdp, sessionId, `document.querySelector('.change-row').click()`);
    await cdp.send('Emulation.setEmulatedMedia', { media: 'print' }, sessionId);
    await delay(200);
    const printState = await evaluate(cdp, sessionId, `({
      strip: getComputedStyle(document.querySelector('.review-strip')).display,
      base: getComputedStyle(document.querySelector('[data-view="base"]')).display,
      delta: getComputedStyle(document.querySelector('[data-view="delta"]')).display,
      head: getComputedStyle(document.querySelector('[data-view="head"]')).display,
      current: getComputedStyle(document.querySelector('[data-delta-review-current]')).opacity,
      same: getComputedStyle(document.querySelector('[data-view="delta"] [data-delta-state="same"]')).opacity
    })`);
    assert.deepEqual(printState, { strip: 'none', base: 'none', delta: 'block', head: 'none', current: '1', same: '1' });
    await cdp.send('Emulation.setEmulatedMedia', {
      media: 'screen',
      features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }],
    }, sessionId);

    await navigateReady(file, readyCondition, 'architecture-delta tamper navigator');
    const tampered = await evaluate(cdp, sessionId, `(() => {
      var companion = document.querySelector('[data-view="delta"] g[data-edge-id="fraud-check"]');
      companion.parentNode.insertBefore(companion.cloneNode(true), companion.nextSibling);
      document.querySelector('.change-row[data-change-key="relationship:fraud-check"]').click();
      return {
        status: document.querySelector('#review-status').textContent,
        disabled: Array.from(document.querySelectorAll('.review-step,.change-row')).every(function (button) { return button.disabled; }),
        canvases: document.querySelectorAll('[data-view]').length
      };
    })()`);
    assert.deepEqual(tampered, { status: 'Review unavailable · compare identity mismatch', disabled: true, canvases: 3 });

    await navigateReady(file, readyCondition, 'architecture-delta finite navigator');
    await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`);
    await waitForReviewFinished('architecture-delta finite playback');
    const finished = await evaluate(cdp, sessionId, `({
      selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
      label: document.querySelector('#review-play').textContent,
      pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
    })`);
    assert.deepEqual(finished, { selected: 'relationship:publish-order', label: 'Replay', pressed: 'false' });
    await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`);
    const replayStarted = await evaluate(cdp, sessionId, `({
      selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
      label: document.querySelector('#review-play').textContent,
      pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
    })`);
    assert.deepEqual(replayStarted, { selected: 'component:fraud', label: 'Pause', pressed: 'true' });
    await waitForReviewFinished('architecture-delta replay playback');
    const replayFinished = await evaluate(cdp, sessionId, `({
      selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey,
      label: document.querySelector('#review-play').textContent,
      pressed: document.querySelector('#review-play').getAttribute('aria-pressed')
    })`);
    assert.deepEqual(replayFinished, { selected: 'relationship:publish-order', label: 'Replay', pressed: 'false' });

    const exportProof = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () {
      var frames = Array.from(document.querySelectorAll('.snapshot-frame'));
      var explorers = frames.map(function (frame) {
        var child = frame.contentWindow;
        return Boolean(child && child.Archify && child.Archify.focus && child.Archify.routeProbe && child.document.querySelector('#btn-node-finder') && child.document.querySelector('#guided-view-play'));
      });
      var svgA = Archify.deltaExport.canonicalSvg();
      document.querySelector('#theme').click();
      document.querySelector('#preset').click();
      document.querySelector('.change-row').click();
      var svgB = Archify.deltaExport.canonicalSvg();
      var parsed = new DOMParser().parseFromString(svgB, 'image/svg+xml');
      var exportStyle = parsed.querySelector('style')?.textContent || '';
      var blob = await Archify.deltaExport.shareCard();
      var bytes = new Uint8Array(await blob.arrayBuffer());
      return {
        explorers: explorers,
        stable: svgA === svgB,
        reviewResidue: parsed.querySelectorAll('[data-delta-review-current]').length,
        boundaryStyle: exportStyle.includes('text[data-delta-boundary-state="added"]{fill:#34d399!important}'),
        markerStyle: exportStyle.includes('.delta-edge-marker[data-delta-state],.delta-boundary-marker[data-delta-state]{color:var(--delta)}'),
        frameStyle: exportStyle.includes('rect[data-graph-role="structural-frame"]'),
        boundaryMarkers: Array.from(parsed.querySelectorAll('.delta-boundary-marker')).map(function (marker) { return marker.textContent; }),
        type: blob.type,
        size: blob.size,
        signature: Array.from(bytes.slice(0, 8)).map(function (byte) { return byte.toString(16).padStart(2, '0'); }).join('')
      };
    })()`, true), 15_000, 'Architecture Delta export');
    assert.deepEqual(exportProof.explorers, [true, true]);
    assert.equal(exportProof.stable, true);
    assert.equal(exportProof.reviewResidue, 0);
    assert.equal(exportProof.boundaryStyle, true);
    assert.equal(exportProof.markerStyle, true);
    assert.equal(exportProof.frameStyle, true);
    assert.deepEqual(exportProof.boundaryMarkers, ['~', '~']);
    assert.equal(exportProof.type, 'image/png');
    assert.ok(exportProof.size > 20_000, `Architecture Delta Share Card is unexpectedly small (${exportProof.size} bytes)`);
    assert.equal(exportProof.signature, '89504e470d0a1a0a');
    console.log(`ok Architecture Delta navigator + export: exact identity, complete explorers, static SVG, and ${exportProof.size}-byte Share Card`);
  }

  async function captureShareCard(file, label) {
    await navigateReady(file, '!!(window.Archify && Archify.exportMenu && Archify.exportMenu.shareCard)', label);
    const sharePayload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () {
      try {
        var blob = await Archify.exportMenu.shareCard();
        var bytes = new Uint8Array(await blob.arrayBuffer());
        var binary = '';
        for (var offset = 0; offset < bytes.length; offset += 32768) {
          binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768));
        }
        return { ok: true, type: blob.type, size: blob.size, base64: btoa(binary) };
      } catch (error) {
        return { ok: false, error: String(error && error.message || error) };
      }
    })()`, true), 10_000, `${label} Share Card export`);

    assert.equal(sharePayload?.ok, true, sharePayload?.error || `${label} Share Card export failed`);
    assert.equal(sharePayload.type, 'image/png');
    assert.ok(sharePayload.size > 20_000, `${label} Share Card is unexpectedly small (${sharePayload.size} bytes)`);

    const png = Buffer.from(sharePayload.base64, 'base64');
    assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a', `${label} output is not a PNG`);
    assert.equal(png.readUInt32BE(16), 1200, `${label} Share Card width`);
    assert.equal(png.readUInt32BE(20), 630, `${label} Share Card height`);

    const pngPath = path.join(tmp, `${label}.share-card.png`);
    fs.writeFileSync(pngPath, png);
    const pixels = execFileSync(ffmpeg, [
      '-v', 'error',
      '-i', pngPath,
      '-vf', 'scale=120:63',
      '-frames:v', '1',
      '-f', 'rawvideo',
      '-pix_fmt', 'rgb24',
      '-',
    ], { maxBuffer: 4 * 1024 * 1024 });
    const colors = new Set();
    const counts = new Map();
    for (let offset = 0; offset < pixels.length; offset += 3) {
      const color = pixels.subarray(offset, offset + 3).toString('hex');
      colors.add(color);
      counts.set(color, (counts.get(color) || 0) + 1);
    }
    const largestColorShare = Math.max(...counts.values()) / (pixels.length / 3);
    assert.ok(colors.size >= 24, `${label} Share Card has only ${colors.size} sampled colors`);
    assert.ok(largestColorShare < 0.96, `${label} Share Card is visually near-blank (${Math.round(largestColorShare * 100)}% one color)`);
    console.log(`ok ${label} Share Card: ${sharePayload.size} bytes, 1200x630, ${colors.size} sampled colors`);
  }

  async function captureCopiedShareCard(file, label) {
    await navigateReady(file, '!!(window.Archify && Archify.exportMenu && Archify.exportMenu.copyShareCard)', label);
    const copiedPayload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () {
      try {
        Object.defineProperty(window, 'ClipboardItem', {
          configurable: true,
          value: function ClipboardItem(items) { this.items = items; }
        });
        Object.defineProperty(navigator, 'clipboard', {
          configurable: true,
          value: {
            write: async function (items) {
              window.__archifyCopiedShareCard = await Promise.resolve(items[0].items['image/png']);
            }
          }
        });
        window.alert = function (message) { window.__archifyCopyAlert = message; };
        await Archify.exportMenu.copyShareCard();
        var blob = window.__archifyCopiedShareCard;
        if (!blob) throw new Error(window.__archifyCopyAlert || 'clipboard received no blob');
        var bytes = new Uint8Array(await blob.arrayBuffer());
        var binary = '';
        for (var offset = 0; offset < bytes.length; offset += 32768) {
          binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768));
        }
        return {
          ok: true,
          type: blob.type,
          size: blob.size,
          base64: btoa(binary),
          receipt: {
            format: document.documentElement.getAttribute('data-last-export-format'),
            width: document.documentElement.getAttribute('data-last-export-width'),
            height: document.documentElement.getAttribute('data-last-export-height'),
            canonical: document.documentElement.getAttribute('data-last-export-canonical'),
            error: document.documentElement.getAttribute('data-last-export-error')
          }
        };
      } catch (error) {
        return { ok: false, error: String(error && error.message || error) };
      }
    })()`, true), 10_000, `${label} Copy Share Card`);

    assert.equal(copiedPayload?.ok, true, copiedPayload?.error || `${label} Copy Share Card failed`);
    assert.equal(copiedPayload.type, 'image/png');
    assert.ok(copiedPayload.size > 20_000, `${label} copied Share Card is unexpectedly small`);
    const png = Buffer.from(copiedPayload.base64, 'base64');
    assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a', `${label} copied output is not a PNG`);
    assert.equal(png.readUInt32BE(16), 1200, `${label} copied Share Card width`);
    assert.equal(png.readUInt32BE(20), 630, `${label} copied Share Card height`);
    assert.deepEqual(copiedPayload.receipt, {
      format: 'share-card',
      width: '1200',
      height: '630',
      canonical: 'true',
      error: null,
    });
    console.log(`ok ${label} Copy Share Card: ${copiedPayload.size} bytes, image/png, truthful receipt`);
  }

  async function captureRouteShareCard(file, label, sourceId, targetId, options = {}) {
    await navigateReady(file, '!!(window.Archify && Archify.routeProbe && Archify.exportMenu && Archify.exportMenu.downloadRouteShareCard)', label);
    const routePayload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () {
      try {
        window.alert = function (message) { window.__archifyRouteAlert = message; };
        Archify.routeProbe.begin({ source: ${JSON.stringify(sourceId)}, focusNode: false });
        if (!Archify.routeProbe.choose(${JSON.stringify(targetId)}, { updateUrl: false })) {
          throw new Error('route did not resolve');
        }
        var snapshot = Archify.routeProbe.exportSnapshot();
        if (!snapshot) throw new Error('resolved route exposed no export snapshot');
        Archify.exportMenu.syncRouteShare();
        var routeMenuItem = document.querySelector('[data-action="route-share-card"]');
        var menuResolved = !!routeMenuItem && !routeMenuItem.hidden && !routeMenuItem.disabled;
        var svg = document.querySelector('.diagram-container svg');
        var firstEdgeKey = snapshot.edges[0].key;
        var primaryCarrier = Array.from(svg.querySelectorAll('[data-edge-key]')).find(function (element) {
          return element.getAttribute('data-edge-key') === firstEdgeKey && hasDrawableGeometry(element);
        });
        if (!primaryCarrier) throw new Error('route exposed no primary geometry carrier');
        var duplicateCarrier = primaryCarrier.cloneNode(true);
        var duplicateGeometry = /^(path|line|polyline)$/i.test(duplicateCarrier.tagName)
          ? duplicateCarrier
          : duplicateCarrier.querySelector('path, line, polyline');
        if (duplicateGeometry.tagName.toLowerCase() === 'path') {
          duplicateGeometry.setAttribute('d', duplicateGeometry.getAttribute('d') + ' M 0 0 L 1 1');
        }
        svg.appendChild(duplicateCarrier);
        var duplicateGeometryRejected = Archify.routeProbe.exportSnapshot() === null;
        var duplicateExportError = '';
        try { await Archify.exportMenu.shareCard({ variant: 'route' }); }
        catch (error) { duplicateExportError = String(error && error.message || error); }
        duplicateCarrier.remove();
        var primaryGeometry = /^(path|line|polyline)$/i.test(primaryCarrier.tagName)
          ? primaryCarrier
          : primaryCarrier.querySelector('path, line, polyline');
        var geometryAttribute = primaryGeometry.tagName.toLowerCase() === 'path' ? 'd' :
          primaryGeometry.tagName.toLowerCase() === 'polyline' ? 'points' : 'x2';
        var originalGeometry = primaryGeometry.getAttribute(geometryAttribute);
        primaryGeometry.setAttribute(geometryAttribute, '');
        var emptyGeometryRejected = Archify.routeProbe.exportSnapshot() === null;
        var emptyGeometryExportError = '';
        try { await Archify.exportMenu.shareCard({ variant: 'route' }); }
        catch (error) { emptyGeometryExportError = String(error && error.message || error); }
        primaryGeometry.setAttribute(geometryAttribute, originalGeometry);
        await new Promise(function (resolve) {
          requestAnimationFrame(function () { requestAnimationFrame(resolve); });
        });
        function stableLiveSnapshot() {
          var clone = svg.cloneNode(true);
          clone.style.removeProperty('transform');
          clone.style.removeProperty('clip-path');
          clone.removeAttribute('data-view-scale');
          Array.from(clone.querySelectorAll('[data-legend-bridge-runtime]')).forEach(function (element) { element.remove(); });
          return clone.outerHTML;
        }
        var liveBefore = stableLiveSnapshot();
        var captured = [];
        var downloads = [];
        var originalCreateObjectURL = URL.createObjectURL.bind(URL);
        var originalAnchorClick = HTMLAnchorElement.prototype.click;
        var originalFillText = CanvasRenderingContext2D.prototype.fillText;
        var headerMetrics = [];
        URL.createObjectURL = function (blob) {
          if (blob && blob.type && blob.type.indexOf('image/svg+xml') === 0) {
            captured.push(blob.text());
          }
          return originalCreateObjectURL(blob);
        };
        HTMLAnchorElement.prototype.click = function () { downloads.push(this.download); };
        CanvasRenderingContext2D.prototype.fillText = function (text, x, y) {
          if (headerMetrics.length < 2 && (y === 62 || y === 87)) {
            headerMetrics.push({
              y: y,
              text: String(text),
              width: this.measureText(String(text)).width,
              maxWidth: y === 62 ? 798 : 848
            });
          }
          return originalFillText.apply(this, arguments);
        };

        var blob;
        var canonicalBlob;
        var routeReceipt;
        var ordinaryReceipt;
        var routeFingerprints = [];
        try {
          blob = await Archify.exportMenu.downloadRouteShareCard();
          routeReceipt = {
            format: document.documentElement.getAttribute('data-last-export-format'),
            variant: document.documentElement.getAttribute('data-last-export-variant'),
            width: document.documentElement.getAttribute('data-last-export-width'),
            height: document.documentElement.getAttribute('data-last-export-height'),
            canonical: document.documentElement.getAttribute('data-last-export-canonical'),
            routeStateClean: document.documentElement.getAttribute('data-last-export-route-state-clean'),
            error: document.documentElement.getAttribute('data-last-export-error')
          };
          var routeSvgText = captured[0] ? await captured[0] : '';
          function fingerprint(text) {
            var hash = 2166136261;
            for (var index = 0; index < text.length; index++) {
              hash ^= text.charCodeAt(index);
              hash = Math.imul(hash, 16777619);
            }
            return (hash >>> 0).toString(16);
          }
          routeFingerprints.push(fingerprint(routeSvgText));

          if (${JSON.stringify(options.journeyInvariance === true)}) {
            async function captureJourneySource(action) {
              action();
              var captureIndex = captured.length;
              await Archify.exportMenu.shareCard({ variant: 'route' });
              routeFingerprints.push(fingerprint(await captured[captureIndex]));
            }
            await captureJourneySource(function () { Archify.routeProbe.selectJourneyIndex(0); });
            await captureJourneySource(function () { Archify.routeProbe.selectJourneyIndex(Math.floor(snapshot.nodeIds.length / 2)); });
            await captureJourneySource(function () { Archify.routeProbe.selectJourneyIndex(snapshot.nodeIds.length - 1); });
            Archify.routeProbe.showOverview({ reveal: false });
            var started = Archify.routeProbe.playJourney();
            if (!started) throw new Error('Route Journey could not enter playing state for invariance check');
            await captureJourneySource(function () {});
            Archify.routeProbe.pauseJourney({ preserveElapsed: false });
            await captureJourneySource(function () {});
            document.documentElement.setAttribute('data-motion', 'still');
            await captureJourneySource(function () {});
            document.documentElement.setAttribute('data-motion', 'live');
            Archify.routeProbe.showOverview({ reveal: false });
          }

          var toBlobError = '';
          var originalToBlob = HTMLCanvasElement.prototype.toBlob;
          HTMLCanvasElement.prototype.toBlob = function (callback) { callback(null); };
          try { await Archify.exportMenu.shareCard({ variant: 'route' }); }
          catch (error) { toBlobError = String(error && error.message || error); }
          finally { HTMLCanvasElement.prototype.toBlob = originalToBlob; }

          var missingToBlobError = '';
          HTMLCanvasElement.prototype.toBlob = undefined;
          try { await Archify.exportMenu.shareCard({ variant: 'route' }); }
          catch (error) { missingToBlobError = String(error && error.message || error); }
          finally { HTMLCanvasElement.prototype.toBlob = originalToBlob; }

          var missingContextError = '';
          var originalGetContext = HTMLCanvasElement.prototype.getContext;
          HTMLCanvasElement.prototype.getContext = function () { return null; };
          try { await Archify.exportMenu.shareCard({ variant: 'route' }); }
          catch (error) { missingContextError = String(error && error.message || error); }
          finally { HTMLCanvasElement.prototype.getContext = originalGetContext; }

          var imageDecodeError = '';
          var OriginalImage = window.Image;
          function FailingImage() {}
          Object.defineProperty(FailingImage.prototype, 'src', {
            set: function () {
              var instance = this;
              queueMicrotask(function () {
                if (typeof instance.onerror === 'function') instance.onerror(new Event('error'));
              });
            }
          });
          window.Image = FailingImage;
          try { await Archify.exportMenu.shareCard({ variant: 'route' }); }
          catch (error) { imageDecodeError = String(error && error.message || error); }
          finally { window.Image = OriginalImage; }

          var unknownVariantError = '';
          try { await Archify.exportMenu.shareCard({ variant: 'unknown' }); }
          catch (error) { unknownVariantError = String(error && error.message || error); }

          var canonicalIndex = captured.length;
          canonicalBlob = await Archify.exportMenu.shareCard();
          var canonicalSvgText = captured[canonicalIndex] ? await captured[canonicalIndex] : '';
          await Archify.exportMenu.run('share-card');
          ordinaryReceipt = {
            format: document.documentElement.getAttribute('data-last-export-format'),
            variant: document.documentElement.getAttribute('data-last-export-variant'),
            width: document.documentElement.getAttribute('data-last-export-width'),
            height: document.documentElement.getAttribute('data-last-export-height'),
            canonical: document.documentElement.getAttribute('data-last-export-canonical'),
            routeStateClean: document.documentElement.getAttribute('data-last-export-route-state-clean'),
            error: document.documentElement.getAttribute('data-last-export-error')
          };
          var svgDownloadIndex = captured.length;
          await Archify.exportMenu.run('svg');
          var exportedSvgText = captured[svgDownloadIndex] ? await captured[svgDownloadIndex] : '';
          var svgReceipt = {
            format: document.documentElement.getAttribute('data-last-export-format'),
            variant: document.documentElement.getAttribute('data-last-export-variant'),
            canonical: document.documentElement.getAttribute('data-last-export-canonical')
          };
          await Archify.exportMenu.run('png');
          var pngReceipt = {
            format: document.documentElement.getAttribute('data-last-export-format'),
            variant: document.documentElement.getAttribute('data-last-export-variant'),
            canonical: document.documentElement.getAttribute('data-last-export-canonical')
          };

          var parser = new DOMParser();
          var routeSvg = parser.parseFromString(routeSvgText, 'image/svg+xml').documentElement;
          var canonicalSvg = parser.parseFromString(canonicalSvgText, 'image/svg+xml').documentElement;
          var exportedSvg = parser.parseFromString(exportedSvgText, 'image/svg+xml').documentElement;
          var matchedNodeIds = Array.from(routeSvg.querySelectorAll('[data-node-id][data-share-route-match]')).map(function (node) {
            return { id: node.getAttribute('data-node-id'), step: Number(node.getAttribute('data-share-route-step')) };
          }).sort(function (a, b) { return a.step - b.step; }).map(function (item) { return item.id; });
          var edgeSteps = new Map();
          Array.from(routeSvg.querySelectorAll('[data-edge-key][data-share-route-match]')).forEach(function (edge) {
            edgeSteps.set(edge.getAttribute('data-edge-key'), Number(edge.getAttribute('data-share-route-step')));
          });
          var matchedEdgeKeys = Array.from(edgeSteps).sort(function (a, b) { return a[1] - b[1]; }).map(function (entry) { return entry[0]; });
          var expectedEdgeKeys = snapshot.edges.map(function (edge) { return edge.key; });
          var routeNodeIds = Array.from(routeSvg.querySelectorAll('[data-node-id]')).map(function (node) { return node.getAttribute('data-node-id'); }).sort();
          var liveNodeIds = Array.from(svg.querySelectorAll('[data-node-id]')).map(function (node) { return node.getAttribute('data-node-id'); }).sort();
          var routeEdgeKeys = Array.from(new Set(Array.from(routeSvg.querySelectorAll('[data-edge-key]')).map(function (edge) { return edge.getAttribute('data-edge-key'); }))).sort();
          var liveEdgeKeys = Array.from(new Set(Array.from(svg.querySelectorAll('[data-edge-key]')).map(function (edge) { return edge.getAttribute('data-edge-key'); }))).sort();

          var bytes = new Uint8Array(await blob.arrayBuffer());
          var binary = '';
          for (var offset = 0; offset < bytes.length; offset += 32768) {
            binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768));
          }

          await new Promise(function (resolve) {
            requestAnimationFrame(function () { requestAnimationFrame(resolve); });
          });
          var liveAfter = stableLiveSnapshot();
          var liveUnchanged = liveBefore === liveAfter;
          var liveDiff = '';
          if (!liveUnchanged) {
            var diffIndex = 0;
            while (diffIndex < liveBefore.length && diffIndex < liveAfter.length && liveBefore[diffIndex] === liveAfter[diffIndex]) diffIndex++;
            liveDiff = 'at ' + diffIndex + ': before=' + liveBefore.slice(Math.max(0, diffIndex - 80), diffIndex + 160) +
              ' after=' + liveAfter.slice(Math.max(0, diffIndex - 80), diffIndex + 160);
          }

          var asyncClearIndex = captured.length;
          var asyncClearPromise = Archify.exportMenu.shareCard({ variant: 'route' });
          Archify.routeProbe.clear({ updateUrl: false, preserveView: true });
          var asyncClearBlob = await asyncClearPromise;
          var asyncClearFingerprint = fingerprint(await captured[asyncClearIndex]);
          var hiddenAfterClear = routeMenuItem.hidden && routeMenuItem.disabled &&
            getComputedStyle(routeMenuItem).display === 'none';
          var staleError = '';
          try { await Archify.exportMenu.shareCard({ variant: 'route' }); }
          catch (error) { staleError = String(error && error.message || error); }
          await Archify.exportMenu.downloadRouteShareCard();
          var failedReceipt = {
            format: document.documentElement.getAttribute('data-last-export-format'),
            variant: document.documentElement.getAttribute('data-last-export-variant'),
            errorFormat: document.documentElement.getAttribute('data-last-export-error-format'),
            error: document.documentElement.getAttribute('data-last-export-error')
          };

          return {
            ok: true,
            type: blob.type,
            size: blob.size,
            base64: btoa(binary),
            snapshot: snapshot,
            matchedNodeIds: matchedNodeIds,
            matchedEdgeKeys: matchedEdgeKeys,
            expectedEdgeKeys: expectedEdgeKeys,
            routeShare: routeSvg.hasAttribute('data-share-route'),
            routeLiveResidue: routeSvg.querySelectorAll('[data-route-match], [data-route-step], [data-route-start], [data-route-end], [data-route-journey-state], [data-route-journey-current], [data-route-journey-overlay]').length,
            routeMotionResidue: (routeSvg.hasAttribute('data-animation') ? 1 : 0) + routeSvg.querySelectorAll('[data-animate]').length,
            routeNodeIds: routeNodeIds,
            liveNodeIds: liveNodeIds,
            routeEdgeKeys: routeEdgeKeys,
            liveEdgeKeys: liveEdgeKeys,
            canonicalRouteResidue: canonicalSvg.querySelectorAll('[data-route-match], [data-route-step], [data-route-start], [data-route-end], [data-share-route-match], [data-share-route-step], [data-share-route-start], [data-share-route-end], [data-share-route-middle]').length,
            canonicalRouteActive: canonicalSvg.hasAttribute('data-route-active') || canonicalSvg.hasAttribute('data-share-route'),
            canonicalSize: canonicalBlob.size,
            liveUnchanged: liveUnchanged,
            liveDiff: liveDiff,
            menuResolved: menuResolved,
            duplicateGeometryRejected: duplicateGeometryRejected,
            duplicateExportError: duplicateExportError,
            emptyGeometryRejected: emptyGeometryRejected,
            emptyGeometryExportError: emptyGeometryExportError,
            toBlobError: toBlobError,
            missingToBlobError: missingToBlobError,
            missingContextError: missingContextError,
            imageDecodeError: imageDecodeError,
            unknownVariantError: unknownVariantError,
            headerMetrics: headerMetrics,
            routeLabelExecuted: !!window.__routeLabelExecuted,
            svgReceipt: svgReceipt,
            pngReceipt: pngReceipt,
            exportedSvgRouteResidue: exportedSvg.hasAttribute('data-route-active') ||
              exportedSvg.hasAttribute('data-share-route') ||
              exportedSvg.querySelectorAll('[data-route-match], [data-route-step], [data-route-start], [data-route-end], [data-share-route-match], [data-share-route-step], [data-share-route-start], [data-share-route-end], [data-share-route-middle]').length > 0,
            exportedSvgDualTheme: /prefers-color-scheme:\s*light/.test(Array.from(exportedSvg.querySelectorAll('style')).map(function (style) { return style.textContent; }).join('\n')),
            asyncClearStable: asyncClearBlob && asyncClearBlob.type === 'image/png' && asyncClearFingerprint === routeFingerprints[0],
            hiddenAfterClear: hiddenAfterClear,
            staleSnapshot: Archify.routeProbe.exportSnapshot(),
            staleError: staleError,
            routeFingerprints: routeFingerprints,
            downloads: downloads,
            routeReceipt: routeReceipt,
            ordinaryReceipt: ordinaryReceipt,
            failedReceipt: failedReceipt
          };
        } finally {
          URL.createObjectURL = originalCreateObjectURL;
          HTMLAnchorElement.prototype.click = originalAnchorClick;
          CanvasRenderingContext2D.prototype.fillText = originalFillText;
        }
      } catch (error) {
        return { ok: false, error: String(error && error.message || error) };
      }
    })()`, true), 25_000, `${label} Route Card export`);

    assert.equal(routePayload?.ok, true, routePayload?.error || `${label} Route Card export failed`);
    assert.equal(routePayload.type, 'image/png');
    assert.ok(routePayload.size > 20_000, `${label} Route Card is unexpectedly small (${routePayload.size} bytes)`);
    const png = Buffer.from(routePayload.base64, 'base64');
    assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a', `${label} Route Card is not a PNG`);
    assert.equal(png.readUInt32BE(16), 1200, `${label} Route Card width`);
    assert.equal(png.readUInt32BE(20), 630, `${label} Route Card height`);
    const pngPath = path.join(tmp, `${label}.route-share-card.png`);
    fs.writeFileSync(pngPath, png);
    const sampledPixels = execFileSync(ffmpeg, [
      '-v', 'error',
      '-i', pngPath,
      '-vf', 'scale=120:63',
      '-frames:v', '1',
      '-f', 'rawvideo',
      '-pix_fmt', 'rgb24',
      '-',
    ], { maxBuffer: 4 * 1024 * 1024 });
    const sampledColors = new Set();
    for (let offset = 0; offset < sampledPixels.length; offset += 3) {
      sampledColors.add(sampledPixels.subarray(offset, offset + 3).toString('hex'));
    }
    assert.ok(sampledColors.size >= 20, `${label} Route Card has only ${sampledColors.size} sampled colors`);
    assert.equal(routePayload.routeShare, true);
    assert.deepEqual(routePayload.matchedNodeIds, routePayload.snapshot.nodeIds);
    assert.deepEqual(routePayload.matchedEdgeKeys, routePayload.expectedEdgeKeys);
    assert.equal(routePayload.routeLiveResidue, 0);
    assert.equal(routePayload.routeMotionResidue, 0);
    assert.deepEqual(routePayload.routeNodeIds, routePayload.liveNodeIds);
    assert.deepEqual(routePayload.routeEdgeKeys, routePayload.liveEdgeKeys);
    assert.equal(routePayload.canonicalRouteResidue, 0);
    assert.equal(routePayload.canonicalRouteActive, false);
    assert.equal(routePayload.liveUnchanged, true, routePayload.liveDiff);
    assert.equal(routePayload.menuResolved, true);
    assert.equal(routePayload.duplicateGeometryRejected, true);
    assert.match(routePayload.duplicateExportError, /Trace a route before exporting a Route Share Card/);
    assert.equal(routePayload.emptyGeometryRejected, true);
    assert.match(routePayload.emptyGeometryExportError, /Trace a route before exporting a Route Share Card/);
    assert.match(routePayload.toBlobError, /canvas\.toBlob returned no data for Share Card/);
    assert.match(routePayload.missingToBlobError, /canvas\.toBlob unavailable for Share Card/);
    assert.match(routePayload.missingContextError, /2D canvas context unavailable for Share Card/);
    assert.ok(routePayload.imageDecodeError);
    assert.match(routePayload.unknownVariantError, /Unknown Share Card variant: unknown/);
    assert.equal(routePayload.routeLabelExecuted, false);
    assert.deepEqual(routePayload.headerMetrics.map((metric) => metric.y), [62, 87]);
    assert.ok(routePayload.headerMetrics.every((metric) => metric.width <= metric.maxWidth + 0.5), `${label} title/subtitle overflowed the Share Card header`);
    assert.deepEqual(routePayload.svgReceipt, { format: 'svg', variant: null, canonical: 'true' });
    assert.deepEqual(routePayload.pngReceipt, { format: 'png', variant: null, canonical: 'true' });
    assert.equal(routePayload.exportedSvgRouteResidue, false);
    assert.equal(routePayload.exportedSvgDualTheme, true);
    assert.equal(routePayload.asyncClearStable, true);
    assert.equal(routePayload.hiddenAfterClear, true);
    assert.equal(routePayload.staleSnapshot, null);
    assert.match(routePayload.staleError, /Trace a route before exporting a Route Share Card/);
    assert.ok(routePayload.routeFingerprints.every((fingerprint) => fingerprint === routePayload.routeFingerprints[0]), `${label} Route source changed across Journey state`);
    assert.match(routePayload.downloads[0], /-route-share-card\.png$/);
    assert.doesNotMatch(routePayload.downloads[0], /-route-.+-to-.+\.png$/);
    assert.deepEqual(routePayload.routeReceipt, {
      format: 'share-card',
      variant: 'route',
      width: '1200',
      height: '630',
      canonical: 'false',
      routeStateClean: 'true',
      error: null,
    });
    assert.deepEqual(routePayload.ordinaryReceipt, {
      format: 'share-card',
      variant: null,
      width: '1200',
      height: '630',
      canonical: 'true',
      routeStateClean: null,
      error: null,
    });
    assert.equal(routePayload.failedReceipt.format, null);
    assert.equal(routePayload.failedReceipt.variant, null);
    assert.equal(routePayload.failedReceipt.errorFormat, 'share-card');
    assert.match(routePayload.failedReceipt.error, /Trace a route before exporting a Route Share Card/);
    if (options.expectedHops !== undefined) assert.equal(routePayload.snapshot.hops, options.expectedHops);
    if (options.expectedSourceLabel !== undefined) assert.equal(routePayload.snapshot.source.label, options.expectedSourceLabel);
    if (options.expectedTargetLabel !== undefined) assert.equal(routePayload.snapshot.target.label, options.expectedTargetLabel);
    console.log(`ok ${label} Route Card: ${routePayload.size} bytes, ${routePayload.snapshot.nodeIds.length} nodes, ${routePayload.snapshot.edges.length} exact hops`);
  }

  async function verifyDynamicReducedMotionRoute(file, label, sourceId, targetId) {
    await cdp.send('Emulation.setEmulatedMedia', {
      features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }],
    }, sessionId);
    await navigateReady(file, '!!(window.Archify && Archify.routeProbe && Archify.exportMenu && Archify.motionGovernor)', label);

    async function sourceFingerprint() {
      return withTimeout(evaluate(cdp, sessionId, String.raw`(async function () {
        var originalCreateObjectURL = URL.createObjectURL.bind(URL);
        var sourcePromise = null;
        URL.createObjectURL = function (blob) {
          if (!sourcePromise && blob && blob.type && blob.type.indexOf('image/svg+xml') === 0) sourcePromise = blob.text();
          return originalCreateObjectURL(blob);
        };
        try {
          await Archify.exportMenu.shareCard({ variant: 'route' });
          var source = await sourcePromise;
          var hash = 2166136261;
          for (var index = 0; index < source.length; index++) {
            hash ^= source.charCodeAt(index);
            hash = Math.imul(hash, 16777619);
          }
          return (hash >>> 0).toString(16);
        } finally {
          URL.createObjectURL = originalCreateObjectURL;
        }
      })()`, true), 10_000, `${label} source fingerprint`);
    }

    const setup = await evaluate(cdp, sessionId, `(function () {
      Archify.routeProbe.begin({ source: ${JSON.stringify(sourceId)}, focusNode: false });
      if (!Archify.routeProbe.choose(${JSON.stringify(targetId)}, { updateUrl: false })) return { resolved: false };
      Archify.routeProbe.showOverview({ reveal: false });
      return {
        resolved: true,
        started: Archify.routeProbe.playJourney(),
        playing: Archify.routeProbe.isJourneyPlaying(),
        motion: document.documentElement.getAttribute('data-motion')
      };
    })()`);
    assert.deepEqual(setup, { resolved: true, started: true, playing: true, motion: 'live' });
    const before = await sourceFingerprint();

    await cdp.send('Emulation.setEmulatedMedia', {
      features: [{ name: 'prefers-reduced-motion', value: 'reduce' }],
    }, sessionId);
    let reduced = null;
    for (let attempt = 0; attempt < 50; attempt += 1) {
      reduced = await evaluate(cdp, sessionId, `({
        matches: matchMedia('(prefers-reduced-motion: reduce)').matches,
        motion: document.documentElement.getAttribute('data-motion'),
        playing: Archify.routeProbe.isJourneyPlaying()
      })`);
      if (reduced.matches && reduced.motion === 'still' && !reduced.playing) break;
      await delay(20);
    }
    assert.deepEqual(reduced, { matches: true, motion: 'still', playing: false });
    const after = await sourceFingerprint();
    assert.equal(after, before, `${label} source changed after dynamic reduced-motion paused Journey`);

    await cdp.send('Emulation.setEmulatedMedia', {
      features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }],
    }, sessionId);
    console.log(`ok ${label}: dynamic reduced-motion paused Journey without changing Route Card source`);
  }

  async function captureRouteVisualMatrix(file, label, sourceId, targetId) {
    await navigateReady(file, '!!(window.Archify && Archify.preset && Archify.routeProbe && Archify.exportMenu)', label);
    const matrix = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () {
      Archify.routeProbe.begin({ source: ${JSON.stringify(sourceId)}, focusNode: false });
      if (!Archify.routeProbe.choose(${JSON.stringify(targetId)}, { updateUrl: false })) {
        throw new Error('route did not resolve');
      }
      var identity = JSON.stringify(Archify.routeProbe.exportSnapshot());
      var results = [];
      for (var preset of ['classic', 'signal-flow', 'blueprint', 'editorial']) {
        if (!Archify.preset.apply(preset)) throw new Error('could not apply preset ' + preset);
        for (var theme of ['dark', 'light']) {
          document.documentElement.setAttribute('data-theme', theme);
          var blob = await Archify.exportMenu.shareCard({ variant: 'route' });
          var bytes = new Uint8Array(await blob.arrayBuffer());
          var hash = 2166136261;
          for (var index = 0; index < bytes.length; index++) {
            hash ^= bytes[index];
            hash = Math.imul(hash, 16777619);
          }
          var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
          results.push({
            preset: preset,
            theme: theme,
            type: blob.type,
            size: blob.size,
            width: view.getUint32(16),
            height: view.getUint32(20),
            hash: (hash >>> 0).toString(16),
            identity: JSON.stringify(Archify.routeProbe.exportSnapshot())
          });
        }
      }
      return { identity: identity, results: results };
    })()`, true), 20_000, `${label} Route visual matrix`);

    assert.equal(matrix.results.length, 8);
    for (const result of matrix.results) {
      assert.equal(result.type, 'image/png', `${label} ${result.preset}/${result.theme} MIME`);
      assert.equal(result.width, 1200, `${label} ${result.preset}/${result.theme} width`);
      assert.equal(result.height, 630, `${label} ${result.preset}/${result.theme} height`);
      assert.ok(result.size > 20_000, `${label} ${result.preset}/${result.theme} is unexpectedly small`);
      assert.equal(result.identity, matrix.identity, `${label} ${result.preset}/${result.theme} changed route identity`);
    }
    assert.equal(new Set(matrix.results.map((result) => result.hash)).size, 8, `${label} presets/themes should produce eight distinct PNGs`);
    console.log(`ok ${label} Route visual matrix: Classic/Flow/Blueprint/Editorial x dark/light`);
  }

  async function captureReachShareCard(file, label, originId, direction, options = {}) {
    await navigateReady(file, '!!(window.Archify && Archify.focus && Archify.focus.reachabilitySnapshot && Archify.exportMenu && Archify.exportMenu.downloadReachShareCard)', label);
    const reachPayload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () {
      try {
        window.alert = function (message) { window.__archifyReachAlert = String(message); };
        if (!Archify.focus.set(${JSON.stringify(originId)}, { toggle: false, updateUrl: false })) {
          throw new Error('focus origin did not resolve');
        }
        if (!Archify.focus.reach(${JSON.stringify(direction)}, { toggle: false, updateUrl: false, reveal: false })) {
          throw new Error('authored reach did not resolve');
        }
        var snapshot = Archify.focus.reachabilitySnapshot();
        if (!snapshot) throw new Error('active authored reach exposed no export snapshot');
        Archify.exportMenu.syncReachShare();
        var reachMenuItem = document.querySelector('[data-action="reach-share-card"]');
        var menuResolved = !!reachMenuItem && !reachMenuItem.hidden && !reachMenuItem.disabled &&
          getComputedStyle(reachMenuItem).display !== 'none';
        var svg = document.querySelector('.diagram-container svg');

        var firstEdge = snapshot.edges[0];
        var primaryCarrier = Array.from(svg.querySelectorAll('[data-edge-key]')).find(function (element) {
          return element.getAttribute('data-edge-key') === firstEdge.key && hasDrawableGeometry(element);
        });
        if (!primaryCarrier) throw new Error('reach exposed no primary geometry carrier');
        var duplicateCarrier = primaryCarrier.cloneNode(true);
        var duplicateGeometry = /^(path|line|polyline)$/i.test(duplicateCarrier.tagName)
          ? duplicateCarrier
          : duplicateCarrier.querySelector('path, line, polyline');
        if (duplicateGeometry.tagName.toLowerCase() === 'path') {
          duplicateGeometry.setAttribute('d', duplicateGeometry.getAttribute('d') + ' M 0 0 L 1 1');
        }
        svg.appendChild(duplicateCarrier);
        var duplicateGeometryRejected = Archify.focus.reachabilitySnapshot() === null;
        var duplicateExportError = '';
        try { await Archify.exportMenu.shareCard({ variant: 'reach' }); }
        catch (error) { duplicateExportError = String(error && error.message || error); }
        duplicateCarrier.remove();
        snapshot = Archify.focus.reachabilitySnapshot();
        if (!snapshot) throw new Error('reach snapshot did not recover after geometry restoration');

        function stableLiveSnapshot() {
          var clone = svg.cloneNode(true);
          clone.style.removeProperty('transform');
          clone.style.removeProperty('clip-path');
          clone.removeAttribute('data-view-scale');
          Array.from(clone.querySelectorAll('[data-legend-bridge-runtime]')).forEach(function (element) { element.remove(); });
          return clone.outerHTML;
        }
        function fingerprintBytes(bytes) {
          var hash = 2166136261;
          for (var index = 0; index < bytes.length; index++) {
            hash ^= bytes[index];
            hash = Math.imul(hash, 16777619);
          }
          return (hash >>> 0).toString(16);
        }
        var liveBefore = stableLiveSnapshot();
        var captured = [];
        var downloads = [];
        var originalCreateObjectURL = URL.createObjectURL.bind(URL);
        var originalAnchorClick = HTMLAnchorElement.prototype.click;
        URL.createObjectURL = function (blob) {
          if (blob && blob.type && blob.type.indexOf('image/svg+xml') === 0) captured.push(blob.text());
          return originalCreateObjectURL(blob);
        };
        HTMLAnchorElement.prototype.click = function () { downloads.push(this.download); };

        try {
          var blob = await Archify.exportMenu.shareCard({ variant: 'reach' });
          var reachSvgText = captured[0] ? await captured[0] : '';
          var downloadedBlob = await Archify.exportMenu.downloadReachShareCard();
          var reachReceipt = {
            format: document.documentElement.getAttribute('data-last-export-format'),
            variant: document.documentElement.getAttribute('data-last-export-variant'),
            width: document.documentElement.getAttribute('data-last-export-width'),
            height: document.documentElement.getAttribute('data-last-export-height'),
            canonical: document.documentElement.getAttribute('data-last-export-canonical'),
            routeStateClean: document.documentElement.getAttribute('data-last-export-route-state-clean'),
            reachStateClean: document.documentElement.getAttribute('data-last-export-reach-state-clean'),
            error: document.documentElement.getAttribute('data-last-export-error')
          };

          var parser = new DOMParser();
          var reachSvg = parser.parseFromString(reachSvgText, 'image/svg+xml').documentElement;
          var matchedNodeIds = Array.from(reachSvg.querySelectorAll('[data-node-id][data-share-reach-match]')).map(function (node) {
            return node.getAttribute('data-node-id');
          });
          var matchedEdgeKeys = Array.from(new Set(Array.from(reachSvg.querySelectorAll('[data-edge-key][data-share-reach-match]')).map(function (edge) {
            return edge.getAttribute('data-edge-key');
          })));
          var reachNodeIds = Array.from(reachSvg.querySelectorAll('[data-node-id]')).map(function (node) {
            return node.getAttribute('data-node-id');
          }).sort();
          var liveNodeIds = Array.from(svg.querySelectorAll('[data-node-id]')).map(function (node) {
            return node.getAttribute('data-node-id');
          }).sort();
          var reachEdgeKeys = Array.from(new Set(Array.from(reachSvg.querySelectorAll('[data-edge-key]')).map(function (edge) {
            return edge.getAttribute('data-edge-key');
          }))).sort();
          var liveEdgeKeys = Array.from(new Set(Array.from(svg.querySelectorAll('[data-edge-key]')).map(function (edge) {
            return edge.getAttribute('data-edge-key');
          }))).sort();
          var reachStyles = Array.from(reachSvg.querySelectorAll('style')).map(function (style) { return style.textContent; }).join('\n');

          await new Promise(function (resolve) {
            requestAnimationFrame(function () { requestAnimationFrame(resolve); });
          });
          var liveAfter = stableLiveSnapshot();

          var matrix = [];
          if (${JSON.stringify(options.matrix === true)}) {
            var identity = JSON.stringify(snapshot);
            for (var preset of ['classic', 'signal-flow', 'blueprint', 'editorial']) {
              if (!Archify.preset.apply(preset)) throw new Error('could not apply preset ' + preset);
              for (var theme of ['dark', 'light']) {
                document.documentElement.setAttribute('data-theme', theme);
                var matrixBlob = await Archify.exportMenu.shareCard({ variant: 'reach' });
                var matrixBytes = new Uint8Array(await matrixBlob.arrayBuffer());
                var matrixView = new DataView(matrixBytes.buffer, matrixBytes.byteOffset, matrixBytes.byteLength);
                matrix.push({
                  preset: preset,
                  theme: theme,
                  type: matrixBlob.type,
                  size: matrixBlob.size,
                  width: matrixView.getUint32(16),
                  height: matrixView.getUint32(20),
                  hash: fingerprintBytes(matrixBytes),
                  identity: JSON.stringify(Archify.focus.reachabilitySnapshot())
                });
              }
            }
            if (matrix.some(function (entry) { return entry.identity !== identity; })) {
              throw new Error('visual matrix changed authored reach identity');
            }
          }

          Archify.focus.clearReach({ updateUrl: false });
          Archify.exportMenu.syncReachShare();
          var hiddenAfterClear = reachMenuItem.hidden && reachMenuItem.disabled &&
            getComputedStyle(reachMenuItem).display === 'none';
          var staleError = '';
          try { await Archify.exportMenu.shareCard({ variant: 'reach' }); }
          catch (error) { staleError = String(error && error.message || error); }
          await Archify.exportMenu.downloadReachShareCard();
          var failedReceipt = {
            format: document.documentElement.getAttribute('data-last-export-format'),
            variant: document.documentElement.getAttribute('data-last-export-variant'),
            errorFormat: document.documentElement.getAttribute('data-last-export-error-format'),
            error: document.documentElement.getAttribute('data-last-export-error')
          };
          var canonicalIndex = captured.length;
          var canonicalBlob = await Archify.exportMenu.shareCard();
          var canonicalSvgText = captured[canonicalIndex] ? await captured[canonicalIndex] : '';
          var canonicalSvg = parser.parseFromString(canonicalSvgText, 'image/svg+xml').documentElement;
          var canonicalReachResidue = canonicalSvg.hasAttribute('data-share-reach') ||
            canonicalSvg.hasAttribute('data-reach-active') ||
            canonicalSvg.querySelectorAll('[data-share-reach-match], [data-share-reach-origin], [data-share-reach-depth], [data-reach-match], [data-reach-origin], [data-reach-depth]').length > 0;

          var bytes = new Uint8Array(await blob.arrayBuffer());
          var binary = '';
          for (var offset = 0; offset < bytes.length; offset += 32768) {
            binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768));
          }
          return {
            ok: true,
            type: blob.type,
            size: blob.size,
            base64: btoa(binary),
            snapshot: snapshot,
            matchedNodeIds: matchedNodeIds,
            matchedEdgeKeys: matchedEdgeKeys,
            expectedEdgeKeys: snapshot.edges.map(function (edge) { return edge.key; }),
            reachDirection: reachSvg.getAttribute('data-share-reach'),
            originCount: reachSvg.querySelectorAll('[data-node-id][data-share-reach-origin]').length,
            reachLiveResidue: reachSvg.hasAttribute('data-reach-active') || reachSvg.querySelectorAll('[data-reach-match], [data-reach-origin], [data-reach-depth]').length > 0,
            routeResidue: reachSvg.hasAttribute('data-share-route') || reachSvg.querySelectorAll('[data-share-route-match], [data-share-route-step], [data-route-match], [data-route-step]').length > 0,
            motionResidue: reachSvg.hasAttribute('data-animation') || reachSvg.querySelectorAll('[data-animate]').length > 0,
            blueprintStaticRule: /data-preset=\"blueprint\"\]\[data-share-reach\][^}]*filter:\s*none/.test(reachStyles),
            reachNodeIds: reachNodeIds,
            liveNodeIds: liveNodeIds,
            reachEdgeKeys: reachEdgeKeys,
            liveEdgeKeys: liveEdgeKeys,
            liveUnchanged: liveBefore === liveAfter,
            menuResolved: menuResolved,
            duplicateGeometryRejected: duplicateGeometryRejected,
            duplicateExportError: duplicateExportError,
            downloadStable: downloadedBlob && downloadedBlob.type === 'image/png',
            downloads: downloads,
            reachReceipt: reachReceipt,
            hiddenAfterClear: hiddenAfterClear,
            staleSnapshot: Archify.focus.reachabilitySnapshot(),
            staleError: staleError,
            failedReceipt: failedReceipt,
            canonicalSize: canonicalBlob.size,
            canonicalReachResidue: canonicalReachResidue,
            matrix: matrix
          };
        } finally {
          URL.createObjectURL = originalCreateObjectURL;
          HTMLAnchorElement.prototype.click = originalAnchorClick;
        }
      } catch (error) {
        return { ok: false, error: String(error && error.message || error) };
      }
    })()`, true), options.matrix ? 35_000 : 15_000, `${label} Reach Card export`);

    assert.equal(reachPayload?.ok, true, reachPayload?.error || `${label} Reach Card export failed`);
    assert.equal(reachPayload.type, 'image/png');
    assert.ok(reachPayload.size > 20_000, `${label} Reach Card is unexpectedly small (${reachPayload.size} bytes)`);
    const png = Buffer.from(reachPayload.base64, 'base64');
    assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a', `${label} Reach Card is not a PNG`);
    assert.equal(png.readUInt32BE(16), 1200, `${label} Reach Card width`);
    assert.equal(png.readUInt32BE(20), 630, `${label} Reach Card height`);
    if (options.outputPath) {
      fs.mkdirSync(path.dirname(options.outputPath), { recursive: true });
      fs.writeFileSync(options.outputPath, png);
    }
    assert.equal(reachPayload.reachDirection, direction);
    assert.deepEqual(reachPayload.matchedNodeIds.slice().sort(), reachPayload.snapshot.nodeIds.slice().sort());
    assert.deepEqual(reachPayload.matchedEdgeKeys.slice().sort(), reachPayload.expectedEdgeKeys.slice().sort());
    assert.equal(reachPayload.originCount, 1);
    assert.equal(reachPayload.reachLiveResidue, false);
    assert.equal(reachPayload.routeResidue, false);
    assert.equal(reachPayload.motionResidue, false);
    assert.equal(reachPayload.blueprintStaticRule, true);
    assert.deepEqual(reachPayload.reachNodeIds, reachPayload.liveNodeIds);
    assert.deepEqual(reachPayload.reachEdgeKeys, reachPayload.liveEdgeKeys);
    assert.equal(reachPayload.liveUnchanged, true);
    assert.equal(reachPayload.menuResolved, true);
    assert.equal(reachPayload.duplicateGeometryRejected, true);
    assert.match(reachPayload.duplicateExportError, /Trace authored reach before exporting a Reach Share Card/);
    assert.equal(reachPayload.downloadStable, true);
    assert.match(reachPayload.downloads[0], new RegExp(`-${direction}-reach-share-card\\.png$`));
    assert.deepEqual(reachPayload.reachReceipt, {
      format: 'share-card',
      variant: 'reach',
      width: '1200',
      height: '630',
      canonical: 'false',
      routeStateClean: null,
      reachStateClean: 'true',
      error: null,
    });
    assert.equal(reachPayload.hiddenAfterClear, true);
    assert.equal(reachPayload.staleSnapshot, null);
    assert.match(reachPayload.staleError, /Trace authored reach before exporting a Reach Share Card/);
    assert.equal(reachPayload.failedReceipt.format, null);
    assert.equal(reachPayload.failedReceipt.variant, null);
    assert.equal(reachPayload.failedReceipt.errorFormat, 'share-card');
    assert.match(reachPayload.failedReceipt.error, /Trace authored reach before exporting a Reach Share Card/);
    assert.ok(reachPayload.canonicalSize > 20_000);
    assert.equal(reachPayload.canonicalReachResidue, false);
    if (options.matrix) {
      assert.equal(reachPayload.matrix.length, 8);
      assert.equal(new Set(reachPayload.matrix.map((entry) => entry.hash)).size, 8, `${label} Reach presets/themes should produce eight distinct PNGs`);
      for (const entry of reachPayload.matrix) {
        assert.equal(entry.type, 'image/png');
        assert.equal(entry.width, 1200);
        assert.equal(entry.height, 630);
        assert.ok(entry.size > 20_000);
      }
    }
    console.log(`ok ${label} Reach Card: ${reachPayload.size} bytes, ${reachPayload.snapshot.nodeIds.length} nodes, ${reachPayload.snapshot.edges.length} authored links`);
  }

  await verifyResolvedLegendContract(legendOutputs);
  await verifySemanticPassportDismissal(path.resolve(skillRoot, '../docs/gallery/artifacts/production-deployment.architecture.html'));
  await verifyArchitectureDeltaNavigator(path.resolve(skillRoot, '../examples/checkout-platform-delta.html'));
  await captureShareCard(output, 'architecture-wide');
  await captureShareCard(sequenceOutput, 'sequence-tall');
  await captureCopiedShareCard(output, 'architecture-wide');
  await captureRouteShareCard(routeOutputs.architecture, 'architecture-route', 'users', 'api', { journeyInvariance: true });
  await captureRouteShareCard(routeOutputs.workflow, 'workflow-route', 'user', 'approval');
  await captureRouteShareCard(routeOutputs.sequence, 'sequence-route', 'web', 'db');
  await captureRouteShareCard(routeOutputs.dataflow, 'dataflow-route', 'web', 'dashboard');
  await captureRouteShareCard(routeOutputs.lifecycle, 'lifecycle-route', 'executing', 'cancelled');
  await captureRouteShareCard(parallelOutput, 'parallel-route', 'users', 'cdn');
  await captureRouteShareCard(specialRouteOutput, 'special-10-hop-route', 'Route_Source-01', 'Route_Target-10', {
    expectedHops: 10,
    expectedSourceLabel: specialSourceLabel,
    expectedTargetLabel: specialTargetLabel,
  });
  await captureRouteVisualMatrix(routeOutputs.architecture, 'architecture-route', 'users', 'api');
  await captureRouteVisualMatrix(routeOutputs.sequence, 'sequence-route', 'web', 'db');
  await captureReachShareCard(routeOutputs.architecture, 'architecture-reach', 'users', 'downstream', { matrix: true });
  await captureReachShareCard(routeOutputs.workflow, 'workflow-reach', 'user', 'downstream');
  await captureReachShareCard(routeOutputs.sequence, 'sequence-reach', 'web', 'downstream');
  await captureReachShareCard(routeOutputs.dataflow, 'dataflow-reach', 'web', 'downstream');
  await captureReachShareCard(routeOutputs.lifecycle, 'lifecycle-reach', 'executing', 'downstream');
  if (externalReachSource) {
    assert.ok(fs.existsSync(externalReachSource), `external Reach Card source does not exist: ${externalReachSource}`);
    await captureReachShareCard(
      externalReachSource,
      'external-reach',
      process.env.ARCHIFY_REACH_CARD_ORIGIN || 'router',
      process.env.ARCHIFY_REACH_CARD_DIRECTION || 'downstream',
      { outputPath: externalReachOutput },
    );
    console.log(`ok external Reach Card artifact: ${externalReachOutput}`);
  }
  await verifyDynamicReducedMotionRoute(routeOutputs.architecture, 'architecture-route reduced motion', 'users', 'api');
  await navigateReady(output, '!!(window.Archify && Archify.motion && Archify.motion.canRecord())', 'motion artifact');

  const payload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () {
    try {
      var blob = await Archify.motion.recordWebm({ duration: 1400, fps: 12 });
      var bytes = new Uint8Array(await blob.arrayBuffer());
      var binary = '';
      for (var offset = 0; offset < bytes.length; offset += 32768) {
        binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768));
      }
      return { ok: true, type: blob.type, size: blob.size, base64: btoa(binary) };
    } catch (error) {
      return { ok: false, error: String(error && error.message || error) };
    }
  })()`, true), 20_000, 'WebM recording');
  assert.equal(payload?.ok, true, payload?.error || 'WebM recording failed');
  assert.match(payload.type, /^video\/webm/);
  assert.ok(payload.size > 10_000, `WebM is unexpectedly small (${payload.size} bytes)`);

  const webm = path.join(tmp, 'motion.webm');
  fs.writeFileSync(webm, Buffer.from(payload.base64, 'base64'));
  const frameMd5 = execFileSync(ffmpeg, [
    '-v', 'error',
    '-i', webm,
    '-vf', 'fps=6,scale=320:-2',
    '-f', 'framemd5',
    '-',
  ], { encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 });
  const hashes = frameMd5
    .split('\n')
    .filter((line) => line && !line.startsWith('#'))
    .map((line) => line.split(',').at(-1).trim());
  const uniqueFrames = new Set(hashes);

  assert.ok(hashes.length >= 4, `decoded only ${hashes.length} WebM frames`);
  assert.ok(uniqueFrames.size >= 2, 'decoded WebM frames are static');
  console.log(`ok WebM artifact: ${payload.size} bytes, ${hashes.length} sampled frames, ${uniqueFrames.size} unique`);
} finally {
  if (cdp && targetId) await withTimeout(cdp.send('Target.closeTarget', { targetId }), 500, 'target close').catch(() => {});
  if (cdp) cdp.socket.close();
  chromeProcess.kill('SIGTERM');
  if (!(await waitForExit(chromeProcess, 1000))) {
    chromeProcess.kill('SIGKILL');
    await waitForExit(chromeProcess, 1000);
  }
  await removeTempTree(tmp);
}
```

## test/workflow-compiler-hard-contract.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';

import { compileWorkflow } from '../renderers/workflow/workflow-compiler.mjs';

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

function workflow({ lanes, nodes, edges }) {
  return {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: {
      title: 'Workflow compiler hard-contract fixture',
      legend: { mode: 'hidden' },
    },
    lanes,
    nodes,
    edges,
  };
}

function oneLaneWorkflow(edges) {
  return workflow({
    lanes: [{ id: 'main', label: 'M' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edges,
  });
}

function nestedOutsideRightCorridorWorkflow() {
  return workflow({
    lanes: [0, 1, 2, 3].map((index) => ({ id: `l${index}`, label: `Lane ${index}` })),
    nodes: [
      { id: 'outer-from', lane: 'l0', col: 0, type: 'backend', label: 'Outer from' },
      { id: 'inner-from', lane: 'l1', col: 0, type: 'backend', label: 'Inner from' },
      { id: 'inner-to', lane: 'l2', col: 2, type: 'database', label: 'Inner to' },
      { id: 'outer-to', lane: 'l3', col: 2, type: 'database', label: 'Outer to' },
    ],
    edges: [
      {
        id: 'outer',
        from: 'outer-from',
        to: 'outer-to',
        route: 'outside-right',
        channelX: 800,
        fromSide: 'right',
        toSide: 'right',
      },
      {
        id: 'inner',
        from: 'inner-from',
        to: 'inner-to',
        route: 'outside-right',
        channelX: 800,
        fromSide: 'right',
        toSide: 'right',
      },
    ],
  });
}

function crossingAtForwardCollinearViaWorkflow({ pinHorizontal = false } = {}) {
  return workflow({
    lanes: [
      { id: 'top', label: 'Top' },
      { id: 'middle', label: 'Middle' },
      { id: 'bottom', label: 'Bottom' },
    ],
    nodes: [
      { id: 'left', lane: 'middle', col: 0, type: 'backend', label: 'Left' },
      { id: 'right', lane: 'middle', col: 4, type: 'backend', label: 'Right' },
      { id: 'above', lane: 'top', col: 2, type: 'backend', label: 'Above' },
      { id: 'below', lane: 'bottom', col: 2, type: 'backend', label: 'Below' },
    ],
    edges: [
      {
        id: pinHorizontal ? 'a-pinned' : 'a-auto',
        from: 'left',
        to: 'right',
        ...(pinHorizontal ? { via: [[334, 243]] } : {}),
      },
      {
        id: 'z-pinned',
        from: 'above',
        to: 'below',
        via: [[334, 243]],
      },
    ],
  });
}

function oneLaneAnchors() {
  const result = compileWorkflow({ workflow: oneLaneWorkflow([]), qualityProfile: 'standard' });
  assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
  const source = result.receipt.nodes.find(({ id }) => id === 'a');
  const target = result.receipt.nodes.find(({ id }) => id === 'b');
  assert.ok(source && target);
  return {
    start: [source.x + source.width, source.y + source.height / 2],
    end: [target.x, target.y + target.height / 2],
  };
}

function assertExplicitPinConflict(result, context) {
  assert.equal(result.ok, false, `${context} must not produce an SVG`);
  assert.equal(result.svg, undefined);
  assert.ok(Array.isArray(result.diagnostics) && result.diagnostics.length > 0);
  assert.ok(
    result.diagnostics.some(({ code }) => code === 'workflow/explicit-pin-conflict'),
    `${context} must report workflow/explicit-pin-conflict:\n${JSON.stringify(result.diagnostics, null, 2)}`,
  );
  assert.deepEqual(result.receipt.diagnostics, result.diagnostics);
}

function assertSupportedFixesNameChangedEdge(diagnostic, edgeIds) {
  assert.ok(diagnostic.supportedFixes.length > 0, JSON.stringify(diagnostic, null, 2));
  for (const fix of diagnostic.supportedFixes) {
    assert.ok(
      edgeIds.some((edgeId) => fix.includes(`edge "${edgeId}"`)),
      `supported fix must name its changed edge (${edgeIds.join(', ')}): ${fix}`,
    );
  }
}

function assertOrthogonal(points) {
  for (let index = 0; index < points.length - 1; index += 1) {
    const [x1, y1] = points[index];
    const [x2, y2] = points[index + 1];
    assert.ok(x1 === x2 || y1 === y2, `segment ${index} must be orthogonal`);
    assert.notDeepEqual(points[index], points[index + 1], `segment ${index} must be non-zero`);
  }
}

function routeContainsChannel(points, field, value) {
  return points.slice(0, -1).some((start, index) => {
    const end = points[index + 1];
    if (field === 'channelX') {
      return start[0] === value && end[0] === value && start[1] !== end[1];
    }
    return start[1] === value && end[1] === value && start[0] !== end[0];
  });
}

function segmentMeetsRect(start, end, rect, clearance = 2) {
  const left = rect.x - clearance;
  const right = rect.x + rect.width + clearance;
  const top = rect.y - clearance;
  const bottom = rect.y + rect.height + clearance;
  if (start[1] === end[1]) {
    return start[1] >= top && start[1] <= bottom
      && Math.max(start[0], end[0]) >= left
      && Math.min(start[0], end[0]) <= right;
  }
  if (start[0] === end[0]) {
    return start[0] >= left && start[0] <= right
      && Math.max(start[1], end[1]) >= top
      && Math.min(start[1], end[1]) <= bottom;
  }
  return true;
}

test('readable-v2 auto routing selects a feasible corridor around a same-lane obstacle', () => {
  const document = workflow({
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'obstacle', lane: 'main', col: 2, type: 'database', label: 'Obstacle' },
      { id: 'b', lane: 'main', col: 5, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b' }],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(
    result.ok,
    true,
    `a feasible automatic corridor must compile:\n${JSON.stringify(result.diagnostics, null, 2)}`,
  );

  const points = result.receipt.edges.find(({ id }) => id === 'ab')?.points;
  const obstacle = result.receipt.nodes.find(({ id }) => id === 'obstacle');
  assert.ok(points && obstacle);
  assert.ok(points.length >= 3, 'the route must bend around the intervening node');
  assertOrthogonal(points);
  for (let index = 0; index < points.length - 1; index += 1) {
    assert.equal(
      segmentMeetsRect(points[index], points[index + 1], obstacle),
      false,
      `segment ${index} must clear the unrelated obstacle`,
    );
  }
});

test('readable-v2 evaluates automatic endpoint sides against the complete labeled route', () => {
  const document = workflow({
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 1, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b', label: 'L'.repeat(50) }],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
  assert.deepEqual(result.receipt.viewBox, [768, 404]);
  assert.deepEqual(result.receipt.edges[0].points, [
    [94, 145], [94, 166], [214, 166], [214, 217],
  ]);
});

test('readable-v2 retries automatic endpoint sides against already planned routes', () => {
  const document = workflow({
    lanes: [0, 1, 2].map((index) => ({ id: `l${index}`, label: `Lane ${index}` })),
    nodes: [
      { id: 'a', lane: 'l0', col: 2, type: 'backend', label: 'A' },
      { id: 'b', lane: 'l0', col: 3, type: 'backend', label: 'B', yOffset: -1 },
      { id: 'c', lane: 'l2', col: 4, type: 'backend', label: 'C' },
    ],
    edges: [
      { id: 'e0', from: 'a', to: 'b', label: 'L'.repeat(17) },
      { id: 'e1', from: 'a', to: 'c' },
    ],
  });

  const first = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  const second = compileWorkflow({ workflow: clone(document), qualityProfile: 'standard' });
  assert.equal(first.ok, true, JSON.stringify(first.diagnostics, null, 2));
  assert.equal(second.ok, true, JSON.stringify(second.diagnostics, null, 2));
  assert.equal(second.svg, first.svg);
  assert.equal(JSON.stringify(second.receipt), JSON.stringify(first.receipt));
  assertOrthogonal(first.receipt.edges.find(({ id }) => id === 'e1').points);
});

test('readable-v2 expands an outside corridor for deterministic labeled fan-out', () => {
  const document = workflow({
    lanes: Array.from({ length: 5 }, (_, index) => ({
      id: `l${index}`,
      label: `Lane ${index}`,
    })),
    nodes: [
      { id: 'hub', lane: 'l0', col: 0, type: 'backend', label: 'Hub', width: 64 },
      { id: 't0', lane: 'l1', col: 2, type: 'external', label: 'T0', width: 160 },
      { id: 't1', lane: 'l2', col: 4, type: 'database', label: 'T1', width: 160 },
      { id: 't2', lane: 'l3', col: 3, type: 'external', label: 'T2', width: 64 },
      { id: 't3', lane: 'l4', col: 4, type: 'database', label: 'T3', width: 120 },
    ],
    edges: [
      { id: 'e0', from: 'hub', to: 't0', label: 'A'.repeat(80) },
      { id: 'e1', from: 'hub', to: 't1', label: 'B'.repeat(80) },
      { id: 'e2', from: 'hub', to: 't2', label: 'C'.repeat(12) },
      { id: 'e3', from: 'hub', to: 't3', label: 'D'.repeat(24) },
    ],
  });

  const first = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  const second = compileWorkflow({ workflow: clone(document), qualityProfile: 'standard' });
  assert.equal(first.ok, true, JSON.stringify(first.diagnostics, null, 2));
  assert.deepEqual(second, first);
  const route = first.receipt.edges.find(({ id }) => id === 'e3')?.points;
  assert.ok(route);
  assertOrthogonal(route);
  assert.ok(
    Math.max(...route.map(([x]) => x)) > 764,
    `the final fan-out edge must escape the occupied route/label region: ${JSON.stringify(route)}`,
  );
});

test('readable-v2 expands an outside corridor without moving the lane geometry', () => {
  const document = workflow({
    lanes: Array.from({ length: 5 }, (_, index) => ({
      id: `l${index}`,
      label: `Lane ${index}`,
    })),
    nodes: [
      { id: 'hub', lane: 'l0', col: 1, type: 'backend', label: 'Hub', width: 92 },
      { id: 't0', lane: 'l1', col: 2, type: 'external', label: 'T0', width: 120 },
      { id: 't1', lane: 'l2', col: 1, type: 'database', label: 'T1', width: 160 },
      { id: 't2', lane: 'l3', col: 1, type: 'external', label: 'T2', width: 120 },
      { id: 't3', lane: 'l4', col: 2, type: 'database', label: 'T3', width: 120 },
    ],
    edges: [
      { id: 'e0', from: 'hub', to: 't0', label: 'A'.repeat(120) },
      { id: 'e1', from: 'hub', to: 't1', label: 'B'.repeat(48) },
      { id: 'e2', from: 'hub', to: 't2', label: 'C'.repeat(24) },
      { id: 'e3', from: 'hub', to: 't3', label: 'D'.repeat(84) },
    ],
  });

  const first = compileWorkflow({ workflow: document, qualityProfile: 'showcase' });
  const second = compileWorkflow({ workflow: clone(document), qualityProfile: 'showcase' });
  assert.equal(first.ok, true, JSON.stringify(first.diagnostics, null, 2));
  assert.deepEqual(second, first);
  const route = first.receipt.edges.find(({ id }) => id === 'e3')?.points;
  assert.ok(route);
  assertOrthogonal(route);
  assert.ok(Math.max(...route.map(([x]) => x)) > 1200, JSON.stringify(route));
  assert.ok(first.receipt.viewBox[0] < 1400, JSON.stringify(first.receipt.viewBox));
});

test('readable-v2 may expand an unlabeled edge around already placed labels', () => {
  const document = workflow({
    lanes: Array.from({ length: 4 }, (_, index) => ({
      id: `l${index}`,
      label: `Lane ${index}`,
    })),
    nodes: [
      { id: 'hub', lane: 'l0', col: 0, type: 'backend', label: 'Hub', width: 120 },
      { id: 't0', lane: 'l1', col: 1, type: 'backend', label: 'T0', width: 92, yOffset: -4 },
      { id: 't1', lane: 'l2', col: 3, type: 'backend', label: 'T1', width: 92, yOffset: 4 },
      { id: 't2', lane: 'l3', col: 1, type: 'external', label: 'T2', width: 120, yOffset: -4 },
      { id: 't3', lane: 'l1', col: 5, type: 'backend', label: 'T3', width: 92, yOffset: -12 },
      { id: 't4', lane: 'l2', col: 1, type: 'backend', label: 'T4', width: 120, yOffset: -12 },
    ],
    edges: [
      { id: 'e00', from: 'hub', to: 't0', label: 'Z'.repeat(80) },
      { id: 'e01', from: 'hub', to: 't1', label: 'Q'.repeat(120) },
      { id: 'e02', from: 'hub', to: 't2', label: 'Q'.repeat(120) },
      { id: 'e03', from: 'hub', to: 't3', label: 'Z'.repeat(80) },
      { id: 'e04', from: 'hub', to: 't4' },
    ],
  });

  const first = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  const second = compileWorkflow({ workflow: clone(document), qualityProfile: 'standard' });
  assert.equal(first.ok, true, JSON.stringify(first.diagnostics, null, 2));
  assert.deepEqual(second, first);
  const route = first.receipt.edges.find(({ id }) => id === 'e04')?.points;
  assert.ok(route);
  assertOrthogonal(route);
  assert.ok(Math.max(...route.map(([x]) => x)) > 764, JSON.stringify(route));

  const boundedDocument = clone(document);
  boundedDocument.meta.viewBox = [1378, 692];
  const bounded = compileWorkflow({ workflow: boundedDocument, qualityProfile: 'standard' });
  assert.equal(bounded.ok, true, JSON.stringify(bounded.diagnostics, null, 2));
  assert.deepEqual(bounded.receipt.viewBox, [1378, 692]);
});

test('readable-v2 feeds a measured outside-channel constraint back into layout', () => {
  const document = workflow({
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 0, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b', label: 'L'.repeat(120) }],
  });
  document.meta.quality_profile = 'showcase';

  const first = compileWorkflow({ workflow: document });
  const second = compileWorkflow({ workflow: clone(document) });
  assert.equal(first.ok, true, JSON.stringify(first.diagnostics, null, 2));
  assert.deepEqual(first.receipt.viewBox, [780, 404]);
  assert.deepEqual(first.receipt.requiredViewBox, [780, 404]);
  assert.deepEqual(first.receipt.edges[0].points, [
    [140, 119], [764, 119], [764, 243], [140, 243],
  ]);
  assert.deepEqual(
    { x: first.receipt.labels[0].x, y: first.receipt.labels[0].y },
    { x: 452, y: 109 },
  );
  assert.equal(second.svg, first.svg);
  assert.equal(JSON.stringify(second.receipt), JSON.stringify(first.receipt));
});

test('readable-v2 feeds a measured adjacent-rank gutter back into layout', () => {
  const document = workflow({
    lanes: [{ id: 'l0', label: 'Lane 0' }, { id: 'l1', label: 'Lane 1' }],
    nodes: [
      { id: 'a', lane: 'l0', col: 0, type: 'backend', label: 'A' },
      { id: 'obstacle', lane: 'l0', col: 1, type: 'database', label: 'Obstacle' },
      { id: 'b', lane: 'l1', col: 1, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b', fromSide: 'right', toSide: 'left' }],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
  assert.deepEqual(result.receipt.columns.slice(0, 2), [94, 218]);
  assert.deepEqual(result.receipt.edges[0].points, [
    [140, 119], [156, 119], [156, 243], [172, 243],
  ]);
});

test('readable-v2 feeds a measured lane gap back into layout', () => {
  const document = workflow({
    lanes: [0, 1, 2].map((index) => ({ id: `l${index}`, label: `Lane ${index}` })),
    nodes: [0, 1, 2].map((index) => ({
      id: `n${index}`, lane: `l${index}`, col: 5, type: 'backend', label: `N${index}`,
    })),
    edges: [
      {
        id: 'e0', from: 'n1', to: 'n2', fromSide: 'bottom', toSide: 'right', label: 'LLLLLLLLLL',
      },
      {
        id: 'e2', from: 'n2', to: 'n1', fromSide: 'left', toSide: 'right', label: 'x',
      },
    ],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'showcase' });
  assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
  assert.deepEqual(result.receipt.columns, [94, 214, 334, 454, 574, 694]);
  assert.deepEqual(result.receipt.nodes.map(({ id, y }) => ({ id, y })), [
    { id: 'n0', y: 93 }, { id: 'n1', y: 229 }, { id: 'n2', y: 365 },
  ]);
  assert.deepEqual(result.receipt.edges.map(({ id, points }) => ({ id, points })), [
    {
      id: 'e0',
      points: [[694, 281], [694, 308], [756, 308], [756, 391], [740, 391]],
    },
    {
      id: 'e2',
      points: [[648, 391], [632, 391], [632, 172], [756, 172], [756, 255], [740, 255]],
    },
  ]);
  assert.deepEqual(result.receipt.requiredViewBox, [772, 552]);
});

for (const { name, makeDocument } of [
  {
    name: 'duplicate authored via points',
    makeDocument: () => {
      const { start } = oneLaneAnchors();
      const pin = [start[0] + 60, start[1]];
      return oneLaneWorkflow([{
        id: 'ab', from: 'a', to: 'b', via: [pin, [...pin]],
      }]);
    },
  },
  {
    name: 'diagonal authored via geometry',
    makeDocument: () => {
      const { start } = oneLaneAnchors();
      return oneLaneWorkflow([{
        id: 'ab', from: 'a', to: 'b', via: [[start[0] + 60, start[1] + 31]],
      }]);
    },
  },
]) {
  test(`readable-v2 rejects ${name} with a typed explicit-pin diagnostic`, () => {
    const result = compileWorkflow({ workflow: makeDocument(), qualityProfile: 'standard' });
    assertExplicitPinConflict(result, name);
  });
}

test('readable-v2 duplicate evidence excludes a compatible channel assertion', () => {
  const document = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    via: [[200, 119], [200, 180], [200, 180], [260, 180], [260, 119]],
    channelY: 180,
  }]);
  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assertExplicitPinConflict(result, 'duplicate via with compatible channelY');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'non-zero route segments');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'ab', field: 'via', path: '/edges/0/via',
    value: [[200, 119], [200, 180], [200, 180], [260, 180], [260, 119]],
  }]);
  assert.ok(diagnostic.supportedFixes.length > 0, JSON.stringify(diagnostic, null, 2));
});

test('readable-v2 obstacle evidence excludes a compatible channel assertion', () => {
  const document = workflow({
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'obstacle', lane: 'main', col: 1, type: 'database', label: 'Obstacle' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{
      id: 'ab',
      from: 'a',
      to: 'b',
      via: [[156, 119], [156, 180], [200, 180], [200, 119], [276, 119]],
      channelY: 180,
    }],
  });
  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assertExplicitPinConflict(result, 'node collision with compatible channelY');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'node clearance');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'ab', field: 'via', path: '/edges/0/via',
    value: [[156, 119], [156, 180], [200, 180], [200, 119], [276, 119]],
  }]);
  assert.ok(diagnostic.supportedFixes.length > 0, JSON.stringify(diagnostic, null, 2));
});

test('readable-v2 infers omitted endpoint sides around an authored via', () => {
  const expected = [[94, 93], [94, 77], [334, 77], [334, 93]];
  for (const qualityProfile of ['standard', 'showcase']) {
    for (const sides of [
      {},
      { fromSide: 'top' },
      { toSide: 'top' },
      { fromSide: 'top', toSide: 'top' },
    ]) {
      const document = oneLaneWorkflow([{
        id: 'ab', from: 'a', to: 'b', via: [[94, 77], [334, 77]], ...sides,
      }]);
      const result = compileWorkflow({ workflow: document, qualityProfile });
      assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
      assert.deepEqual(result.receipt.edges[0].points, expected);
    }
  }

  const restricted = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    fromSide: 'right',
    via: [[94, 77], [334, 77]],
  }]);
  assertExplicitPinConflict(
    compileWorkflow({ workflow: restricted, qualityProfile: 'standard' }),
    'authored fromSide must remain a hard restriction',
  );

  const duplicate = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    via: [[94, 77], [94, 77], [334, 77]],
  }]);
  const duplicateResult = compileWorkflow({ workflow: duplicate, qualityProfile: 'standard' });
  assertExplicitPinConflict(duplicateResult, 'inferred top ports around duplicate via geometry');
  assert.equal(duplicateResult.diagnostics[0].evidence.invariant, 'non-zero route segments');
  assert.deepEqual(duplicateResult.diagnostics[0].evidence.from, [94, 77]);
  assert.deepEqual(duplicateResult.diagnostics[0].evidence.to, [94, 77]);
});

test('readable-v2 reports an infeasible authored target side with its verified single-pin repair', () => {
  const document = workflow({
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    nodes: [
      { id: 'a', lane: 'top', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'bottom', col: 0, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b', toSide: 'top' }],
  });
  document.groups = [{
    id: 'g', label: 'Very long group label', lane: 'bottom', fromCol: 0, toCol: 0,
  }];

  const result = compileWorkflow({ workflow: document, qualityProfile: 'showcase' });

  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  assert.equal(result.diagnostics.length, 1, JSON.stringify(result.diagnostics, null, 2));
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/explicit-pin-conflict');
  assert.deepEqual(diagnostic.subject, {
    diagramType: 'workflow',
    edge: 'ab',
    from: 'a',
    to: 'b',
    path: '/edges/0/toSide',
  });
  assert.equal(
    diagnostic.evidence.invariant,
    'readable route feasibility with authored endpoint sides',
  );
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'ab',
    field: 'toSide',
    path: '/edges/0/toSide',
    value: 'top',
  }]);
  assert.deepEqual(diagnostic.supportedFixes, [
    'remove toSide from edge "ab" so readable-v2 can replan the remaining endpoint-side pins',
  ]);
  assert.deepEqual(result.receipt.diagnostics, result.diagnostics);

  const repaired = clone(document);
  delete repaired.edges[0].toSide;
  const verified = compileWorkflow({ workflow: repaired, qualityProfile: 'showcase' });
  assert.equal(verified.ok, true, JSON.stringify(verified.diagnostics, null, 2));
});

test('readable-v2 reports via and fromSide as a minimal conflict with two verified repairs', () => {
  const document = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    fromSide: 'right',
    toSide: 'top',
    via: [[20, 119], [20, 50], [334, 50]],
  }]);

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  assert.equal(result.diagnostics.length, 1, JSON.stringify(result.diagnostics, null, 2));
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/explicit-pin-conflict');
  assert.equal(diagnostic.evidence.invariant, 'perpendicular endpoint-side direction');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [
    {
      edge: 'ab',
      field: 'fromSide',
      path: '/edges/0/fromSide',
      value: 'right',
    },
    {
      edge: 'ab',
      field: 'via',
      path: '/edges/0/via',
      value: [[20, 119], [20, 50], [334, 50]],
    },
  ]);
  assert.deepEqual(diagnostic.supportedFixes, [
    'remove fromSide from edge "ab" and replan the remaining explicit pins',
    'remove via from edge "ab" and replan the remaining explicit pins',
  ]);

  for (const field of ['fromSide', 'via']) {
    const repaired = clone(document);
    delete repaired.edges[0][field];
    const verified = compileWorkflow({ workflow: repaired, qualityProfile: 'standard' });
    assert.equal(
      verified.ok,
      true,
      `removing ${field} must recompile:\n${JSON.stringify(verified.diagnostics, null, 2)}`,
    );
  }
});

test('readable-v2 infers endpoint sides for auto channel pins', () => {
  const cases = [
    {
      edge: { channelY: 77 },
      document: oneLaneWorkflow([]),
      expected: [[94, 93], [94, 77], [334, 77], [334, 93]],
    },
    {
      edge: { channelX: 20 },
      document: workflow({
        lanes: [{ id: 'l0', label: 'L0' }, { id: 'l1', label: 'L1' }],
        nodes: [
          { id: 'a', lane: 'l0', col: 0, type: 'backend', label: 'A' },
          { id: 'b', lane: 'l1', col: 2, type: 'backend', label: 'B' },
        ],
        edges: [],
      }),
      expected: [[48, 119], [20, 119], [20, 243], [288, 243]],
    },
    {
      edge: { channelX: 20, channelY: 181 },
      document: workflow({
        lanes: [{ id: 'l0', label: 'L0' }, { id: 'l1', label: 'L1' }],
        nodes: [
          { id: 'a', lane: 'l0', col: 0, type: 'backend', label: 'A' },
          { id: 'b', lane: 'l1', col: 2, type: 'backend', label: 'B' },
        ],
        edges: [],
      }),
      expected: [[48, 119], [20, 119], [20, 181], [334, 181], [334, 217]],
    },
  ];

  for (const qualityProfile of ['standard', 'showcase']) {
    for (const fixture of cases) {
      fixture.document.edges = [{ id: 'ab', from: 'a', to: 'b', ...fixture.edge }];
      const result = compileWorkflow({ workflow: clone(fixture.document), qualityProfile });
      assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
      assert.deepEqual(result.receipt.edges[0].points, fixture.expected);
      for (const [field, value] of Object.entries(fixture.edge)) {
        assert.ok(routeContainsChannel(result.receipt.edges[0].points, field, value));
      }
    }
  }
});

test('readable-v2 treats an omitted preset side as a solver choice', () => {
  const crossLane = (route, extra = {}) => workflow({
    lanes: [{ id: 'l0', label: 'X' }, { id: 'l1', label: 'Y' }],
    nodes: [
      { id: 'a', lane: 'l0', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'l1', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b', route, ...extra }],
  });
  const cases = [
    {
      route: 'straight',
      document: oneLaneWorkflow([{ id: 'ab', from: 'a', to: 'b', route: 'straight' }]),
      expected: [[140, 119], [288, 119]],
    },
    { route: 'drop', document: crossLane('drop'), expected: [[94, 145], [94, 166], [334, 166], [334, 217]] },
    { route: 'outside-right', document: crossLane('outside-right'), expected: [[140, 119], [764, 119], [764, 243], [380, 243]] },
    { route: 'return-left', document: crossLane('return-left'), expected: [[48, 119], [20, 119], [20, 243], [288, 243]] },
    { route: 'bottom-channel', document: crossLane('bottom-channel'), expected: [[94, 145], [94, 301], [334, 301], [334, 269]] },
    { route: 'up-channel', document: crossLane('up-channel'), expected: [[94, 93], [94, 65], [334, 65], [334, 217]] },
    { route: 'outside-right partial', document: crossLane('outside-right', { fromSide: 'right' }), expected: [[140, 119], [764, 119], [764, 243], [380, 243]] },
  ];

  for (const fixture of cases) {
    const result = compileWorkflow({ workflow: fixture.document, qualityProfile: 'standard' });
    assert.equal(result.ok, true, `${fixture.route}: ${JSON.stringify(result.diagnostics, null, 2)}`);
    assert.deepEqual(result.receipt.edges[0].points, fixture.expected);
  }
});

test('readable-v2 treats an infeasible route preset as a candidate-family conflict, not a coordinate pin', () => {
  const document = workflow({
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b', route: 'straight' }],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/route-preset-conflict');
  assert.ok(diagnostic);
  assert.ok(result.diagnostics.every(({ code }) => code !== 'workflow/explicit-pin-conflict'));
  assert.ok(diagnostic.supportedFixes.length > 0);
  assert.ok(diagnostic.supportedFixes.every((fix) => /^set edge |^remove route /.test(fix)));
  for (const fix of diagnostic.supportedFixes) {
    const repaired = clone(document);
    const preset = fix.match(/verified preset "([^"]+)"/)?.[1];
    if (preset) repaired.edges[0].route = preset;
    else delete repaired.edges[0].route;
    const verified = compileWorkflow({ workflow: repaired, qualityProfile: 'standard' });
    assert.equal(verified.ok, true, `advertised fix must recompile: ${fix}`);
  }
});

test('readable-v2 never accepts a same-lane drop through the preset-only fallback', () => {
  const document = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    route: 'drop',
    fromSide: 'top',
    toSide: 'top',
  }]);
  document.nodes.forEach((node) => { node.yOffset = 20; });

  for (const qualityProfile of ['standard', 'showcase']) {
    const result = compileWorkflow({ workflow: clone(document), qualityProfile });
    assert.equal(result.ok, false);
    assert.equal(result.svg, undefined);
    assert.ok(result.diagnostics.some(({ code }) => code === 'workflow/route-preset-conflict'));
    assert.ok(result.diagnostics.every(({ code }) => code !== 'workflow/explicit-pin-conflict'));
  }
});

test('readable-v2 never silently ignores coordinate pins that conflict with a route preset', () => {
  const cases = [
    {
      name: 'straight with channelY',
      document: oneLaneWorkflow([{
        id: 'ab', from: 'a', to: 'b', route: 'straight', channelY: 300,
      }]),
    },
    {
      name: 'straight with channelX',
      document: oneLaneWorkflow([{
        id: 'ab', from: 'a', to: 'b', route: 'straight', channelX: 300,
      }]),
    },
    {
      name: 'drop with channelX',
      document: workflow({
        lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
        nodes: [
          { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
          { id: 'b', lane: 'target', col: 0, type: 'backend', label: 'B' },
        ],
        edges: [{ id: 'ab', from: 'a', to: 'b', route: 'drop', channelX: 777 }],
      }),
    },
    ...[
      ['outside-right', 'channelY'],
      ['return-left', 'channelY'],
      ['bottom-channel', 'channelX'],
      ['up-channel', 'channelX'],
    ].map(([route, field]) => ({
      name: `${route} with ${field}`,
      document: oneLaneWorkflow([{
        id: 'ab', from: 'a', to: 'b', route, [field]: 300,
      }]),
    })),
  ];

  for (const { name, document } of cases) {
    let result;
    assert.doesNotThrow(() => {
      result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
    }, `${name} must stay inside the public compiler result boundary`);
    assertExplicitPinConflict(result, name);
    const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/explicit-pin-conflict');
    assert.equal(diagnostic.evidence.route, document.edges[0].route);
    assert.ok(diagnostic.evidence.conflictingPins.length > 0);
  }
});

test('readable-v2 preset compatibility reports both authored causal paths and verified repairs', () => {
  const document = oneLaneWorkflow([{
    id: 'ab', from: 'a', to: 'b', route: 'straight', channelY: 300,
  }]);
  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assertExplicitPinConflict(result, 'straight preset with channelY');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'route preset compatibility');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [
    {
      edge: 'ab', field: 'route', path: '/edges/0/route', value: 'straight',
    },
    {
      edge: 'ab', field: 'channelY', path: '/edges/0/channelY', value: 300,
    },
  ]);
  assert.deepEqual(diagnostic.supportedFixes, [
    'remove route from edge "ab" and keep the remaining verified route assertions',
    'remove channelY from edge "ab" and keep the remaining verified route assertions',
  ]);

  for (const field of ['route', 'channelY']) {
    const repaired = clone(document);
    delete repaired.edges[0][field];
    const verified = compileWorkflow({ workflow: repaired, qualityProfile: 'standard' });
    assert.equal(
      verified.ok,
      true,
      `removing ${field} must recompile:\n${JSON.stringify(verified.diagnostics, null, 2)}`,
    );
  }
});

test('readable-v2 rejects a raw via that does not belong to its route preset family', () => {
  const { start, end } = oneLaneAnchors();
  const document = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    route: 'straight',
    via: [
      [start[0] + 20, start[1]],
      [start[0] + 20, start[1] + 40],
      [end[0] - 20, start[1] + 40],
      [end[0] - 20, end[1]],
    ],
  }]);

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assertExplicitPinConflict(result, 'straight preset with a detouring via');
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/explicit-pin-conflict');
  assert.equal(diagnostic.evidence.route, 'straight');
  assert.equal(diagnostic.evidence.invariant, 'route preset compatibility');
});

for (const fixture of [
  {
    preset: 'straight',
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edge: { via: [[180, 119], [240, 119]] },
    expected: [[140, 119], [180, 119], [240, 119], [288, 119]],
  },
  {
    preset: 'drop',
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 2, type: 'backend', label: 'B' },
    ],
    edge: { fromSide: 'bottom', toSide: 'top', via: [[94, 181], [334, 181]] },
    expected: [[94, 145], [94, 181], [334, 181], [334, 217]],
  },
  {
    preset: 'outside-right',
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 2, type: 'backend', label: 'B' },
    ],
    edge: { fromSide: 'right', toSide: 'right', via: [[720, 119], [720, 243]] },
    expected: [[140, 119], [720, 119], [720, 243], [380, 243]],
  },
  {
    preset: 'return-left',
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 2, type: 'backend', label: 'B' },
    ],
    edge: { fromSide: 'left', toSide: 'left', via: [[20, 119], [20, 243]] },
    expected: [[48, 119], [20, 119], [20, 243], [288, 243]],
  },
  {
    preset: 'bottom-channel',
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edge: { fromSide: 'bottom', toSide: 'bottom', via: [[94, 200], [334, 200]] },
    expected: [[94, 145], [94, 200], [334, 200], [334, 145]],
  },
  {
    preset: 'up-channel',
    lanes: [{ id: 'main', label: 'M' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edge: { fromSide: 'top', toSide: 'top', via: [[94, 20], [334, 20]] },
    expected: [[94, 93], [94, 20], [334, 20], [334, 93]],
  },
]) {
  test(`readable-v2 preserves a compatible ${fixture.preset} via without normalization`, () => {
    const document = workflow({
      lanes: fixture.lanes,
      nodes: fixture.nodes,
      edges: [{
        id: 'ab', from: 'a', to: 'b', route: fixture.preset, ...fixture.edge,
      }],
    });
    const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
    assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
    assert.deepEqual(result.receipt.edges[0].points, fixture.expected);
  });
}

test('readable-v2 keeps a compatible channel pin authoritative inside its route preset', () => {
  const document = workflow({
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{
      id: 'ab',
      from: 'a',
      to: 'b',
      route: 'drop',
      channelY: 181,
      fromSide: 'bottom',
      toSide: 'top',
    }],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
  const points = result.receipt.edges.find(({ id }) => id === 'ab').points;
  assert.ok(
    points.slice(0, -1).some((point, index) => (
      point[1] === 181 && points[index + 1][1] === 181
    )),
    `the final path must contain the authored channelY: ${JSON.stringify(points)}`,
  );
});

for (const fixture of [
  {
    preset: 'outside-right', field: 'channelX', value: 720,
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 2, type: 'backend', label: 'B' },
    ],
    sides: { fromSide: 'right', toSide: 'right' },
  },
  {
    preset: 'return-left', field: 'channelX', value: 20,
    lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
    nodes: [
      { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'target', col: 2, type: 'backend', label: 'B' },
    ],
    sides: { fromSide: 'left', toSide: 'left' },
  },
  {
    preset: 'bottom-channel', field: 'channelY', value: 200,
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    sides: { fromSide: 'bottom', toSide: 'bottom' },
  },
  {
    preset: 'up-channel', field: 'channelY', value: 20,
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    sides: { fromSide: 'top', toSide: 'top' },
  },
]) {
  test(`readable-v2 keeps ${fixture.field} authoritative for ${fixture.preset}`, () => {
    const document = workflow({
      lanes: fixture.lanes,
      nodes: fixture.nodes,
      edges: [{
        id: 'ab',
        from: 'a',
        to: 'b',
        route: fixture.preset,
        [fixture.field]: fixture.value,
        ...fixture.sides,
      }],
    });
    const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
    assert.equal(result.ok, true, JSON.stringify(result.diagnostics, null, 2));
    const points = result.receipt.edges[0].points;
    assert.ok(routeContainsChannel(points, fixture.field, fixture.value));
  });
}

for (const fixture of [
  {
    preset: 'outside-right', field: 'channelX', value: 20,
    sides: { fromSide: 'left', toSide: 'left' },
    crossLane: true,
  },
  {
    preset: 'return-left', field: 'channelX', value: 720,
    sides: { fromSide: 'right', toSide: 'right' },
    crossLane: true,
  },
  {
    preset: 'bottom-channel', field: 'channelY', value: 20,
    sides: { fromSide: 'top', toSide: 'top' },
  },
  {
    preset: 'up-channel', field: 'channelY', value: 340,
    sides: { fromSide: 'bottom', toSide: 'bottom' },
  },
  {
    preset: 'drop', field: 'channelY', value: 20,
    sides: { fromSide: 'top', toSide: 'top' },
  },
]) {
  test(`readable-v2 rejects ${fixture.preset} when its channel belongs to another route family`, () => {
    for (const qualityProfile of ['standard', 'showcase']) {
      const edges = [{
        id: 'ab',
        from: 'a',
        to: 'b',
        route: fixture.preset,
        [fixture.field]: fixture.value,
        ...fixture.sides,
      }];
      const document = fixture.crossLane
        ? workflow({
          lanes: [{ id: 'source', label: 'Source' }, { id: 'target', label: 'Target' }],
          nodes: [
            { id: 'a', lane: 'source', col: 0, type: 'backend', label: 'A' },
            { id: 'b', lane: 'target', col: 2, type: 'backend', label: 'B' },
          ],
          edges,
        })
        : oneLaneWorkflow(edges);
      const result = compileWorkflow({ workflow: document, qualityProfile });
      assertExplicitPinConflict(result, `${fixture.preset} ${fixture.field} under ${qualityProfile}`);
      const diagnostic = result.diagnostics.find(({ code }) => (
        code === 'workflow/explicit-pin-conflict'
      ));
      assert.equal(diagnostic.evidence.invariant, 'route preset compatibility');
    }
  });
}

test('readable-v2 treats channels beside via as assertions over the authored path', () => {
  const { start, end } = oneLaneAnchors();
  const document = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    channelX: start[0] + 40,
    channelY: start[1] + 61,
    via: [
      [start[0] + 40, start[1]],
      [start[0] + 40, start[1] + 61],
      [end[0] - 48, start[1] + 61],
      [end[0] - 48, end[1]],
    ],
  }]);

  const matching = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(matching.ok, true, JSON.stringify(matching.diagnostics, null, 2));
  assert.deepEqual(matching.receipt.edges[0].points, [start, ...document.edges[0].via, end]);

  const mismatched = clone(document);
  mismatched.edges[0].channelX += 1;
  const result = compileWorkflow({ workflow: mismatched, qualityProfile: 'standard' });
  assertExplicitPinConflict(result, 'via missing its asserted channelX');
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/explicit-pin-conflict');
  assert.equal(diagnostic.evidence.invariant, 'channel pin preservation');
  assert.deepEqual(
    diagnostic.evidence.conflictingPins.map(({ edge, field, path }) => ({ edge, field, path })),
    [
      { edge: 'ab', field: 'via', path: '/edges/0/via' },
      { edge: 'ab', field: 'channelX', path: '/edges/0/channelX' },
    ],
  );

  const nearButNotExact = clone(document);
  nearButNotExact.edges[0].channelX += 0.00005;
  const nearResult = compileWorkflow({
    workflow: nearButNotExact,
    qualityProfile: 'standard',
  });
  assertExplicitPinConflict(nearResult, 'channel assertions must preserve exact authored numbers');
  assert.equal(nearResult.diagnostics[0].evidence.invariant, 'channel pin preservation');
});

test('readable-v2 reports via and channelY as a minimal assertion conflict with two verified repairs', () => {
  const document = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    via: [[94, 77], [334, 77]],
    channelY: 200,
  }]);

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  assert.equal(result.diagnostics.length, 1, JSON.stringify(result.diagnostics, null, 2));
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/explicit-pin-conflict');
  assert.equal(diagnostic.evidence.invariant, 'channel pin preservation');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [
    {
      edge: 'ab',
      field: 'via',
      path: '/edges/0/via',
      value: [[94, 77], [334, 77]],
    },
    {
      edge: 'ab',
      field: 'channelY',
      path: '/edges/0/channelY',
      value: 200,
    },
  ]);
  assert.deepEqual(diagnostic.supportedFixes, [
    'remove via from edge "ab" and replan the remaining explicit route assertions',
    'remove channelY from edge "ab" and replan the remaining explicit route assertions',
  ]);

  for (const field of ['via', 'channelY']) {
    const repaired = clone(document);
    delete repaired.edges[0][field];
    const verified = compileWorkflow({ workflow: repaired, qualityProfile: 'standard' });
    assert.equal(
      verified.ok,
      true,
      `removing ${field} must recompile:\n${JSON.stringify(verified.diagnostics, null, 2)}`,
    );
  }
});

test('readable-v2 reports conflicts between absolute label and route pins as typed geometry', () => {
  const document = workflow({
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    nodes: [
      { id: 'a', lane: 'top', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'top', col: 4, type: 'backend', label: 'B' },
      { id: 'c', lane: 'bottom', col: 0, type: 'backend', label: 'C' },
      { id: 'd', lane: 'bottom', col: 4, type: 'backend', label: 'D' },
    ],
    edges: [
      { id: 'labelpin', from: 'a', to: 'b', label: 'PIN', labelAt: [334, 243] },
      { id: 'routepin', from: 'c', to: 'd', via: [[334, 243]] },
    ],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'showcase' });
  assertExplicitPinConflict(result, 'labelAt crossing an authored via');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit label-route clearance');
  assert.deepEqual(diagnostic.evidence.conflictingPins.map(({ edge, field }) => ({ edge, field })), [
    { edge: 'labelpin', field: 'labelAt' },
    { edge: 'routepin', field: 'via' },
  ]);
  assert.equal(diagnostic.evidence.clearancePx, 0);
  assertSupportedFixesNameChangedEdge(diagnostic, ['labelpin', 'routepin']);
  assert.ok(
    diagnostic.supportedFixes.some((fix) => fix.includes('edge "routepin"')),
    `a routepin repair must identify routepin: ${diagnostic.supportedFixes}`,
  );
  assert.doesNotMatch(diagnostic.supportedFixes.join('\n'), /remove (?:one |the )?label(?!At)/i);
});

test('readable-v2 derives label-route pins from joint verified removals when route plans first', () => {
  const document = workflow({
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    nodes: [
      { id: 'a', lane: 'top', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'top', col: 4, type: 'backend', label: 'B' },
      { id: 'c', lane: 'bottom', col: 0, type: 'backend', label: 'C' },
      { id: 'd', lane: 'bottom', col: 4, type: 'backend', label: 'D' },
    ],
    edges: [
      { id: 'a-route', from: 'c', to: 'd', via: [[334, 243]] },
      { id: 'z-label', from: 'a', to: 'b', label: 'PIN', labelAt: [334, 243] },
    ],
  });
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'route-first joint label-route conflict');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit label-route clearance');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'z-label', field: 'labelAt', path: '/edges/1/labelAt', value: [334, 243],
  }]);
  assert.ok(diagnostic.supportedFixes.length > 0, JSON.stringify(diagnostic, null, 2));
  for (const fix of diagnostic.supportedFixes) {
    const replacement = fix.match(/^set labelAt on edge "z-label" to \[(-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?)\]$/);
    assert.ok(replacement, `route removal is not causal and must not be advertised: ${fix}`);
    const repaired = clone(document);
    repaired.edges[1].labelAt = replacement.slice(1).map(Number);
    const verified = compileWorkflow({ workflow: repaired });
    assert.equal(verified.ok, true, `advertised label repair must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`);
  }
});

test('readable-v2 derives both causal pins when label plans before the authored route', () => {
  const document = workflow({
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    nodes: [
      { id: 'a', lane: 'top', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'top', col: 4, type: 'backend', label: 'B' },
      { id: 'c', lane: 'bottom', col: 0, type: 'backend', label: 'C' },
      { id: 'd', lane: 'bottom', col: 4, type: 'backend', label: 'D' },
    ],
    edges: [
      { id: 'z-route', from: 'c', to: 'd', via: [[334, 243]] },
      { id: 'a-label', from: 'a', to: 'b', label: 'PIN', labelAt: [334, 243] },
    ],
  });
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'label-first joint label-route conflict');
  const diagnostic = result.diagnostics[0];
  assert.deepEqual(diagnostic.evidence.conflictingPins, [
    { edge: 'a-label', field: 'labelAt', path: '/edges/1/labelAt', value: [334, 243] },
    { edge: 'z-route', field: 'via', path: '/edges/0/via', value: [[334, 243]] },
  ]);
  assert.ok(
    diagnostic.supportedFixes.some((fix) => fix.startsWith('set labelAt on edge "a-label"')),
    JSON.stringify(diagnostic.supportedFixes, null, 2),
  );
  assert.ok(
    diagnostic.supportedFixes.includes('remove via from edge "z-route" so readable-v2 can replan the remaining authored label-route pins'),
    JSON.stringify(diagnostic.supportedFixes, null, 2),
  );
});

test('readable-v2 classifies an authored labelAt colliding with an earlier automatic route', () => {
  const document = workflow({
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    nodes: [
      { id: 'a', lane: 'top', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'top', col: 4, type: 'backend', label: 'B' },
      { id: 'c', lane: 'bottom', col: 0, type: 'backend', label: 'C' },
      { id: 'd', lane: 'bottom', col: 4, type: 'backend', label: 'D' },
    ],
    edges: [
      { id: 'a-route', from: 'c', to: 'd' },
      { id: 'z-label', from: 'a', to: 'b', label: 'PIN', labelAt: [334, 243] },
    ],
  });
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'labelAt colliding with an earlier automatic route');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit label-route clearance');
  assert.equal(diagnostic.subject.path, '/edges/1/labelAt');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'z-label',
    field: 'labelAt',
    path: '/edges/1/labelAt',
    value: [334, 243],
  }]);
  assert.deepEqual(diagnostic.evidence.labelAt, [334, 243]);
  assert.deepEqual(diagnostic.evidence.collidedRoute, {
    edge: 'a-route',
    from: 'c',
    to: 'd',
    points: [[140, 243], [528, 243]],
  });
  assert.deepEqual(diagnostic.evidence.routeSegment, {
    from: [140, 243],
    to: [528, 243],
  });

  for (const fix of diagnostic.supportedFixes) {
    const replacement = fix.match(/^set labelAt on edge "z-label" to \[(-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?)\]$/);
    assert.ok(replacement, `only a concrete labelAt alternative may be advertised: ${fix}`);
    const repaired = clone(document);
    repaired.edges[1].labelAt = replacement.slice(1).map(Number);
    const verified = compileWorkflow({ workflow: repaired });
    assert.equal(verified.ok, true, `advertised labelAt alternative must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`);
  }
});

test('readable-v2 classifies an automatic label colliding with a later authored route', () => {
  const document = workflow({
    lanes: [
      { id: 'top', label: 'Top' },
      { id: 'mid', label: 'Mid' },
      { id: 'bottom', label: 'Bottom' },
    ],
    nodes: [
      { id: 'a', lane: 'mid', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'mid', col: 4, type: 'backend', label: 'B' },
      { id: 'c', lane: 'top', col: 2, type: 'backend', label: 'C' },
      { id: 'd', lane: 'bottom', col: 2, type: 'backend', label: 'D' },
    ],
    edges: [
      { id: 'a-label', from: 'a', to: 'b', label: 'AUTO' },
      { id: 'z-route', from: 'c', to: 'd', via: [[334, 243]] },
    ],
  });
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'automatic label colliding with a later authored route');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit label-route clearance');
  assert.equal(diagnostic.subject.path, '/edges/1/via');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'z-route',
    field: 'via',
    path: '/edges/1/via',
    value: [[334, 243]],
  }]);
  assert.deepEqual(diagnostic.evidence.collidedRoute, {
    edge: 'z-route',
    from: 'c',
    to: 'd',
    points: [[334, 145], [334, 243], [334, 341]],
  });
  assert.deepEqual(diagnostic.evidence.routeSegment, {
    from: [334, 145],
    to: [334, 341],
  });

  assert.ok(diagnostic.supportedFixes.length > 0, JSON.stringify(diagnostic, null, 2));
  for (const fix of diagnostic.supportedFixes) {
    assert.equal(
      fix,
      'remove via from edge "z-route" so readable-v2 can replan the remaining authored route assertions',
    );
    const repaired = clone(document);
    delete repaired.edges[1].via;
    const verified = compileWorkflow({ workflow: repaired });
    assert.equal(verified.ok, true, `advertised route alternative must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`);
  }
});

test('readable-v2 classifies an authored labelAt colliding with an earlier automatic label', () => {
  const document = oneLaneWorkflow([
    { id: 'a-auto', from: 'a', to: 'b', label: 'AUTO' },
    { id: 'z-pin', from: 'a', to: 'b', label: 'PIN', labelAt: [214, 119] },
  ]);
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'labelAt colliding with an earlier automatic label');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit label-label clearance');
  assert.equal(diagnostic.subject.path, '/edges/1/labelAt');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'z-pin',
    field: 'labelAt',
    path: '/edges/1/labelAt',
    value: [214, 119],
  }]);
  assert.deepEqual(diagnostic.evidence.labelRects.map(({ edge, ...rect }) => ({ edge, ...rect })), [
    { edge: 'z-pin', x: 199, y: 109, width: 30, height: 14 },
    { edge: 'a-auto', x: 199, y: 99, width: 30, height: 14 },
  ]);

  for (const fix of diagnostic.supportedFixes) {
    const replacement = fix.match(/^set labelAt on edge "z-pin" to \[(-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?)\]$/);
    assert.ok(replacement, `only a concrete labelAt alternative may be advertised: ${fix}`);
    const repaired = clone(document);
    repaired.edges[1].labelAt = replacement.slice(1).map(Number);
    const verified = compileWorkflow({ workflow: repaired });
    assert.equal(verified.ok, true, `advertised labelAt alternative must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`);
  }
});

test('readable-v2 derives the causal labelAt from joint verified label removals', () => {
  const document = workflow({
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    nodes: [
      { id: 'a', lane: 'top', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'top', col: 4, type: 'backend', label: 'B' },
      { id: 'c', lane: 'bottom', col: 0, type: 'backend', label: 'C' },
      { id: 'd', lane: 'bottom', col: 4, type: 'backend', label: 'D' },
    ],
    edges: [
      { id: 'a-label', from: 'a', to: 'b', label: 'FIRST', labelAt: [334, 119] },
      { id: 'z-label', from: 'c', to: 'd', label: 'SECOND', labelAt: [334, 119] },
    ],
  });
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'joint causal label-label conflict');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit label-label clearance');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'z-label', field: 'labelAt', path: '/edges/1/labelAt', value: [334, 119],
  }]);
  assert.ok(diagnostic.supportedFixes.length > 0, JSON.stringify(diagnostic, null, 2));
  for (const fix of diagnostic.supportedFixes) {
    const replacement = fix.match(/^set labelAt on edge "z-label" to \[(-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?)\]$/);
    assert.ok(replacement, `only the causal label pin may be changed: ${fix}`);
    const repaired = clone(document);
    repaired.edges[1].labelAt = replacement.slice(1).map(Number);
    const verified = compileWorkflow({ workflow: repaired });
    assert.equal(verified.ok, true, `advertised label repair must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`);
  }
});

test('readable-v2 reports conflicts between two absolute label pins without deleting semantics', () => {
  const document = oneLaneWorkflow([
    {
      id: 'one', from: 'a', to: 'b', label: 'ONE', via: [[214, 119]], labelAt: [214, 80],
    },
    {
      id: 'two', from: 'a', to: 'b', label: 'TWO', via: [[214, 119]], labelAt: [214, 80],
    },
  ]);

  for (const qualityProfile of ['standard', 'showcase']) {
    const result = compileWorkflow({ workflow: clone(document), qualityProfile });
    assertExplicitPinConflict(result, `overlapping labelAt pins under ${qualityProfile}`);
    const diagnostic = result.diagnostics[0];
    assert.equal(diagnostic.evidence.invariant, 'explicit label-label clearance');
    assert.deepEqual(diagnostic.evidence.conflictingPins.map(({ edge, field }) => ({ edge, field })), [
      { edge: 'one', field: 'labelAt' },
      { edge: 'two', field: 'labelAt' },
    ]);
    assertSupportedFixesNameChangedEdge(diagnostic, ['one', 'two']);
    assert.doesNotMatch(diagnostic.supportedFixes.join('\n'), /remove (?:one |the )?label(?!At)/i);
  }
});

test('readable-v2 reports showcase crossings between two absolute route pins', () => {
  const document = workflow({
    lanes: [
      { id: 'top', label: 'Top' },
      { id: 'middle', label: 'Middle' },
      { id: 'bottom', label: 'Bottom' },
    ],
    nodes: [
      { id: 'left', lane: 'middle', col: 0, type: 'backend', label: 'Left' },
      { id: 'right', lane: 'middle', col: 4, type: 'backend', label: 'Right' },
      { id: 'above', lane: 'top', col: 2, type: 'backend', label: 'Above' },
      { id: 'below', lane: 'bottom', col: 2, type: 'backend', label: 'Below' },
    ],
    edges: [
      { id: 'horizontal', from: 'left', to: 'right', via: [[250, 243]] },
      { id: 'vertical', from: 'above', to: 'below', via: [[334, 200]] },
    ],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'showcase' });
  assertExplicitPinConflict(result, 'two crossing absolute routes');
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.evidence.invariant, 'explicit route-route crossing');
  assert.deepEqual(diagnostic.evidence.point, [334, 243]);
  assertSupportedFixesNameChangedEdge(diagnostic, ['horizontal', 'vertical']);
  assert.ok(
    diagnostic.supportedFixes.some((fix) => fix.includes('edge "vertical"')),
    `a vertical repair must identify vertical: ${diagnostic.supportedFixes}`,
  );
});

test('readable-v2 catches an automatic route crossing exactly at an authored straight-through via', () => {
  const document = crossingAtForwardCollinearViaWorkflow();
  const standard = compileWorkflow({ workflow: clone(document), qualityProfile: 'standard' });
  assert.equal(standard.ok, true, JSON.stringify(standard.diagnostics, null, 2));
  assert.deepEqual(
    standard.receipt.edges.find(({ id }) => id === 'z-pinned').points,
    [[334, 145], [334, 243], [334, 341]],
    'analysis must not remove the authored via from the receipt',
  );

  const result = compileWorkflow({ workflow: document, qualityProfile: 'showcase' });
  assertExplicitPinConflict(result, 'an automatic route crossing at an authored straight-through via');
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/explicit-pin-conflict');
  assert.equal(diagnostic.evidence.invariant, 'explicit route-route crossing');
  assert.deepEqual(diagnostic.evidence.point, [334, 243]);
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'z-pinned',
    field: 'via',
    path: '/edges/1/via',
    value: [[334, 243]],
  }]);
  assert.equal(diagnostic.subject.edge, 'z-pinned');
  assert.equal(diagnostic.subject.path, '/edges/1/via');
  assertSupportedFixesNameChangedEdge(diagnostic, ['z-pinned']);
});

test('readable-v2 catches two pinned routes crossing exactly at their straight-through vias', () => {
  const document = crossingAtForwardCollinearViaWorkflow({ pinHorizontal: true });
  const standard = compileWorkflow({ workflow: clone(document), qualityProfile: 'standard' });
  assert.equal(standard.ok, true, JSON.stringify(standard.diagnostics, null, 2));
  assert.deepEqual(
    standard.receipt.edges.map(({ id, points }) => ({ id, points })),
    [
      { id: 'a-pinned', points: [[140, 243], [334, 243], [528, 243]] },
      { id: 'z-pinned', points: [[334, 145], [334, 243], [334, 341]] },
    ],
    'analysis must retain both authored straight-through vias in the receipt',
  );

  const result = compileWorkflow({ workflow: document, qualityProfile: 'showcase' });
  assertExplicitPinConflict(result, 'two pinned routes crossing at straight-through vias');
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/explicit-pin-conflict');
  assert.equal(diagnostic.evidence.invariant, 'explicit route-route crossing');
  assert.deepEqual(diagnostic.evidence.point, [334, 243]);
  assert.deepEqual(diagnostic.evidence.conflictingPins, [
    { edge: 'a-pinned', field: 'via', path: '/edges/0/via', value: [[334, 243]] },
    { edge: 'z-pinned', field: 'via', path: '/edges/1/via', value: [[334, 243]] },
  ]);
  assertSupportedFixesNameChangedEdge(diagnostic, ['a-pinned', 'z-pinned']);
});

test('readable-v2 classifies a side-only authored route crossing an automatic route', () => {
  const document = workflow({
    lanes: ['l0', 'l1', 'l2'].map((id) => ({ id, label: id })),
    nodes: [
      { id: 'n00', lane: 'l0', col: 0, type: 'backend', label: 'N00' },
      { id: 'n01', lane: 'l0', col: 1, type: 'backend', label: 'N01' },
      { id: 'n02', lane: 'l0', col: 2, type: 'backend', label: 'N02' },
      { id: 'n11', lane: 'l1', col: 1, type: 'backend', label: 'N11' },
    ],
    edges: [
      { id: 'a-auto', from: 'n00', to: 'n01' },
      { id: 'z-side', from: 'n02', to: 'n11', fromSide: 'top', toSide: 'left' },
    ],
  });
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'side-only route crossing an automatic route');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit route-route crossing');
  assert.ok(diagnostic.evidence.conflictingPins.length > 0);
  assert.ok(diagnostic.evidence.conflictingPins.every(({ edge, field, path, value }) => (
    edge === 'z-side'
    && ['fromSide', 'toSide'].includes(field)
    && path === `/edges/1/${field}`
    && value === document.edges[1][field]
  )), JSON.stringify(diagnostic.evidence.conflictingPins, null, 2));
  assert.ok(diagnostic.supportedFixes.length > 0, JSON.stringify(diagnostic, null, 2));

  for (const fix of diagnostic.supportedFixes) {
    const removal = fix.match(/^remove (fromSide|toSide)(?: and (fromSide|toSide))? from edge "z-side" /);
    assert.ok(removal, `supported fix must be a concrete side-pin removal: ${fix}`);
    const repaired = clone(document);
    for (const field of removal.slice(1).filter(Boolean)) delete repaired.edges[1][field];
    const verified = compileWorkflow({ workflow: repaired });
    assert.equal(verified.ok, true, `advertised side-pin repair must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`);
  }
});

test('readable-v2 classifies a side-only authored route sharing an automatic corridor', () => {
  const document = workflow({
    lanes: ['l0', 'l1', 'l2'].map((id) => ({ id, label: id })),
    nodes: [
      { id: 'n00', lane: 'l0', col: 0, type: 'backend', label: 'N00' },
      { id: 'n02', lane: 'l0', col: 2, type: 'backend', label: 'N02' },
      { id: 'n01', lane: 'l0', col: 1, type: 'backend', label: 'N01' },
      { id: 'n03', lane: 'l0', col: 3, type: 'backend', label: 'N03' },
    ],
    edges: [
      { id: 'a-auto', from: 'n00', to: 'n02' },
      { id: 'z-side', from: 'n01', to: 'n03', fromSide: 'bottom', toSide: 'top' },
    ],
  });
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'side-only route sharing an automatic corridor');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit route-route corridor clearance');
  assert.deepEqual(diagnostic.evidence.overlapStart, [214, 161]);
  assert.deepEqual(diagnostic.evidence.overlapEnd, [334, 161]);
  assert.equal(diagnostic.evidence.overlapLengthPx, 120);
  assert.ok(diagnostic.evidence.conflictingPins.length > 0);
  assert.ok(diagnostic.evidence.conflictingPins.every(({ edge, field, path, value }) => (
    edge === 'z-side'
    && ['fromSide', 'toSide'].includes(field)
    && path === `/edges/1/${field}`
    && value === document.edges[1][field]
  )), JSON.stringify(diagnostic.evidence.conflictingPins, null, 2));
  assert.ok(diagnostic.supportedFixes.length > 0, JSON.stringify(diagnostic, null, 2));

  for (const fix of diagnostic.supportedFixes) {
    const removal = fix.match(/^remove (fromSide|toSide)(?: and (fromSide|toSide))? from edge "z-side" /);
    assert.ok(removal, `supported fix must be a concrete side-pin removal: ${fix}`);
    const repaired = clone(document);
    for (const field of removal.slice(1).filter(Boolean)) delete repaired.edges[1][field];
    const verified = compileWorkflow({ workflow: repaired });
    assert.equal(verified.ok, true, `advertised side-pin repair must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`);
  }
});

test('readable-v2 classifies a preset-only route sharing an automatic corridor', () => {
  const document = workflow({
    lanes: [{ id: 'l0', label: 'l0' }, { id: 'l1', label: 'l1' }],
    nodes: [
      { id: 'n00', lane: 'l0', col: 0, type: 'backend', label: 'N00' },
      { id: 'n11', lane: 'l1', col: 1, type: 'backend', label: 'N11' },
      { id: 'n01', lane: 'l0', col: 1, type: 'backend', label: 'N01' },
      { id: 'n02', lane: 'l0', col: 2, type: 'backend', label: 'N02' },
    ],
    edges: [
      { id: 'a-auto', from: 'n00', to: 'n11' },
      { id: 'z-route', from: 'n01', to: 'n02', route: 'bottom-channel' },
    ],
  });
  document.meta.quality_profile = 'showcase';

  const result = compileWorkflow({ workflow: document });
  assertExplicitPinConflict(result, 'preset-only route sharing an automatic corridor');
  const diagnostic = result.diagnostics[0];
  assert.equal(diagnostic.evidence.invariant, 'explicit route-route corridor clearance');
  assert.deepEqual(diagnostic.subject, {
    diagramType: 'workflow',
    edge: 'z-route',
    from: 'n01',
    to: 'n02',
    path: '/edges/1/route',
  });
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'z-route',
    field: 'route',
    path: '/edges/1/route',
    value: 'bottom-channel',
  }]);
  assert.deepEqual(diagnostic.evidence.overlapStart, [214, 166]);
  assert.deepEqual(diagnostic.evidence.overlapEnd, [214, 177]);
  assert.equal(diagnostic.evidence.overlapLengthPx, 11);
  assert.deepEqual(diagnostic.supportedFixes, [
    'remove route from edge "z-route" so readable-v2 can replan the remaining authored route assertions',
  ]);

  const repaired = clone(document);
  delete repaired.edges[1].route;
  const verified = compileWorkflow({ workflow: repaired });
  assert.equal(verified.ok, true, JSON.stringify(verified.diagnostics, null, 2));
});

test('readable-v2 reports unknown edge endpoints with a precise semantic diagnostic', () => {
  const document = oneLaneWorkflow([{ id: 'ab', from: 'a', to: 'ghost' }]);
  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, false);
  assert.equal(result.diagnostics.length, 1);
  assert.equal(result.diagnostics[0].code, 'workflow/unknown-edge-endpoint');
  assert.equal(result.diagnostics[0].subject.edge, 'ab');
  assert.equal(result.diagnostics[0].subject.path, '/edges/0/to');
  assert.deepEqual(result.diagnostics[0].evidence, {
    endpoint: 'target',
    unknownNodeId: 'ghost',
    availableNodeIds: ['a', 'b'],
  });
});

test('unknown endpoint diagnostics retain the authored edge pointer after canonical sorting', () => {
  const document = oneLaneWorkflow([
    { id: 'z-valid', from: 'a', to: 'b' },
    { id: 'a-invalid', from: 'a', to: 'ghost' },
  ]);

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  assert.equal(result.diagnostics.length, 1);
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/unknown-edge-endpoint');
  assert.equal(diagnostic.subject.edge, 'a-invalid');
  assert.equal(diagnostic.subject.path, '/edges/1/to');
  assert.equal(diagnostic.evidence.endpoint, 'target');
  assert.equal(diagnostic.evidence.unknownNodeId, 'ghost');
  assert.ok(diagnostic.supportedFixes.length > 0);
});

test('unknown node lane diagnostics retain the authored node pointer after canonical sorting', () => {
  const document = oneLaneWorkflow([]);
  document.nodes[0].lane = 'ghost';

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  assert.equal(result.diagnostics.length, 1);
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/unknown-node-lane');
  assert.equal(diagnostic.subject.node, 'a');
  assert.equal(diagnostic.subject.path, '/nodes/0/lane');
  assert.deepEqual(diagnostic.evidence, {
    unknownLaneId: 'ghost',
    availableLaneIds: ['main'],
  });
  assert.ok(diagnostic.supportedFixes.length > 0);
});

for (const fixture of [
  {
    name: 'lane id',
    code: 'workflow/duplicate-lane-id',
    expectedSubject: { lane: 'main', path: '/lanes/1/id' },
    expectedEvidence: {
      duplicateLaneId: 'main',
      firstPath: '/lanes/0/id',
      duplicatePath: '/lanes/1/id',
    },
    mutate(document) {
      document.lanes.push({ id: 'main', label: 'Duplicate Main' });
    },
  },
  {
    name: 'node id',
    code: 'workflow/duplicate-node-id',
    expectedSubject: { node: 'a', path: '/nodes/2/id' },
    expectedEvidence: {
      duplicateNodeId: 'a',
      firstPath: '/nodes/0/id',
      duplicatePath: '/nodes/2/id',
    },
    mutate(document) {
      document.nodes.push({
        id: 'a', lane: 'main', col: 4, type: 'database', label: 'Duplicate A',
      });
    },
  },
]) {
  test(`duplicate ${fixture.name} diagnostics name both authored source pointers`, () => {
    const document = oneLaneWorkflow([]);
    fixture.mutate(document);

    const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });

    assert.equal(result.ok, false);
    assert.equal(result.svg, undefined);
    assert.equal(result.diagnostics.length, 1);
    const [diagnostic] = result.diagnostics;
    assert.equal(diagnostic.code, fixture.code);
    assert.deepEqual(
      diagnostic.subject,
      { diagramType: 'workflow', ...fixture.expectedSubject },
    );
    assert.deepEqual(diagnostic.evidence, fixture.expectedEvidence);
    assert.ok(diagnostic.supportedFixes.length > 0);
  });
}

test('showcase rejects nested authored outside-right corridors while standard permits them', () => {
  const document = nestedOutsideRightCorridorWorkflow();

  const standard = compileWorkflow({ workflow: clone(document), qualityProfile: 'standard' });
  assert.equal(standard.ok, true, JSON.stringify(standard.diagnostics, null, 2));

  const showcase = compileWorkflow({ workflow: clone(document), qualityProfile: 'showcase' });
  assertExplicitPinConflict(showcase, 'nested authored outside-right corridors');
  const diagnostic = showcase.diagnostics.find(({ code }) => (
    code === 'workflow/explicit-pin-conflict'
  ));
  assertSupportedFixesNameChangedEdge(diagnostic, ['inner', 'outer']);
  assert.deepEqual(
    diagnostic.evidence.conflictingPins.map(({ edge, field, value }) => ({ edge, field, value })),
    [{ edge: 'inner', field: 'channelX', value: 800 }],
  );
  assert.deepEqual(diagnostic.supportedFixes, [
    'remove channelX from edge "inner" so readable-v2 can replan the remaining authored route assertions',
  ]);
  const repaired = clone(document);
  delete repaired.edges.find(({ id }) => id === 'inner').channelX;
  const verified = compileWorkflow({ workflow: repaired, qualityProfile: 'showcase' });
  assert.equal(verified.ok, true, JSON.stringify(verified.diagnostics, null, 2));
});

test('an explicit standard profile overrides an ambient showcase profile', () => {
  const document = nestedOutsideRightCorridorWorkflow();
  document.meta.quality_profile = 'showcase';
  const previousProfile = process.env.ARCHIFY_QUALITY_PROFILE;

  try {
    process.env.ARCHIFY_QUALITY_PROFILE = 'showcase';
    const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
    assert.equal(
      result.ok,
      true,
      `the explicit standard profile must win:\n${JSON.stringify(result.diagnostics, null, 2)}`,
    );
  } finally {
    if (previousProfile === undefined) delete process.env.ARCHIFY_QUALITY_PROFILE;
    else process.env.ARCHIFY_QUALITY_PROFILE = previousProfile;
  }
});

test('readable-v2 never advertises an unverified blocking-node repair', () => {
  const document = workflow({
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [{ id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' }],
    edges: [{ id: 'self', from: 'a', to: 'a' }],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, false);
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/solver-budget-exhausted');
  assert.ok(diagnostic, JSON.stringify(result.diagnostics, null, 2));
  assert.deepEqual(diagnostic.supportedFixes, []);
});

for (const { name, makeVia } of [
  {
    name: 'a 4px endpoint stub',
    makeVia: ({ start, end }) => [
      [start[0] + 4, start[1]],
      [start[0] + 4, start[1] + 41],
      [end[0], start[1] + 41],
    ],
  },
  {
    name: 'a 12px interior turn segment',
    makeVia: ({ start }) => [
      [start[0] + 20, start[1]],
      [start[0] + 20, start[1] + 31],
      [start[0] + 32, start[1] + 31],
      [start[0] + 32, start[1]],
    ],
  },
]) {
  test(`standard profile rejects ${name} as a hard route constraint`, () => {
    const via = makeVia(oneLaneAnchors());
    const result = compileWorkflow({
      workflow: oneLaneWorkflow([{ id: 'ab', from: 'a', to: 'b', via }]),
      qualityProfile: 'standard',
    });
    assert.equal(result.ok, false, `${name} must be invalid in standard as well as showcase`);
    assert.ok(Array.isArray(result.diagnostics) && result.diagnostics.length > 0);
  });
}

test('readable-v2 reports collapsed channel pins without escaping the compiler boundary', () => {
  const { start } = oneLaneAnchors();
  for (const pin of [
    { channelX: start[0] },
    { channelY: start[1] },
  ]) {
    let result;
    assert.doesNotThrow(() => {
      result = compileWorkflow({
        workflow: oneLaneWorkflow([{ id: 'ab', from: 'a', to: 'b', ...pin }]),
        qualityProfile: 'standard',
      });
    });
    assertExplicitPinConflict(result, JSON.stringify(pin));
    assert.ok(result.diagnostics.some(({ evidence }) => (
      evidence?.invariant === 'non-zero route segments'
    )));
  }
});

test('parallel anonymous edges remain byte-deterministic when their input order changes', () => {
  const document = oneLaneWorkflow([
    { from: 'a', to: 'b', variant: 'default', role: 'main' },
    { from: 'a', to: 'b', variant: 'dashed', role: 'async' },
  ]);
  const reordered = clone(document);
  reordered.edges.reverse();

  const first = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  const second = compileWorkflow({ workflow: reordered, qualityProfile: 'standard' });
  assert.equal(first.ok, true, JSON.stringify(first.diagnostics, null, 2));
  assert.equal(second.ok, true, JSON.stringify(second.diagnostics, null, 2));
  assert.equal(second.svg, first.svg);
  assert.equal(JSON.stringify(second.receipt), JSON.stringify(first.receipt));
});

test('compileWorkflow returns a diagnostic result instead of throwing when meta is absent', () => {
  const document = oneLaneWorkflow([]);
  delete document.meta;

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  assert.ok(Array.isArray(result.diagnostics) && result.diagnostics.length > 0);
});

test('compileWorkflow returns typed failures for non-document public inputs without throwing', () => {
  for (const input of [undefined, null, [], {}]) {
    const result = compileWorkflow({ workflow: input });
    assert.equal(result.ok, false);
    assert.equal(result.svg, undefined);
    assert.ok(Array.isArray(result.diagnostics) && result.diagnostics.length > 0);
    assert.ok(result.diagnostics.every(({ code }) => code !== 'internal/unclassified'));
    assert.ok(result.diagnostics.every(({ supportedFixes }) => (
      Array.isArray(supportedFixes) && supportedFixes.length === 0
    )));
    assert.deepEqual(result.receipt.diagnostics, result.diagnostics);
  }
});

test('compileWorkflow enforces the canonical workflow schema at its public boundary', () => {
  for (const { expectedCode, mutate, qualityProfile } of [
    { expectedCode: 'schema/additionalProperties', mutate: (document) => { document.unsupported = true; } },
    { expectedCode: 'schema/enum', mutate: (document) => { document.nodes[0].type = 'bogus'; } },
    { expectedCode: 'schema/minimum', mutate: (document) => { document.nodes[0].width = 31; } },
    { expectedCode: 'schema/required', mutate: (document) => { delete document.nodes[0].label; } },
    { expectedCode: 'schema/enum', mutate: () => {}, qualityProfile: 'impossible' },
  ]) {
    const document = oneLaneWorkflow([]);
    mutate(document);
    const result = compileWorkflow({ workflow: document, qualityProfile });
    assert.equal(result.ok, false);
    assert.equal(result.svg, undefined);
    assert.ok(
      result.diagnostics.some(({ code }) => code === expectedCode),
      JSON.stringify(result.diagnostics, null, 2),
    );
    assert.ok(result.diagnostics.every(({ code }) => code !== 'internal/unclassified'));
    assert.ok(result.diagnostics.every(({ supportedFixes }) => (
      Array.isArray(supportedFixes) && supportedFixes.length === 0
    )));
    assert.deepEqual(result.receipt.diagnostics, result.diagnostics);
  }
});

test('readable-v2 rejects a negative absolute label pin without an explicit viewBox', () => {
  const document = oneLaneWorkflow([{
    id: 'ab', from: 'a', to: 'b', label: 'pinned', labelAt: [-20, 80],
  }]);
  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  assert.equal(result.diagnostics.length, 1, JSON.stringify(result.diagnostics, null, 2));
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/explicit-pin-conflict');
  assert.deepEqual(diagnostic.subject, {
    diagramType: 'workflow',
    edge: 'ab',
    from: 'a',
    to: 'b',
    path: '/edges/0/labelAt',
  });
  assert.equal(diagnostic.evidence.invariant, 'viewBox-origin containment');
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'ab',
    field: 'labelAt',
    path: '/edges/0/labelAt',
    value: [-20, 80],
  }]);
  assert.deepEqual(diagnostic.evidence.offendingRect, {
    x: -39.4,
    y: 70,
    width: 38.8,
    height: 14,
  });
  assert.deepEqual(result.receipt.diagnostics, result.diagnostics);

  assert.ok(diagnostic.supportedFixes.length > 0);
  for (const fix of diagnostic.supportedFixes) {
    const repaired = clone(document);
    const target = repaired.edges.find(({ id }) => fix.includes(`edge "${id}"`));
    assert.ok(target, `supported fix must name an existing edge: ${fix}`);
    const replacement = fix.match(/^set labelAt on edge "[^"]+" to \[(-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?)\]$/);
    if (replacement) {
      target.labelAt = replacement.slice(1).map(Number);
    } else {
      assert.match(fix, /^remove labelAt from edge "[^"]+" /);
      delete target.labelAt;
    }
    const verified = compileWorkflow({ workflow: repaired, qualityProfile: 'standard' });
    assert.equal(verified.ok, true, `advertised fix must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`);
  }
});

test('readable-v2 rejects an explicit channel that crosses the measured legend', () => {
  const document = oneLaneWorkflow([{
    id: 'ab',
    from: 'a',
    to: 'b',
    route: 'bottom-channel',
    channelY: 200,
    fromSide: 'bottom',
    toSide: 'bottom',
  }]);
  document.meta.legend = { mode: 'all' };
  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assertExplicitPinConflict(result, 'legend-crossing channelY');
  assert.ok(result.diagnostics.some(({ evidence }) => evidence?.invariant === 'legend clearance'));
});

test('readable-v2 explicit-pin diagnostics name the first intersected node and segment', () => {
  const document = workflow({
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'obstacle', lane: 'main', col: 1, type: 'database', label: 'Obstacle' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{
      id: 'ab',
      from: 'a',
      to: 'b',
      fromSide: 'right',
      toSide: 'left',
      via: [[200, 119], [260, 119]],
    }],
  });

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assertExplicitPinConflict(result, 'unrelated node collision');
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/explicit-pin-conflict');
  assert.equal(diagnostic.evidence.invariant, 'node clearance');
  assert.deepEqual({
    obstacleNode: diagnostic.evidence.obstacleNode,
    obstacleRole: diagnostic.evidence.obstacleRole,
    segmentIndex: diagnostic.evidence.segmentIndex,
    from: diagnostic.evidence.from,
    to: diagnostic.evidence.to,
    clearancePx: diagnostic.evidence.clearancePx,
  }, {
    obstacleNode: 'obstacle',
    obstacleRole: 'unrelated',
    segmentIndex: 0,
    from: [140, 119],
    to: [200, 119],
    clearancePx: 2,
  });
});

test('fixed-v1 publishes only repairs that survive complete replanning', () => {
  const document = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Verified fixes', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    groups: [{ id: 'target-only', label: 'Target', lane: 'main', fromCol: 2, toCol: 2 }],
    nodes: [
      { id: 'a', lane: 'main', col: 1, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b' }],
  };

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(result.ok, false);
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/column-capacity');
  assert.ok(diagnostic);
  assert.ok(
    diagnostic.supportedFixes.every((fix) => !/^move node /.test(fix)),
    `moving the group's only node must not be advertised as verified: ${diagnostic.supportedFixes}`,
  );
});

test('fixed-v1 verifies the exact serialized values in every rounded-width repair', () => {
  const document = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Rounded verified widths', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'M' }],
    nodes: [
      { id: 'a', lane: 'main', col: 1, type: 'backend', label: 'A', width: 52.006 },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B', width: 51.995 },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b' }],
  };

  const result = compileWorkflow({ workflow: document, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.equal(result.diagnostics.length, 1, JSON.stringify(result.diagnostics, null, 2));
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/column-capacity');
  assert.deepEqual(diagnostic.evidence.nodeWidthsPx, [52.006, 51.995]);
  assert.ok(diagnostic.supportedFixes.length > 0);
  assert.ok(
    diagnostic.supportedFixes.includes('set node widths "a"=52px and "b"=51.99px'),
    JSON.stringify(diagnostic.supportedFixes, null, 2),
  );

  for (const fix of diagnostic.supportedFixes) {
    const repaired = clone(document);
    if (fix === 'migrate this workflow to schema_version 2') {
      repaired.schema_version = 2;
    } else {
      const move = fix.match(/^move node "([^"]+)" to verified free column (\d+)$/);
      const widths = [...fix.matchAll(/"([^"]+)"=([\d.]+)px/g)];
      if (move) {
        repaired.nodes.find(({ id }) => id === move[1]).col = Number(move[2]);
      } else {
        assert.ok(widths.length > 0, `unsupported advertised repair: ${fix}`);
        for (const [, nodeId, width] of widths) {
          repaired.nodes.find(({ id }) => id === nodeId).width = Number(width);
        }
      }
    }
    const verified = compileWorkflow({ workflow: repaired, qualityProfile: 'standard' });
    assert.equal(
      verified.ok,
      true,
      `advertised fix must recompile: ${fix}\n${JSON.stringify(verified.diagnostics, null, 2)}`,
    );
  }
});
```

## test/workflow-compiler.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { compileWorkflow } from '../renderers/workflow/workflow-compiler.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const cli = path.join(skillRoot, 'bin', 'archify.mjs');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-workflow-compiler-'));

function readJson(file) {
  return JSON.parse(fs.readFileSync(file, 'utf8'));
}

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

function sha256(value) {
  return createHash('sha256').update(value).digest('hex');
}

function attribute(tag, name) {
  const value = tag.match(new RegExp(`\\b${name}="([^"]*)"`))?.[1];
  assert.notEqual(value, undefined, `expected ${name} in ${tag}`);
  return value;
}

function numericRect(attributes) {
  return Object.fromEntries(['x', 'y', 'width', 'height'].map((name) => [
    name,
    Number(attribute(attributes, name)),
  ]));
}

function nodeRect(svg, id) {
  const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const attributes = svg.match(new RegExp(
    `<g\\b[^>]*id="node-${escapedId}"[^>]*>[\\s\\S]*?<rect\\b([^>]*)>`,
  ))?.[1];
  assert.ok(attributes, `expected rendered node ${id}`);
  return numericRect(attributes);
}

function edgePoints(svg, id) {
  const tag = (svg.match(/<path\b[^>]*>/g) || []).find((candidate) => (
    attributeOrUndefined(candidate, 'data-edge-id') === id
  ));
  assert.ok(tag, `expected rendered edge ${id}`);
  return attribute(tag, 'data-composition-points')
    .split(';')
    .map((point) => point.split(',').map(Number));
}

function edgeLabelRect(svg, id) {
  const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const attributes = svg.match(new RegExp(
    `<g\\b(?=[^>]*data-edge-id="${escapedId}")(?=[^>]*data-edge-label=)[^>]*>[\\s\\S]*?<rect\\b([^>]*)>`,
  ))?.[1];
  assert.ok(attributes, `expected rendered label mask for edge ${id}`);
  return numericRect(attributes);
}

function attributeOrUndefined(tag, name) {
  return tag.match(new RegExp(`\\b${name}="([^"]*)"`))?.[1];
}

function svgViewBox(svg) {
  const tag = svg.match(/<svg\b[^>]*>/)?.[0];
  assert.ok(tag, 'expected an SVG root');
  return attribute(tag, 'viewBox').split(/\s+/).map(Number);
}

function legendGeometry(svg) {
  return svg.match(/<g data-legend-semantic-kind="[^"]+"[^>]*>/g) || [];
}

function assertRectInsideViewBox(rect, viewBox, message) {
  const [minX, minY, width, height] = viewBox;
  assert.ok(rect.x >= minX, `${message}: left edge ${rect.x} is outside ${minX}`);
  assert.ok(rect.y >= minY, `${message}: top edge ${rect.y} is outside ${minY}`);
  assert.ok(
    rect.x + rect.width <= minX + width,
    `${message}: right edge ${rect.x + rect.width} is outside ${minX + width}`,
  );
  assert.ok(
    rect.y + rect.height <= minY + height,
    `${message}: bottom edge ${rect.y + rect.height} is outside ${minY + height}`,
  );
}

function assertRectInsideRect(rect, container, message) {
  assert.ok(rect.x >= container.x, `${message}: left edge ${rect.x} is outside ${container.x}`);
  assert.ok(rect.y >= container.y, `${message}: top edge ${rect.y} is outside ${container.y}`);
  assert.ok(
    rect.x + rect.width <= container.x + container.width,
    `${message}: right edge ${rect.x + rect.width} is outside ${container.x + container.width}`,
  );
  assert.ok(
    rect.y + rect.height <= container.y + container.height,
    `${message}: bottom edge ${rect.y + rect.height} is outside ${container.y + container.height}`,
  );
}

function groupFrameRect(svg, index = 0) {
  const attributes = svg.match(new RegExp(
    `<rect\\b(?=[^>]*data-composition-frame-kind="group")(?=[^>]*data-composition-frame-id="group-${index}")([^>]*)/>`,
  ))?.[1];
  assert.ok(attributes, `expected rendered group frame ${index}`);
  return numericRect(attributes);
}

function asciiGroupLabelTextRect(svg, label) {
  const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const attributes = svg.match(new RegExp(`<text\\b([^>]*)>${escapedLabel}</text>`))?.[1];
  assert.ok(attributes, `expected rendered group label ${label}`);
  return {
    x: Number(attribute(attributes, 'x')),
    y: Number(attribute(attributes, 'y')) - 10,
    width: Array.from(label).length * 5.6,
    height: 14,
  };
}

function rectsOverlap(left, right) {
  return !(
    left.x + left.width <= right.x
    || right.x + right.width <= left.x
    || left.y + left.height <= right.y
    || right.y + right.height <= left.y
  );
}

function orthogonalSegmentIntersectsRect(start, end, rect) {
  const left = rect.x;
  const right = rect.x + rect.width;
  const top = rect.y;
  const bottom = rect.y + rect.height;
  if (start[1] === end[1]) {
    return start[1] >= top && start[1] <= bottom
      && Math.max(start[0], end[0]) >= left
      && Math.min(start[0], end[0]) <= right;
  }
  if (start[0] === end[0]) {
    return start[0] >= left && start[0] <= right
      && Math.max(start[1], end[1]) >= top
      && Math.min(start[1], end[1]) <= bottom;
  }
  throw new Error(`expected an orthogonal route segment: ${JSON.stringify([start, end])}`);
}

function asciiLaneHeaderTextRect(label, { laneTop = 52, laneIndex = 0 } = {}) {
  const prefix = String(laneIndex + 1).padStart(2, '0');
  return {
    x: 54,
    y: laneTop + 12,
    width: Array.from(`${prefix} / ${label}`).length * 6.2,
    height: 14,
  };
}

function compileSuccessfully(workflow, qualityProfile) {
  const request = qualityProfile === undefined
    ? { workflow }
    : { workflow, qualityProfile };
  const result = compileWorkflow(request);
  assert.equal(
    result.ok,
    true,
    `expected workflow compilation to succeed:\n${JSON.stringify(result.diagnostics, null, 2)}`,
  );
  assert.equal(typeof result.svg, 'string');
  assert.match(result.svg, /^\s*<svg\b/, 'compiler should return one canonical renderer SVG');
  assert.ok(result.receipt);
  assert.deepEqual(result.receipt.diagnostics, []);
  return result;
}

function adjacentWorkflow({
  fromCol = 1,
  toCol = fromCol + 1,
  label,
  widths = [92, 92],
  nodeLabels = ['A', 'B'],
  viewBox,
  frames = false,
} = {}) {
  const workflow = {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: {
      title: `Adjacent ranks ${fromCol} to ${toCol}`,
      legend: { mode: 'hidden' },
      ...(viewBox ? { viewBox } : {}),
    },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      {
        id: 'a', lane: 'main', col: fromCol, type: 'backend', label: nodeLabels[0], width: widths[0],
      },
      {
        id: 'b', lane: 'main', col: toCol, type: 'backend', label: nodeLabels[1], width: widths[1],
      },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b', ...(label ? { label } : {}) }],
  };
  if (frames) {
    workflow.phases = [{
      id: 'phase', label: 'P', fromCol, toCol,
    }];
    workflow.groups = [{
      id: 'group', label: 'G', lane: 'main', fromCol, toCol,
    }];
  }
  return workflow;
}

function assertReadableAdjacentResult(result, { label, widths = [92, 92] } = {}) {
  assert.equal(result.receipt.contract, 'readable-v2');
  assert.deepEqual(result.receipt.viewBox, svgViewBox(result.svg).slice(2));
  assert.ok(Array.isArray(result.receipt.requiredViewBox));
  assert.equal(result.receipt.columns.length, 6);
  assert.ok(result.receipt.columns.every(Number.isFinite));
  for (let index = 1; index < result.receipt.columns.length; index += 1) {
    assert.ok(
      result.receipt.columns[index] > result.receipt.columns[index - 1],
      `column ${index} must be strictly after column ${index - 1}`,
    );
  }

  const source = nodeRect(result.svg, 'a');
  const target = nodeRect(result.svg, 'b');
  assert.equal(source.width, widths[0]);
  assert.equal(target.width, widths[1]);
  assert.ok(source.x + source.width <= target.x, 'same-lane nodes must not overlap');

  const points = edgePoints(result.svg, 'ab');
  assert.deepEqual(points.length, 2, `adjacent facing nodes should use a direct route: ${JSON.stringify(points)}`);
  assert.equal(points[0][0], source.x + source.width, 'edge must leave the source right side');
  assert.equal(points[1][0], target.x, 'edge must enter the target left side');
  assert.equal(points[0][1], points[1][1], 'direct route must be horizontal');
  const directClearance = points[1][0] - points[0][0];
  assert.ok(directClearance >= 28, `direct clearance ${directClearance}px must be at least 28px`);

  if (label) {
    const mask = edgeLabelRect(result.svg, 'ab');
    assert.ok(
      directClearance + 1e-9 >= mask.width + 8,
      `labeled direct clearance ${directClearance}px must fit ${mask.width}px mask plus 8px breathing room`,
    );
    assertRectInsideViewBox(mask, svgViewBox(result.svg), 'edge label mask');
  }
}

test('fixed-v1 compiler preserves the official workflow baseline SVG byte-for-byte', () => {
  const workflow = readJson(path.join(__dirname, 'fixtures', 'v1-baseline', 'agent-tool-call.workflow.json'));
  const result = compileSuccessfully(workflow);
  assert.equal(result.receipt.contract, 'fixed-v1');
  assert.equal(
    sha256(result.svg),
    '4e493db1977889675ce7b04bf9ba60fb97cb50f01fc0fd9e8446861282c65645',
  );
});

test('fixed-v1 compiler preserves the exact 700x400 compatibility geometry', () => {
  const workflow = readJson(path.join(
    __dirname,
    'fixtures',
    'v1-workflow-700x400.workflow.json',
  ));

  const result = compileSuccessfully(workflow);
  assert.equal(result.receipt.contract, 'fixed-v1');
  assert.deepEqual(result.receipt.viewBox, [700, 400]);
  assert.deepEqual(svgViewBox(result.svg), [0, 0, 700, 400]);
  assert.equal(
    sha256(result.svg),
    '28b0167460d16c55ae6bf38bde41368248671a78b3a49133da05ed1efb4354af',
    'the v1 compiler extraction must not move or reserialize legacy geometry',
  );
});

test('fixed-v1 keeps valid phase and group spans independent of label measurement', () => {
  const workflow = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Fixed v1 frame geometry', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    phases: [{ id: 'phase', label: 'P'.repeat(17), fromCol: 0, toCol: 0 }],
    groups: [{
      id: 'group', label: 'G'.repeat(30), lane: 'main', fromCol: 0, toCol: 0,
    }],
    nodes: [{ id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' }],
    edges: [],
  };

  const result = compileSuccessfully(workflow);
  assert.match(
    result.svg,
    /<line x1="42" y1="35" x2="134" y2="35"/,
    'v1 phase spans must retain the legacy fixed 46px padding',
  );
  assert.match(
    result.svg,
    /data-composition-frame-kind="group"[^>]* x="38" y="90" width="100"/,
    'v1 group spans must retain the legacy fixed 50px padding',
  );
});

test('fixed-v1 reports one causal column-capacity diagnostic for issue #126', () => {
  const workflow = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Issue 126 causal diagnostic', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 1, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b', label: 'liga' }],
  };

  const result = compileWorkflow({ workflow, qualityProfile: 'showcase' });
  assert.equal(result.ok, false);
  assert.equal(result.diagnostics.length, 1, JSON.stringify(result.diagnostics, null, 2));
  assert.deepEqual(result.receipt.diagnostics, result.diagnostics);

  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/column-capacity');
  assert.equal(diagnostic.subject.edge, 'ab');
  assert.equal(diagnostic.subject.from, 'a');
  assert.equal(diagnostic.subject.to, 'b');
  assert.equal(diagnostic.subject.fromCol, 1);
  assert.equal(diagnostic.subject.toCol, 2);
  assert.deepEqual(diagnostic.evidence, {
    centerDistancePx: 80,
    nodeWidthsPx: [92, 92],
    actualSignedClearancePx: -12,
    requiredDirectClearancePx: 28,
  });
  assert.deepEqual(diagnostic.suppresses, [
    'workflow/short-edge',
    'clean-flow/endpoint-side-direction',
    'workflow/label-node-overlap',
  ]);
  assert.ok(diagnostic.supportedFixes.some((fix) => /schema_version 2/.test(fix)));
  assert.ok(diagnostic.supportedFixes.some((fix) => /column 3/.test(fix)));
  assert.ok(diagnostic.supportedFixes.some((fix) => /width/i.test(fix)));
  assert.doesNotMatch(
    diagnostic.supportedFixes.join('\n'),
    /drop|remove|omit|unlabel|channel/i,
    'a causal overlap diagnostic must not propose a label or routing non-fix',
  );
});

test('fixed-v1 verifies the real coordinate migration before advertising it', () => {
  const workflow = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: {
      title: 'Pinned issue 126 migration',
      viewBox: [720, 400],
      legend: { mode: 'hidden' },
    },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 1, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{
      id: 'ab',
      from: 'a',
      to: 'b',
      fromSide: 'top',
      toSide: 'top',
      via: [[220, 60], [300, 60]],
    }],
  };

  const result = compileWorkflow({ workflow, qualityProfile: 'showcase' });
  assert.equal(result.ok, false);
  assert.equal(result.diagnostics[0].code, 'workflow/column-capacity');
  assert.ok(
    result.diagnostics[0].supportedFixes.includes('migrate this workflow to schema_version 2'),
    JSON.stringify(result.diagnostics[0], null, 2),
  );
});

test('readable-v2 supports every adjacent rank with and without a semantic label', () => {
  for (const qualityProfile of ['standard', 'showcase']) {
    for (let fromCol = 0; fromCol < 5; fromCol += 1) {
      for (const label of [undefined, 'liga']) {
        const result = compileSuccessfully(adjacentWorkflow({ fromCol, label }), qualityProfile);
        assertReadableAdjacentResult(result, { label });
      }
    }
  }
});

test('readable-v2 satisfies the complete adjacent-rank acceptance matrix', () => {
  let cases = 0;
  for (const qualityProfile of ['standard', 'showcase']) {
    for (let fromCol = 0; fromCol < 5; fromCol += 1) {
      for (const label of [undefined, 'liga']) {
        for (const frames of [false, true]) {
          for (const width of [900, 1080, 1400, 1600]) {
            const result = compileSuccessfully(adjacentWorkflow({
              fromCol,
              label,
              frames,
              viewBox: [width, 420],
            }), qualityProfile);
            assert.deepEqual(result.receipt.viewBox, [width, 420]);
            assertReadableAdjacentResult(result, { label });
            if (frames) assert.match(result.svg, /data-composition-frame-kind="group"/);
            cases += 1;
          }
        }
      }
    }
  }
  assert.equal(cases, 160);
});

test('readable-v2 phase and group frames derive from solved ranks without moving the core geometry', () => {
  for (const qualityProfile of ['standard', 'showcase']) {
    for (let fromCol = 0; fromCol < 5; fromCol += 1) {
      const plain = compileSuccessfully(adjacentWorkflow({ fromCol, label: 'liga' }), qualityProfile);
      const framed = compileSuccessfully(
        adjacentWorkflow({ fromCol, label: 'liga', frames: true }),
        qualityProfile,
      );

      assert.deepEqual(framed.receipt.columns, plain.receipt.columns);
      assert.deepEqual(nodeRect(framed.svg, 'a'), nodeRect(plain.svg, 'a'));
      assert.deepEqual(nodeRect(framed.svg, 'b'), nodeRect(plain.svg, 'b'));
      assert.deepEqual(edgePoints(framed.svg, 'ab'), edgePoints(plain.svg, 'ab'));
      assert.match(framed.svg, /data-composition-frame-kind="group"/);
      assertReadableAdjacentResult(framed, { label: 'liga' });
    }
  }
});

test('readable-v2 treats the phase header mask as a routing obstacle', () => {
  const document = {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: { title: 'Phase route obstacle', legend: { mode: 'hidden' } },
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    nodes: [
      { id: 'a', lane: 'top', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'bottom', col: 4, type: 'backend', label: 'B' },
    ],
    edges: [{
      id: 'ab', from: 'a', to: 'b', fromSide: 'top', toSide: 'top',
    }],
  };

  const withoutPhase = compileSuccessfully(clone(document), 'showcase');
  assert.ok(withoutPhase.receipt.edges.find(({ id }) => id === 'ab'));

  const withPhaseDocument = clone(document);
  withPhaseDocument.phases = [{ id: 'p', label: 'Phase', fromCol: 0, toCol: 5 }];
  const withPhase = compileSuccessfully(withPhaseDocument, 'showcase');
  const columns = withPhase.receipt.columns;
  const phaseMask = {
    x: columns[0] - 46,
    y: 27,
    width: (columns[5] + 46) - (columns[0] - 46),
    height: 16,
  };
  const points = withPhase.receipt.edges.find(({ id }) => id === 'ab').points;
  for (let index = 0; index < points.length - 1; index += 1) {
    assert.equal(
      orthogonalSegmentIntersectsRect(points[index], points[index + 1], phaseMask),
      false,
      `edge ab segment ${index} must clear the phase mask: ${JSON.stringify(points)}`,
    );
  }
});

test('readable-v2 never routes through a single-rank group label', () => {
  const groupLabel = 'Very long group label';
  const document = {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: { title: 'Group label route obstacle', legend: { mode: 'hidden' } },
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    groups: [{ id: 'g', label: groupLabel, lane: 'top', fromCol: 2, toCol: 2 }],
    nodes: [
      { id: 'a', lane: 'top', col: 2, type: 'backend', label: 'A' },
      { id: 'b', lane: 'bottom', col: 4, type: 'database', label: 'B' },
    ],
    edges: [{
      id: 'ab', from: 'a', to: 'b', fromSide: 'top', toSide: 'top',
    }],
  };

  const first = compileWorkflow({ workflow: document, qualityProfile: 'showcase' });
  const second = compileWorkflow({ workflow: clone(document), qualityProfile: 'showcase' });
  assert.deepEqual(second, first, 'repeated compilation must be deterministic');

  assert.equal(first.ok, false);
  assert.equal(first.svg, undefined);
  assert.equal(first.diagnostics.length, 1, JSON.stringify(first.diagnostics, null, 2));
  const [diagnostic] = first.diagnostics;
  assert.equal(diagnostic.code, 'workflow/explicit-pin-conflict');
  assert.equal(diagnostic.subject.path, '/edges/0/fromSide');
  assert.equal(
    diagnostic.evidence.invariant,
    'readable route feasibility with authored endpoint sides',
  );
  assert.deepEqual(diagnostic.evidence.conflictingPins, [{
    edge: 'ab',
    field: 'fromSide',
    path: '/edges/0/fromSide',
    value: 'top',
  }]);
  assert.ok(diagnostic.supportedFixes.length > 0);
  assert.deepEqual(first.receipt.diagnostics, first.diagnostics);

  const repaired = clone(document);
  delete repaired.edges[0].fromSide;
  const rendered = compileWorkflow({ workflow: repaired, qualityProfile: 'showcase' });
  assert.equal(rendered.ok, true, JSON.stringify(rendered.diagnostics, null, 2));

  const escapedLabel = groupLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const attributes = rendered.svg.match(new RegExp(
    `<text\\b([^>]*)>${escapedLabel}</text>`,
  ))?.[1];
  assert.ok(attributes, 'expected the rendered group label');
  const groupLabelRect = {
    x: Number(attribute(attributes, 'x')),
    y: Number(attribute(attributes, 'y')) - 10,
    width: Array.from(groupLabel).length * 5.6,
    height: 14,
  };
  const points = rendered.receipt.edges.find(({ id }) => id === 'ab').points;
  for (let index = 0; index < points.length - 1; index += 1) {
    assert.equal(
      orthogonalSegmentIntersectsRect(points[index], points[index + 1], groupLabelRect),
      false,
      `edge ab segment ${index} must clear the group label: ${JSON.stringify(points)}`,
    );
  }
});

test('readable-v2 shifts top-side routes beyond lane header text deterministically', () => {
  const makeDocument = (topLaneLabel) => ({
    schema_version: 2,
    diagram_type: 'workflow',
    meta: { title: 'Lane header route obstacle', legend: { mode: 'hidden' } },
    lanes: [
      { id: 'top', label: topLaneLabel },
      { id: 'bottom', label: 'Bottom' },
    ],
    nodes: [
      { id: 'a', lane: 'top', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'bottom', col: 4, type: 'database', label: 'B' },
    ],
    edges: [{
      id: 'ab', from: 'a', to: 'b', fromSide: 'top', toSide: 'top',
    }],
  });

  const compiled = new Map();
  for (const label of ['Responsibility owner', 'R']) {
    const first = compileSuccessfully(makeDocument(label), 'showcase');
    const second = compileSuccessfully(makeDocument(label), 'showcase');
    assert.deepEqual(second, first, `${label}: repeated compilation must be deterministic`);

    const points = first.receipt.edges.find(({ id }) => id === 'ab').points;
    const headerRect = asciiLaneHeaderTextRect(label);
    for (let index = 0; index < points.length - 1; index += 1) {
      assert.equal(
        orthogonalSegmentIntersectsRect(points[index], points[index + 1], headerRect),
        false,
        `${label}: edge ab segment ${index} must clear the top lane header: ${JSON.stringify(points)}`,
      );
    }
    compiled.set(label, first);
  }

  assert.ok(
    compiled.get('Responsibility owner').receipt.columns[0]
      > compiled.get('R').receipt.columns[0],
    'the wider lane header must move the col-0 top-side corridor farther right',
  );
});

test('explicit viewBox width is containment capacity and never stretches readable-v2 geometry', () => {
  for (const qualityProfile of ['standard', 'showcase']) {
    for (let fromCol = 0; fromCol < 5; fromCol += 1) {
      const intrinsic = compileSuccessfully(adjacentWorkflow({
        fromCol,
        label: 'liga',
      }), qualityProfile);
      const expectedGeometry = {
        columns: intrinsic.receipt.columns,
        source: nodeRect(intrinsic.svg, 'a'),
        target: nodeRect(intrinsic.svg, 'b'),
        edge: edgePoints(intrinsic.svg, 'ab'),
      };

      for (const width of [900, 1080, 1400, 1600]) {
        const result = compileSuccessfully(adjacentWorkflow({
          fromCol,
          label: 'liga',
          viewBox: [width, 420],
        }), qualityProfile);
        assert.deepEqual(result.receipt.viewBox, [width, 420]);
        assert.deepEqual(svgViewBox(result.svg), [0, 0, width, 420]);
        assert.deepEqual(result.receipt.requiredViewBox, intrinsic.receipt.requiredViewBox);
        assert.deepEqual(result.receipt.columns, expectedGeometry.columns);
        assert.deepEqual(nodeRect(result.svg, 'a'), expectedGeometry.source);
        assert.deepEqual(nodeRect(result.svg, 'b'), expectedGeometry.target);
        assert.deepEqual(edgePoints(result.svg, 'ab'), expectedGeometry.edge);
        assertReadableAdjacentResult(result, { label: 'liga' });
      }
    }
  }
});

test('capacity-only viewBox failures preserve the intrinsic routes, labels, and requirement', () => {
  const document = {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: { title: 'capacity', legend: { mode: 'hidden' } },
    lanes: [{ id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }],
    nodes: [
      { id: 'n0', lane: 'top', col: 0, type: 'backend', label: 'N0' },
      { id: 'n1', lane: 'bottom', col: 1, type: 'backend', label: 'N1' },
      { id: 'n2', lane: 'top', col: 1, type: 'database', label: 'N2' },
      { id: 'n3', lane: 'bottom', col: 3, type: 'database', label: 'N3' },
    ],
    edges: [
      { id: 'e0', from: 'n3', to: 'n0' },
      {
        id: 'e1',
        from: 'n1',
        to: 'n2',
        label: 'ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ',
      },
    ],
  };
  const intrinsic = compileSuccessfully(clone(document), 'showcase');

  const insufficientDocument = clone(document);
  insufficientDocument.meta.viewBox = [900, 1000];
  const insufficient = compileWorkflow({
    workflow: insufficientDocument,
    qualityProfile: 'showcase',
  });
  assert.equal(insufficient.ok, false);
  assert.equal(insufficient.svg, undefined);
  assert.equal(insufficient.diagnostics.length, 1, JSON.stringify(insufficient.diagnostics, null, 2));
  const [capacity] = insufficient.diagnostics;
  assert.equal(capacity.code, 'workflow/viewbox-capacity');
  assert.deepEqual(capacity.evidence.actualViewBox, [900, 1000]);
  assert.deepEqual(capacity.evidence.requiredViewBox, intrinsic.receipt.requiredViewBox);

  const sufficientDocument = clone(document);
  sufficientDocument.meta.viewBox = [intrinsic.receipt.requiredViewBox[0], 1000];
  const sufficient = compileSuccessfully(sufficientDocument, 'showcase');
  assert.deepEqual(sufficient.receipt.requiredViewBox, intrinsic.receipt.requiredViewBox);
  assert.deepEqual(sufficient.receipt.edges, intrinsic.receipt.edges);
  assert.deepEqual(sufficient.receipt.labels, intrinsic.receipt.labels);
});

test('explicit viewBox capacity names the rank and label constraints that require more width', () => {
  const result = compileWorkflow({
    workflow: adjacentWorkflow({ fromCol: 1, label: 'liga', viewBox: [700, 420] }),
    qualityProfile: 'showcase',
  });
  assert.equal(result.ok, false);
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/viewbox-capacity');
  assert.ok(diagnostic, JSON.stringify(result.diagnostics, null, 2));
  assert.ok(diagnostic.evidence.contributors.includes('rank 1→2 direct clearance'));
  assert.ok(diagnostic.evidence.contributors.includes('edge ab label mask'));
});

test('explicit viewBox capacity names the rank and authored node widths that require more width', () => {
  const result = compileWorkflow({
    workflow: adjacentWorkflow({
      fromCol: 1,
      widths: [240, 240],
      viewBox: [700, 420],
    }),
    qualityProfile: 'showcase',
  });
  assert.equal(result.ok, false);
  const diagnostic = result.diagnostics.find(({ code }) => code === 'workflow/viewbox-capacity');
  assert.ok(diagnostic, JSON.stringify(result.diagnostics, null, 2));
  const contributors = diagnostic.evidence.contributors;
  assert.ok(
    contributors.some((contributor) => /rank 1→2/i.test(contributor) && /clearance/i.test(contributor)),
    `contributors must name the rank 1→2 width constraint: ${JSON.stringify(contributors)}`,
  );
  for (const nodeId of ['a', 'b']) {
    assert.ok(
      contributors.some((contributor) => (
        new RegExp(`node ${nodeId}\\b`, 'i').test(contributor) && /width/i.test(contributor)
      )),
      `contributors must name node ${nodeId}'s authored width: ${JSON.stringify(contributors)}`,
    );
  }
});

test('explicit viewBox height capacity names the authored tall node without naming a hidden legend', () => {
  const result = compileWorkflow({
    workflow: {
      schema_version: 2,
      diagram_type: 'workflow',
      meta: {
        title: 'Tall node height capacity',
        legend: { mode: 'hidden' },
        viewBox: [1200, 280],
      },
      lanes: [{ id: 'main', label: 'M' }],
      nodes: [{
        id: 'tall', lane: 'main', col: 0, type: 'backend', label: 'Tall', height: 160,
      }],
      edges: [],
    },
    qualityProfile: 'showcase',
  });

  assert.equal(result.ok, false);
  assert.equal(result.svg, undefined);
  assert.equal(result.diagnostics.length, 1, JSON.stringify(result.diagnostics, null, 2));
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/viewbox-capacity');
  assert.deepEqual(diagnostic.evidence.actualViewBox, [1200, 280]);
  assert.equal(diagnostic.evidence.requiredViewBox[1], 374);
  const contributors = diagnostic.evidence.contributors;
  assert.ok(
    contributors.some((contributor) => (
      /node tall\b/i.test(contributor)
      && /height/i.test(contributor)
      && /160px/i.test(contributor)
    )),
    `contributors must name tall's authored 160px height: ${JSON.stringify(contributors)}`,
  );
  assert.equal(
    contributors.some((contributor) => /legend/i.test(contributor)),
    false,
    `a hidden legend must not be named as a height contributor: ${JSON.stringify(contributors)}`,
  );
});

test('readable-v2 measures asymmetric custom widths and CJK edge labels', () => {
  const cases = [
    { widths: [32, 92], label: '同步', nodeLabels: ['甲', '乙'] },
    { widths: [102, 124], label: '資料 ready', nodeLabels: ['入口', '出口'] },
    { widths: [132, 160], label: '审批通过', nodeLabels: ['请求', '执行'] },
    { widths: [92, 240], label: '结果 ✅ 回传', nodeLabels: ['工具', '外部服务'] },
  ];

  for (const current of cases) {
    const result = compileSuccessfully(adjacentWorkflow({
      fromCol: 1,
      label: current.label,
      widths: current.widths,
      nodeLabels: current.nodeLabels,
      viewBox: [1600, 420],
    }), 'showcase');
    assertReadableAdjacentResult(result, current);
  }
});

test('readable-v2 compares long-label gutter growth with a legal automatic channel', () => {
  const automaticDocument = adjacentWorkflow({
    fromCol: 3,
    label: 'L'.repeat(80),
  });
  const automatic = compileSuccessfully(automaticDocument, 'showcase');
  const forcedStraightDocument = clone(automaticDocument);
  forcedStraightDocument.edges[0].route = 'straight';
  const forcedStraight = compileSuccessfully(forcedStraightDocument, 'showcase');

  assert.ok(edgePoints(automatic.svg, 'ab').length > 2, 'the compact plan must use a legal channel');
  assert.ok(
    automatic.receipt.requiredViewBox[0] < forcedStraight.receipt.requiredViewBox[0],
    'the selected channel must require less canvas than forcing the direct gutter',
  );
  assertRectInsideViewBox(
    edgeLabelRect(automatic.svg, 'ab'),
    svgViewBox(automatic.svg),
    'long automatic edge label',
  );

  const leftRankDocument = adjacentWorkflow({ fromCol: 0, label: 'X'.repeat(100) });
  const leftRank = compileSuccessfully(leftRankDocument, 'showcase');
  assertRectInsideViewBox(
    edgeLabelRect(leftRank.svg, 'ab'),
    svgViewBox(leftRank.svg),
    'left-rank long automatic edge label',
  );
});

test('readable-v2 property matrix satisfies every adjacent rank across supported representative widths', () => {
  const widths = [32, 92, 102, 124, 132, 160, 240];
  for (let fromCol = 0; fromCol < 5; fromCol += 1) {
    for (const sourceWidth of widths) {
      for (const targetWidth of widths) {
        const result = compileSuccessfully(adjacentWorkflow({
          fromCol,
          label: '同步 ✅',
          widths: [sourceWidth, targetWidth],
        }), 'showcase');
        assertReadableAdjacentResult(result, {
          label: '同步 ✅',
          widths: [sourceWidth, targetWidth],
        });
      }
    }
  }
});

test('readable-v2 expands rank spans for measured phase and group label capacity', () => {
  const workflow = adjacentWorkflow({ fromCol: 0, frames: true });
  workflow.phases[0].label = 'P'.repeat(50);
  workflow.groups[0].label = 'G'.repeat(50);

  const result = compileSuccessfully(workflow, 'showcase');
  assert.ok(result.receipt.columns[1] - result.receipt.columns[0] > 120);
  assertReadableAdjacentResult(result);
});

test('readable-v2 contains a long single-rank group label in its frame and intrinsic canvas', () => {
  const workflow = {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: { title: 'Single-rank group capacity', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    groups: [{
      id: 'long-group',
      label: 'X'.repeat(120),
      lane: 'main',
      fromCol: 5,
      toCol: 5,
    }],
    nodes: [
      { id: 'outside', lane: 'main', col: 2, type: 'database', label: 'Outside' },
      { id: 'node', lane: 'main', col: 5, type: 'backend', label: 'Node' },
    ],
    edges: [],
  };

  const result = compileSuccessfully(workflow, 'showcase');
  assert.ok(result.receipt.requiredViewBox[0] > 768);
  const groupAttributes = result.svg.match(
    /<rect\b(?=[^>]*data-composition-frame-id="group-0")[^>]*\b(x="[^"]+"[^>]*)\/>/,
  )?.[1];
  assert.ok(groupAttributes);
  const group = numericRect(groupAttributes);
  assert.ok(group.width >= 120 * 5.6 + 20, `group width ${group.width} must contain its label`);
  const outside = nodeRect(result.svg, 'outside');
  assert.ok(group.x >= outside.x + outside.width, 'single-rank group must not cover an unrelated rank');
  const labelBaseline = Number(result.svg.match(/<text\b[^>]*\by="([^"]+)"[^>]*>X{120}<\/text>/)?.[1]);
  assert.ok(labelBaseline <= nodeRect(result.svg, 'node').y - 4, 'group label must remain above its node');
});

test('readable-v2 group frames contain custom-width member rectangles on every side', () => {
  const workflow = {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: { title: 'Wide group member', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    groups: [{ id: 'group', label: 'G', lane: 'main', fromCol: 2, toCol: 2 }],
    nodes: [{
      id: 'wide', lane: 'main', col: 2, type: 'backend', label: 'Wide', width: 240,
    }],
    edges: [],
  };

  const result = compileSuccessfully(workflow, 'showcase');
  assertRectInsideRect(
    nodeRect(result.svg, 'wide'),
    groupFrameRect(result.svg),
    'custom-width group member',
  );
});

test('readable-v2 group frames contain tagged and tall members away from the label rail', () => {
  const cases = [
    ['tagged member', { tag: 'TAG' }],
    ['120px-tall member', { height: 120 }],
  ];

  for (const [description, authoredGeometry] of cases) {
    const workflow = {
      schema_version: 2,
      diagram_type: 'workflow',
      meta: { title: description, legend: { mode: 'hidden' } },
      lanes: [{ id: 'main', label: 'Main' }],
      groups: [{ id: 'group', label: 'G', lane: 'main', fromCol: 0, toCol: 1 }],
      nodes: [{
        id: 'member',
        lane: 'main',
        col: 1,
        type: 'backend',
        label: 'Member',
        ...authoredGeometry,
      }],
      edges: [],
    };

    const result = compileSuccessfully(workflow, 'showcase');
    const member = nodeRect(result.svg, 'member');
    const groupLabel = asciiGroupLabelTextRect(result.svg, 'G');
    assert.ok(
      member.x >= groupLabel.x + groupLabel.width,
      `${description}: fixture must keep the member horizontally clear of its group label`,
    );
    assertRectInsideRect(member, groupFrameRect(result.svg), description);
  }
});

test('readable-v2 keeps a first-rank group label mask clear of its lane header mask', () => {
  const workflow = {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: { title: 'First-rank group labels', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    groups: [{ id: 'group', label: 'G', lane: 'main', fromCol: 0, toCol: 0 }],
    nodes: [{ id: 'member', lane: 'main', col: 0, type: 'backend', label: 'Member' }],
    edges: [],
  };

  const result = compileSuccessfully(workflow, 'showcase');
  const laneHeader = asciiLaneHeaderTextRect('Main');
  const groupLabel = asciiGroupLabelTextRect(result.svg, 'G');
  assert.equal(
    rectsOverlap(laneHeader, groupLabel),
    false,
    `lane header ${JSON.stringify(laneHeader)} must not overlap group label ${JSON.stringify(groupLabel)}`,
  );
});

test('readable-v2 measures multi-row legends into intrinsic and explicit viewBox capacity', () => {
  const workflow = adjacentWorkflow({ fromCol: 0 });
  workflow.meta.legend = {
    mode: 'all',
    entries: Object.fromEntries([
      'frontend', 'backend', 'security', 'messagebus', 'database', 'cloud', 'external',
    ].map((kind) => [kind, { label: `${kind} contract evidence` }])),
  };
  const hiddenDocument = clone(workflow);
  hiddenDocument.meta.legend = { mode: 'hidden' };
  const hidden = compileSuccessfully(hiddenDocument, 'showcase');
  const intrinsic = compileSuccessfully(workflow, 'showcase');
  assert.ok(intrinsic.receipt.requiredViewBox[1] > hidden.receipt.requiredViewBox[1]);

  const tooShortDocument = clone(workflow);
  tooShortDocument.meta.viewBox = [
    intrinsic.receipt.requiredViewBox[0],
    hidden.receipt.requiredViewBox[1],
  ];
  const tooShort = compileWorkflow({ workflow: tooShortDocument, qualityProfile: 'showcase' });
  assert.equal(tooShort.ok, false);
  assert.ok(tooShort.diagnostics.some(({ code }) => code === 'workflow/viewbox-capacity'));

  const sufficientDocument = clone(workflow);
  sufficientDocument.meta.viewBox = [...intrinsic.receipt.requiredViewBox];
  const sufficient = compileSuccessfully(sufficientDocument, 'showcase');
  assert.deepEqual(sufficient.receipt.viewBox, intrinsic.receipt.requiredViewBox);

  const expectedGeometry = {
    requiredViewBox: intrinsic.receipt.requiredViewBox,
    columns: intrinsic.receipt.columns,
    nodes: intrinsic.receipt.nodes,
    edges: intrinsic.receipt.edges,
    labels: intrinsic.receipt.labels,
    legend: legendGeometry(intrinsic.svg),
  };
  for (const width of [900, 1400]) {
    const explicitDocument = clone(workflow);
    explicitDocument.meta.viewBox = [width, 1000];
    const explicit = compileSuccessfully(explicitDocument, 'showcase');
    assert.deepEqual(explicit.receipt.requiredViewBox, expectedGeometry.requiredViewBox);
    assert.deepEqual(explicit.receipt.columns, expectedGeometry.columns);
    assert.deepEqual(explicit.receipt.nodes, expectedGeometry.nodes);
    assert.deepEqual(explicit.receipt.edges, expectedGeometry.edges);
    assert.deepEqual(explicit.receipt.labels, expectedGeometry.labels);
    assert.deepEqual(legendGeometry(explicit.svg), expectedGeometry.legend);
  }
});

test('a wide node in an unrelated lane does not expand the adjacent-rank gap', () => {
  const baselineDocument = adjacentWorkflow({
    fromCol: 1,
    label: 'liga',
    viewBox: [1600, 520],
  });
  baselineDocument.lanes.push({ id: 'other', label: 'Other' });
  const baseline = compileSuccessfully(baselineDocument, 'showcase');

  const withUnrelatedWideNode = clone(baselineDocument);
  withUnrelatedWideNode.nodes.push({
    id: 'wide', lane: 'other', col: 1, type: 'database', label: 'Wide', width: 240,
  });
  const widened = compileSuccessfully(withUnrelatedWideNode, 'showcase');

  assert.equal(
    widened.receipt.columns[2] - widened.receipt.columns[1],
    baseline.receipt.columns[2] - baseline.receipt.columns[1],
  );
});

test('explicit labelAt participates in readable-v2 viewBox containment', () => {
  const workflow = adjacentWorkflow({
    fromCol: 0,
    label: 'outside',
    viewBox: [900, 420],
  });
  workflow.nodes[1].col = 5;
  workflow.edges[0].labelAt = [563, 539];

  const result = compileWorkflow({ workflow, qualityProfile: 'showcase' });
  assert.equal(result.ok, false);
  assert.equal(result.diagnostics.length, 1, JSON.stringify(result.diagnostics, null, 2));
  const [diagnostic] = result.diagnostics;
  assert.equal(diagnostic.code, 'workflow/viewbox-capacity');
  assert.deepEqual(diagnostic.evidence.actualViewBox, [900, 420]);
  assert.ok(diagnostic.evidence.requiredViewBox[1] > 420);
  assert.ok(
    diagnostic.evidence.contributors.some((contributor) => /edge ab label/i.test(contributor)),
    JSON.stringify(diagnostic.evidence.contributors),
  );
  assert.doesNotMatch(diagnostic.supportedFixes.join('\n'), /drop|remove (?:the )?label|unlabel/i);
});

test('sanitized downstream fixtures cover labels, explicit routes, custom widths, and explicit viewBoxes', () => {
  const fixtureRoot = path.join(__dirname, 'fixtures', 'issue-126');

  const labels = compileSuccessfully(readJson(path.join(fixtureRoot, 'labels.workflow.json')), 'showcase');
  assertRectInsideViewBox(
    edgeLabelRect(labels.svg, 'request-result'),
    svgViewBox(labels.svg),
    'sanitized semantic label',
  );

  const explicitRoute = compileSuccessfully(
    readJson(path.join(fixtureRoot, 'explicit-route.workflow.json')),
    'showcase',
  );
  assert.ok(
    edgePoints(explicitRoute.svg, 'producer-consumer').some(([x]) => x === 720),
    'the authored channelX pin must remain exact',
  );

  const customWidths = compileSuccessfully(
    readJson(path.join(fixtureRoot, 'custom-widths.workflow.json')),
    'showcase',
  );
  assert.equal(nodeRect(customWidths.svg, 'compact').width, 32);
  assert.equal(nodeRect(customWidths.svg, 'wide').width, 240);

  const explicitViewBox = compileSuccessfully(
    readJson(path.join(fixtureRoot, 'explicit-viewbox.workflow.json')),
    'showcase',
  );
  assert.deepEqual(explicitViewBox.receipt.viewBox, [1600, 520]);
});

test('readable-v2 SVG and receipt bytes are deterministic across repeated and reordered input', () => {
  const workflow = {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: { title: 'Deterministic workflow', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'frontend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
      { id: 'c', lane: 'main', col: 4, type: 'database', label: 'C' },
    ],
    edges: [
      { id: 'ab', from: 'a', to: 'b', label: 'one' },
      { id: 'bc', from: 'b', to: 'c', label: 'two' },
    ],
  };

  const first = compileSuccessfully(clone(workflow), 'showcase');
  for (let iteration = 0; iteration < 3; iteration += 1) {
    const repeated = compileSuccessfully(clone(workflow), 'showcase');
    assert.equal(repeated.svg, first.svg);
    assert.equal(JSON.stringify(repeated.receipt), JSON.stringify(first.receipt));
  }

  const reordered = clone(workflow);
  reordered.nodes.reverse();
  reordered.edges.reverse();
  const reorderedResult = compileSuccessfully(reordered, 'showcase');
  assert.equal(reorderedResult.svg, first.svg);
  assert.equal(JSON.stringify(reorderedResult.receipt), JSON.stringify(first.receipt));
});

test('CLI validate workflow --layout-json prints the stable compiler receipt', () => {
  const input = path.join(tmp, 'layout-json.workflow.json');
  fs.writeFileSync(input, JSON.stringify(adjacentWorkflow({
    fromCol: 3,
    label: 'liga',
    viewBox: [1080, 420],
  })));

  const result = spawnSync(process.execPath, [
    cli,
    'validate',
    'workflow',
    input,
    '--layout-json',
    '--quality',
    'standard',
  ], { encoding: 'utf8' });

  assert.equal(result.status, 0, result.stderr);
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.contract, 'readable-v2');
  assert.deepEqual(receipt.viewBox, [1080, 420]);
  assert.ok(Array.isArray(receipt.requiredViewBox));
  assert.equal(receipt.columns.length, 6);
  assert.deepEqual(receipt.diagnostics, []);
});

test('CLI validate workflow --layout-json returns only the causal compiler failure receipt', () => {
  const input = path.join(tmp, 'layout-json-failure.workflow.json');
  fs.writeFileSync(input, JSON.stringify({
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Issue 126 failure receipt', legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 1, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [{ id: 'ab', from: 'a', to: 'b' }],
  }));

  const result = spawnSync(process.execPath, [
    cli,
    'validate',
    'workflow',
    input,
    '--layout-json',
    '--json',
  ], { encoding: 'utf8' });

  assert.equal(result.status, 1);
  assert.equal(result.stderr, '');
  const receipt = JSON.parse(result.stdout);
  assert.equal(receipt.contract, 'fixed-v1');
  assert.equal(receipt.diagnostics.length, 1, JSON.stringify(receipt.diagnostics, null, 2));
  assert.equal(receipt.diagnostics[0].code, 'workflow/column-capacity');
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/workflow-migration.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
  createHorizontalRankMapper,
  migrateWorkflowDocument,
} from '../migrations/workflow-v2.mjs';
import {
  createMappedWorkflowCandidate,
  intrinsicWorkflow,
  planningWorkflow,
} from '../renderers/workflow/workflow-migration-geometry.mjs';
import { compileWorkflow } from '../renderers/workflow/workflow-compiler.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const cli = path.join(skillRoot, 'bin', 'archify.mjs');
const fixture = path.join(
  __dirname,
  'fixtures',
  'v1-workflow-explicit-coordinates.workflow.json',
);
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-workflow-migration-'));

function sha256(value) {
  return createHash('sha256').update(value).digest('hex');
}

function runMigration(source, destination, { importModule, env } = {}) {
  return spawnSync(process.execPath, [
    ...(importModule ? ['--import', importModule] : []),
    cli,
    'migrate',
    'workflow',
    source,
    destination,
    '--to-schema',
    '2',
    '--json',
  ], {
    encoding: 'utf8',
    ...(env ? { env: { ...process.env, ...env } } : {}),
  });
}

function runValidation(source) {
  return spawnSync(process.execPath, [cli, 'validate', 'workflow', source, '--json'], {
    encoding: 'utf8',
  });
}

function parseJsonOutput(result) {
  assert.doesNotThrow(
    () => JSON.parse(result.stdout),
    `expected JSON stdout, received:\n${result.stdout}\nstderr:\n${result.stderr}`,
  );
  return JSON.parse(result.stdout);
}

function copyFixture(name, mutate = (value) => value) {
  const source = path.join(tmp, name);
  const value = mutate(JSON.parse(fs.readFileSync(fixture, 'utf8')));
  fs.writeFileSync(source, `${JSON.stringify(value, null, 2)}\n`);
  return source;
}

function explicitPinConflictWorkflow() {
  return {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: {
      title: 'Unmappable explicit label pin',
      viewBox: [900, 420],
      legend: { mode: 'hidden' },
    },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 5, type: 'backend', label: 'B' },
    ],
    edges: [{
      id: 'ab',
      from: 'a',
      to: 'b',
      label: 'outside',
      labelAt: [365, -20],
    }],
  };
}

function heightConstrainedWorkflow() {
  return {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: {
      title: 'Legacy height capacity',
      viewBox: [720, 240],
      legend: { mode: 'hidden' },
    },
    lanes: [
      { id: 'first', label: 'First' },
      { id: 'second', label: 'Second' },
    ],
    nodes: [
      { id: 'a', lane: 'first', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'second', col: 2, type: 'database', label: 'B' },
    ],
    edges: [],
  };
}

function profileDivergenceWorkflow() {
  return {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: {
      title: 'profile divergence',
      quality_profile: 'showcase',
      legend: { mode: 'hidden' },
    },
    lanes: [
      { id: 'top', label: 'Top' },
      { id: 'middle', label: 'Middle' },
      { id: 'bottom', label: 'Bottom' },
      { id: 'issue', label: 'Issue' },
    ],
    nodes: [
      { id: 'left', lane: 'middle', col: 0, type: 'backend', label: 'Left' },
      { id: 'right', lane: 'middle', col: 4, type: 'backend', label: 'Right' },
      { id: 'above', lane: 'top', col: 2, type: 'backend', label: 'Above' },
      { id: 'below', lane: 'bottom', col: 2, type: 'backend', label: 'Below' },
      { id: 'a', lane: 'issue', col: 1, type: 'backend', label: 'A' },
      { id: 'b', lane: 'issue', col: 2, type: 'backend', label: 'B' },
    ],
    edges: [
      { id: 'ab', from: 'a', to: 'b' },
      { id: 'horizontal', from: 'left', to: 'right', via: [[244, 243]] },
      { id: 'vertical', from: 'above', to: 'below', via: [[300, 200]] },
    ],
  };
}

test('migration geometry helpers construct mapped v2 candidates without mutating the source', () => {
  const source = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Pure migration geometry', viewBox: [720, 400], legend: { mode: 'hidden' } },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [
      { id: 'a', lane: 'main', col: 1, type: 'backend', label: 'A' },
      { id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
    ],
    mainPath: ['a', 'b'],
    edges: [{
      id: 'ab',
      from: 'a',
      to: 'b',
      label: 'pin',
      fromSide: 'top',
      toSide: 'top',
      via: [[220, 60], [260, 60], [300, 60]],
      labelAt: [260, 42],
      channelX: 260,
    }],
  };
  const sourceBefore = JSON.parse(JSON.stringify(source));

  const intrinsic = intrinsicWorkflow(source);
  assert.equal(intrinsic.schema_version, 2);
  assert.equal('viewBox' in intrinsic.meta, false);
  assert.deepEqual(intrinsic.edges, source.edges);

  const planning = planningWorkflow(source);
  assert.deepEqual(planning.edges, []);
  assert.equal('mainPath' in planning, false);

  const mapX = createHorizontalRankMapper([88, 220, 300], [94, 214, 334]);
  assert.equal(mapX(260), 274, 'the migration module must preserve the mapper re-export');

  const mapped = createMappedWorkflowCandidate(
    source,
    [88, 220, 300],
    [94, 214, 334],
  );
  assert.equal(mapped.document.schema_version, 2);
  assert.deepEqual(mapped.document.meta.viewBox, [720, 400]);
  assert.deepEqual(mapped.document.edges[0].via, [[214, 60], [274, 60], [334, 60]]);
  assert.deepEqual(mapped.document.edges[0].labelAt, [274, 42]);
  assert.equal(mapped.document.edges[0].channelX, 274);
  assert.deepEqual(mapped.changedCoordinates, [
    { path: '/edges/0/via/0/0', from: 220, to: 214 },
    { path: '/edges/0/via/1/0', from: 260, to: 274 },
    { path: '/edges/0/via/2/0', from: 300, to: 334 },
    { path: '/edges/0/labelAt/0', from: 260, to: 274 },
    { path: '/edges/0/channelX', from: 260, to: 274 },
  ]);
  assert.deepEqual(source, sourceBefore);
});

test('document migration treats a legacy explicit viewBox as capacity and expands it monotonically', () => {
  const source = heightConstrainedWorkflow();
  const sourceBefore = JSON.parse(JSON.stringify(source));

  const migration = migrateWorkflowDocument(source);

  assert.equal(migration.ok, true, JSON.stringify(migration, null, 2));
  assert.deepEqual(source, sourceBefore, 'the document migration must not mutate its input');
  assert.deepEqual(migration.document.meta.viewBox, [768, 404]);
  assert.deepEqual(migration.document.meta.viewBox, migration.newRequiredViewBox);
  assert.ok(migration.document.meta.viewBox[0] >= source.meta.viewBox[0]);
  assert.ok(migration.document.meta.viewBox[1] >= source.meta.viewBox[1]);
  assert.ok(migration.preExistingDiagnostics.some(({ message }) => (
    /viewBox height 240/.test(message)
  )), JSON.stringify(migration.preExistingDiagnostics, null, 2));
  assert.deepEqual(migration.migrationDiagnostics, []);
  assert.deepEqual(migration.newSchemaDiagnostics, []);
});

test('CLI atomically commits a capacity-expanded migration and preserves source bytes', () => {
  const source = path.join(tmp, 'height-capacity-source.workflow.json');
  const destination = path.join(tmp, 'height-capacity-destination.workflow.json');
  const sourceBytes = Buffer.from(`${JSON.stringify(heightConstrainedWorkflow(), null, 2)}\n`);
  fs.writeFileSync(source, sourceBytes);
  fs.writeFileSync(destination, 'destination sentinel\n');

  const result = runMigration(source, destination);

  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.deepEqual(fs.readFileSync(source), sourceBytes, 'the CLI must preserve source bytes');
  const destinationBytes = fs.readFileSync(destination);
  const migrated = JSON.parse(destinationBytes);
  const report = parseJsonOutput(result);
  assert.equal(report.ok, true);
  assert.equal(migrated.schema_version, 2);
  assert.deepEqual(migrated.meta.viewBox, [768, 404]);
  assert.deepEqual(migrated.meta.viewBox, report.newRequiredViewBox);
  assert.ok(migrated.meta.viewBox[0] >= 720);
  assert.ok(migrated.meta.viewBox[1] >= 240);
  assert.deepEqual(report.destination, {
    path: path.resolve(destination),
    sha256: sha256(destinationBytes),
    bytes: destinationBytes.length,
  });
  assert.deepEqual(report.migrationDiagnostics, []);
  assert.deepEqual(report.newSchemaDiagnostics, []);
});

test('column-capacity diagnostics do not advertise migration across a quality-profile divergence', () => {
  const document = profileDivergenceWorkflow();
  const standard = compileWorkflow({ workflow: document, qualityProfile: 'standard' });
  assert.equal(standard.ok, false);
  const capacity = standard.diagnostics.find(({ code }) => code === 'workflow/column-capacity');
  assert.ok(capacity, JSON.stringify(standard.diagnostics, null, 2));

  const migration = migrateWorkflowDocument(document);
  assert.equal(migration.ok, false, 'the authored showcase migration must reject its pinned crossing');
  assert.ok(migration.newSchemaDiagnostics.some(({ code, evidence }) => (
    code === 'workflow/explicit-pin-conflict'
    && evidence?.invariant === 'explicit route-route crossing'
  )), JSON.stringify(migration, null, 2));
  assert.ok(
    capacity.supportedFixes.every((fix) => !/migrate this workflow/i.test(fix)),
    `a standard-only verification must not advertise the failing authored showcase migration: ${capacity.supportedFixes}`,
  );
});

test('document migration uses the authored profile and returns an independently compilable document', () => {
  const document = profileDivergenceWorkflow();
  document.meta.quality_profile = 'standard';

  const migration = migrateWorkflowDocument(document);

  assert.equal(migration.ok, true, JSON.stringify(migration, null, 2));
  assert.deepEqual(migration.migrationDiagnostics, []);
  assert.deepEqual(migration.newSchemaDiagnostics, []);
  const standalone = compileWorkflow({ workflow: migration.document });
  assert.equal(standalone.ok, true, JSON.stringify(standalone.diagnostics, null, 2));
});

test('document migration defaults an omitted profile to standard', () => {
  const document = profileDivergenceWorkflow();
  delete document.meta.quality_profile;

  const migration = migrateWorkflowDocument(document);

  assert.equal(migration.ok, true, JSON.stringify(migration, null, 2));
  const standalone = compileWorkflow({ workflow: migration.document });
  assert.equal(standalone.ok, true, JSON.stringify(standalone.diagnostics, null, 2));
});

test('CLI migration ignores ambient standard for an authored showcase workflow', () => {
  const source = path.join(tmp, 'authored-showcase-source.workflow.json');
  const destination = path.join(tmp, 'authored-showcase-destination.workflow.json');
  const sourceBytes = Buffer.from(`${JSON.stringify(profileDivergenceWorkflow(), null, 2)}\n`);
  fs.writeFileSync(source, sourceBytes);

  const migration = runMigration(source, destination, {
    env: { ARCHIFY_QUALITY_PROFILE: 'standard' },
  });

  assert.notEqual(migration.status, 0);
  assert.deepEqual(fs.readFileSync(source), sourceBytes);
  assert.equal(fs.existsSync(destination), false);
  const report = parseJsonOutput(migration);
  assert.equal(report.ok, false);
  assert.ok(report.newSchemaDiagnostics.some(({ code, evidence }) => (
    code === 'workflow/explicit-pin-conflict'
    && evidence?.invariant === 'explicit route-route crossing'
  )), JSON.stringify(report, null, 2));
});

test('CLI migration ignores ambient showcase for an authored standard workflow', () => {
  const source = path.join(tmp, 'authored-standard-source.workflow.json');
  const destination = path.join(tmp, 'authored-standard-destination.workflow.json');
  const document = profileDivergenceWorkflow();
  document.meta.quality_profile = 'standard';
  const sourceBytes = Buffer.from(`${JSON.stringify(document, null, 2)}\n`);
  fs.writeFileSync(source, sourceBytes);

  const migration = runMigration(source, destination, {
    env: { ARCHIFY_QUALITY_PROFILE: 'showcase' },
  });

  assert.equal(migration.status, 0, migration.stderr || migration.stdout);
  assert.deepEqual(fs.readFileSync(source), sourceBytes);
  assert.equal(fs.existsSync(destination), true);
  const report = parseJsonOutput(migration);
  assert.equal(report.ok, true);
  const migrated = JSON.parse(fs.readFileSync(destination, 'utf8'));
  const standalone = compileWorkflow({ workflow: migrated });
  assert.equal(standalone.ok, true, JSON.stringify(standalone.diagnostics, null, 2));
});

test('CLI migration makes the blocking new-schema diagnostic primary over pre-existing findings', () => {
  const source = path.join(tmp, 'blocking-diagnostic-source.workflow.json');
  const destination = path.join(tmp, 'blocking-diagnostic-destination.workflow.json');
  fs.writeFileSync(source, `${JSON.stringify(profileDivergenceWorkflow(), null, 2)}\n`);

  const migration = runMigration(source, destination, {
    env: { ARCHIFY_QUALITY_PROFILE: 'showcase' },
  });

  assert.notEqual(migration.status, 0);
  assert.equal(fs.existsSync(destination), false);
  const report = parseJsonOutput(migration);
  assert.equal(report.ok, false);
  assert.equal(report.preExistingDiagnostics[0].code, 'workflow/column-capacity');
  const blocker = report.newSchemaDiagnostics.find(({ code }) => code === 'workflow/explicit-pin-conflict');
  assert.ok(blocker, JSON.stringify(report, null, 2));
  assert.equal(report.error, blocker.message);
  assert.deepEqual(report.diagnostics[0], blocker);
  assert.ok(report.diagnostics.some(({ code }) => code === 'workflow/column-capacity'));
});

test('workflow migration remaps absolute x coordinates, expands only the constrained viewBox axis, and reports hashes', () => {
  const source = copyFixture('pinned-source.workflow.json');
  const destination = path.join(tmp, 'pinned-destination.workflow.json');
  const sourceBefore = fs.readFileSync(source);

  const result = runMigration(source, destination);
  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.deepEqual(fs.readFileSync(source), sourceBefore, 'migration must not mutate its source bytes');
  assert.equal(fs.existsSync(destination), true);

  const migratedBytes = fs.readFileSync(destination);
  const migrated = JSON.parse(migratedBytes);
  assert.equal(migrated.schema_version, 2);
  assert.deepEqual(migrated.meta.viewBox, [768, 700], 'width grows to fit v2 while sufficient height is preserved');
  assert.deepEqual(migrated.edges[0].via, [[214, 119]]);
  assert.deepEqual(migrated.edges[1].labelAt, [394, 203]);
  assert.equal(migrated.edges[2].channelX, 574);

  const report = parseJsonOutput(result);
  assert.equal(report.ok, true);
  assert.equal(report.command, 'migrate');
  assert.equal(report.type, 'workflow');
  assert.equal(report.fromSchemaVersion, 1);
  assert.equal(report.toSchemaVersion, 2);
  assert.deepEqual(report.source, {
    path: path.resolve(source),
    sha256: sha256(sourceBefore),
    bytes: sourceBefore.length,
  });
  assert.deepEqual(report.destination, {
    path: path.resolve(destination),
    sha256: sha256(migratedBytes),
    bytes: migratedBytes.length,
  });
  assert.deepEqual(report.preExistingDiagnostics, []);
  assert.ok(Array.isArray(report.migrationDiagnostics));
  assert.deepEqual(report.newSchemaDiagnostics, []);
  assert.deepEqual(report.changedCoordinates, [
    { path: '/edges/0/via/0/0', from: 220, to: 214 },
    { path: '/edges/1/labelAt/0', from: 365, to: 394 },
    { path: '/edges/2/channelX', from: 500, to: 574 },
  ]);
  assert.deepEqual(report.oldRequiredViewBox, [720, 652]);
  assert.deepEqual(report.newRequiredViewBox, [768, 652]);
});

test('workflow migration never shrinks an already spacious explicit viewBox', () => {
  const source = copyFixture('spacious-source.workflow.json', (workflow) => {
    workflow.meta.viewBox = [1600, 900];
    return workflow;
  });
  const destination = path.join(tmp, 'spacious-destination.workflow.json');

  const result = runMigration(source, destination);
  assert.equal(result.status, 0, result.stderr || result.stdout);
  const migrated = JSON.parse(fs.readFileSync(destination, 'utf8'));
  assert.deepEqual(migrated.meta.viewBox, [1600, 900]);
  assert.equal(parseJsonOutput(result).destination.sha256, sha256(fs.readFileSync(destination)));
});

test('workflow migration reports the measured legacy requirement separately from authored capacity', () => {
  const source = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: {
      title: 'Spacious legacy capacity',
      viewBox: [1600, 900],
      legend: { mode: 'hidden' },
    },
    lanes: [{ id: 'main', label: 'Main' }],
    nodes: [{ id: 'only', lane: 'main', col: 0, type: 'backend', label: 'Only' }],
    edges: [],
  };

  const migration = migrateWorkflowDocument(source);

  assert.equal(migration.ok, true, JSON.stringify(migration, null, 2));
  assert.deepEqual(migration.oldRequiredViewBox, [720, 280]);
  assert.deepEqual(migration.document.meta.viewBox, [1600, 900]);
  assert.deepEqual(migration.newRequiredViewBox, [768, 280]);
});

test('workflow migration staging never aliases a destination named like its verification artifact', () => {
  const source = copyFixture('artifact-name-source.workflow.json');
  const destination = path.join(tmp, 'migration-check.html');

  const result = runMigration(source, destination);
  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.equal(JSON.parse(fs.readFileSync(destination, 'utf8')).schema_version, 2);
  assert.equal(parseJsonOutput(result).destination.path, path.resolve(destination));
});

test('workflow migration cleanup failure warns without reversing a successful commit', () => {
  const source = copyFixture('cleanup-warning-source.workflow.json');
  const destination = path.join(tmp, 'cleanup-warning-destination.workflow.json');
  const importModule = path.join(__dirname, 'fixtures', 'fail-migration-cleanup.mjs');

  const result = runMigration(source, destination, { importModule });

  assert.equal(result.status, 0, result.stderr || result.stdout);
  assert.equal(JSON.parse(fs.readFileSync(destination, 'utf8')).schema_version, 2);
  assert.equal(parseJsonOutput(result).ok, true);
  assert.match(result.stderr, /Warning: could not remove workflow migration staging directory/);
  assert.match(result.stderr, /simulated migration cleanup failure/);
});

test('workflow migration is idempotent when its v2 destination is migrated again', () => {
  const source = copyFixture('idempotent-source.workflow.json');
  const firstDestination = path.join(tmp, 'idempotent-first.workflow.json');
  const secondDestination = path.join(tmp, 'idempotent-second.workflow.json');

  const first = runMigration(source, firstDestination);
  assert.equal(first.status, 0, first.stderr || first.stdout);
  const second = runMigration(firstDestination, secondDestination);
  assert.equal(second.status, 0, second.stderr || second.stdout);

  const firstBytes = fs.readFileSync(firstDestination);
  const secondBytes = fs.readFileSync(secondDestination);
  assert.deepEqual(secondBytes, firstBytes);

  const report = parseJsonOutput(second);
  assert.equal(report.fromSchemaVersion, 2);
  assert.equal(report.toSchemaVersion, 2);
  assert.deepEqual(report.changedCoordinates, []);
  assert.deepEqual(report.preExistingDiagnostics, []);
  assert.deepEqual(report.migrationDiagnostics, []);
  assert.deepEqual(report.newSchemaDiagnostics, []);
  assert.equal(report.destination.sha256, report.source.sha256);
  assert.deepEqual(report.newRequiredViewBox, report.oldRequiredViewBox);
});

test('fallback planning preserves straight-edge rank constraints when mapping absolute pins', () => {
  const source = path.join(tmp, 'fallback-rank-source.workflow.json');
  const destination = path.join(tmp, 'fallback-rank-destination.workflow.json');
  const document = {
    schema_version: 1,
    diagram_type: 'workflow',
    meta: { title: 'Fallback rank mapping', viewBox: [720, 520], legend: { mode: 'hidden' } },
    lanes: [
      { id: 'pin', label: 'Pinned label' },
      { id: 'straight', label: 'Straight constraint' },
      { id: 'blocker', label: 'Blocker' },
    ],
    nodes: [
      { id: 'a', lane: 'pin', col: 0, type: 'backend', label: 'A' },
      { id: 'b', lane: 'pin', col: 1, type: 'backend', label: 'B' },
      { id: 'c', lane: 'straight', col: 0, type: 'backend', label: 'C' },
      { id: 'd', lane: 'straight', col: 1, type: 'backend', label: 'D' },
      { id: 'obstacle', lane: 'blocker', col: 4, type: 'database', label: 'Obstacle' },
    ],
    mainPath: ['a', 'b'],
    edges: [
      {
        id: 'pin-edge',
        from: 'a',
        to: 'b',
        label: 'p',
        labelAt: [562, 367],
        route: 'bottom-channel',
        fromSide: 'bottom',
        toSide: 'bottom',
      },
      { id: 'straight-edge', from: 'c', to: 'd', label: 'x', route: 'straight' },
    ],
  };
  fs.writeFileSync(source, `${JSON.stringify(document, null, 2)}\n`);

  const result = runMigration(source, destination);
  assert.equal(result.status, 0, result.stderr || result.stdout);
  const migrated = JSON.parse(fs.readFileSync(destination, 'utf8'));
  assert.deepEqual(migrated.edges[0].labelAt, [643.52, 367]);
  const report = parseJsonOutput(result);
  assert.deepEqual(report.changedCoordinates, [{
    path: '/edges/0/labelAt/0',
    from: 562,
    to: 643.52,
  }]);
  assert.deepEqual(report.newSchemaDiagnostics, []);
});

for (const { example, expectedEdge } of [
  { example: 'incident-response.workflow.json', expectedEdge: ['alert', 'page'] },
  { example: 'release-delivery.workflow.json', expectedEdge: ['pull_request', 'build'] },
]) {
  test(`workflow migration preserves causal route diagnostics for packaged ${example}`, () => {
    const source = path.join(skillRoot, 'examples', example);
    const destination = path.join(tmp, `migrated-${example}`);
    const sourceBefore = fs.readFileSync(source);

    const validation = runValidation(source);
    assert.equal(validation.status, 0, validation.stderr || validation.stdout);
    const result = runMigration(source, destination);
    assert.notEqual(result.status, 0);
    assert.equal(fs.existsSync(destination), false);
    assert.deepEqual(fs.readFileSync(source), sourceBefore);

    const failure = parseJsonOutput(result);
    assert.equal(failure.diagnostics.length, 1, JSON.stringify(failure.diagnostics, null, 2));
    assert.ok(failure.diagnostics.every(({ message }) => !/mainPath step .* no matching edge/.test(message)));
    const [diagnostic] = failure.diagnostics;
    assert.equal(diagnostic.code, 'workflow/route-preset-conflict');
    assert.deepEqual([diagnostic.subject?.from, diagnostic.subject?.to], expectedEdge);
    assert.equal(diagnostic.subject?.route, 'drop');
  });
}

test('workflow migration rejects the source path as its destination without changing the file', () => {
  const source = copyFixture('same-path.workflow.json');
  const sourceBefore = fs.readFileSync(source);

  const result = runMigration(source, source);
  assert.notEqual(result.status, 0);
  assert.deepEqual(fs.readFileSync(source), sourceBefore);

  const failure = parseJsonOutput(result);
  assert.equal(failure.ok, false);
  assert.equal(failure.command, 'migrate');
  assert.ok(Array.isArray(failure.diagnostics));
  assert.equal(failure.diagnostics.length, 1);
  assert.equal(failure.diagnostics[0].code, 'migration/source-destination');
  assert.match(failure.error, /source|destination/i);
  assert.ok(failure.diagnostics[0].supportedFixes.some((fix) => /different.*destination/i.test(fix)));
});

test('workflow migration reports a cyclic-symlink destination as structured JSON', () => {
  const source = copyFixture('symlink-cycle-source.workflow.json');
  const destination = path.join(tmp, 'migration-cycle-a.workflow.json');
  const otherLink = path.join(tmp, 'migration-cycle-b.workflow.json');
  fs.symlinkSync(otherLink, destination, 'file');
  fs.symlinkSync(destination, otherLink, 'file');

  const result = runMigration(source, destination);
  assert.notEqual(result.status, 0);
  assert.equal(result.stderr, '');
  const failure = parseJsonOutput(result);
  assert.equal(failure.ok, false);
  assert.equal(failure.diagnostics[0].code, 'output/symlink-cycle');
  assert.equal(failure.diagnostics[0].subject.output, path.resolve(destination));
});

test('failed workflow migration emits diagnostics and never writes its destination', () => {
  const source = path.join(tmp, 'pin-conflict-source.workflow.json');
  const destination = path.join(tmp, 'pin-conflict-destination.workflow.json');
  fs.writeFileSync(source, `${JSON.stringify(explicitPinConflictWorkflow(), null, 2)}\n`);
  assert.equal(fs.existsSync(destination), false);

  const result = runMigration(source, destination);
  assert.notEqual(result.status, 0);
  assert.equal(fs.existsSync(destination), false, 'a failed migration must not leave a partial destination');

  const failure = parseJsonOutput(result);
  assert.equal(failure.ok, false);
  assert.equal(failure.command, 'migrate');
  assert.ok(Array.isArray(failure.diagnostics));
  assert.ok(
    failure.diagnostics.some(({ code }) => code === 'workflow/explicit-pin-conflict'),
    JSON.stringify(failure.diagnostics, null, 2),
  );
});

process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
```

## test/workflow-semantic-contract.test.mjs

```js
import { test } from 'node:test';
import assert from 'node:assert/strict';

import { compileWorkflow } from '../renderers/workflow/workflow-compiler.mjs';

function document(semanticChecks) {
  return {
    schema_version: 2,
    diagram_type: 'workflow',
    meta: {
      title: 'Workflow semantic contract fixture',
      legend: { mode: 'hidden' },
    },
    lanes: [
      { id: 'main', label: 'Main' },
      { id: 'recovery', label: 'Recovery' },
    ],
    nodes: [
      { id: 'input', lane: 'main', col: 0, type: 'external', label: 'Input' },
      { id: 'process', lane: 'main', col: 2, type: 'backend', label: 'Process' },
      { id: 'result', lane: 'main', col: 4, type: 'frontend', label: 'Result' },
      { id: 'ledger', lane: 'recovery', col: 4, type: 'database', label: 'Ledger' },
      { id: 'resume', lane: 'recovery', col: 1, type: 'backend', label: 'Resume' },
    ],
    edges: [
      { id: 'start', from: 'input', to: 'process' },
      { id: 'finish', from: 'process', to: 'result' },
      { id: 'record', from: 'process', to: 'ledger' },
    ],
    ...(semanticChecks ? { semanticChecks } : {}),
  };
}

test('semanticChecks rejects an undeclared root instead of accepting an orphan recovery node', () => {
  const workflow = document({
    allowedRoots: ['input'],
  });

  const result = compileWorkflow({ workflow, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.ok(
    result.diagnostics.some((diagnostic) => (
      diagnostic.code === 'workflow/unexpected-root'
      && diagnostic.subject?.node === 'resume'
    )),
    JSON.stringify(result.diagnostics, null, 2),
  );
});

test('semanticChecks rejects a missing required edge with its exact endpoints', () => {
  const workflow = document({
    requiredEdges: [{ from: 'ledger', to: 'resume' }],
  });

  const result = compileWorkflow({ workflow, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.ok(
    result.diagnostics.some((diagnostic) => (
      diagnostic.code === 'workflow/required-edge'
      && diagnostic.subject?.from === 'ledger'
      && diagnostic.subject?.to === 'resume'
    )),
    JSON.stringify(result.diagnostics, null, 2),
  );
});

test('semanticChecks accepts required reachability through intermediate nodes without changing SVG bytes', () => {
  const baseline = document();
  const constrained = document({
    allowedRoots: ['input', 'resume'],
    allowedTerminals: ['result', 'ledger', 'resume'],
    requiredEdges: [{ from: 'process', to: 'ledger' }],
    requiredPaths: [{ from: 'input', to: 'result' }],
  });

  const baselineResult = compileWorkflow({ workflow: baseline, qualityProfile: 'standard' });
  const constrainedResult = compileWorkflow({ workflow: constrained, qualityProfile: 'standard' });

  assert.equal(baselineResult.ok, true, JSON.stringify(baselineResult.diagnostics, null, 2));
  assert.equal(constrainedResult.ok, true, JSON.stringify(constrainedResult.diagnostics, null, 2));
  assert.equal(constrainedResult.svg, baselineResult.svg);
  assert.deepEqual(constrainedResult.receipt, baselineResult.receipt);
});

test('semanticChecks rejects a required path that is not reachable in authored direction', () => {
  const workflow = document({
    requiredPaths: [{ from: 'ledger', to: 'resume' }],
  });

  const result = compileWorkflow({ workflow, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.ok(
    result.diagnostics.some((diagnostic) => diagnostic.code === 'workflow/required-path'),
    JSON.stringify(result.diagnostics, null, 2),
  );
});

test('semanticChecks rejects an undeclared terminal', () => {
  const workflow = document({
    allowedTerminals: ['result', 'ledger'],
  });

  const result = compileWorkflow({ workflow, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.ok(
    result.diagnostics.some((diagnostic) => (
      diagnostic.code === 'workflow/unexpected-terminal'
      && diagnostic.subject?.node === 'resume'
    )),
    JSON.stringify(result.diagnostics, null, 2),
  );
});

test('semanticChecks reports unknown contract node ids before graph analysis', () => {
  const workflow = document({
    requiredEdges: [{ from: 'missing', to: 'resume' }],
  });

  const result = compileWorkflow({ workflow, qualityProfile: 'standard' });

  assert.equal(result.ok, false);
  assert.deepEqual(
    result.diagnostics.map(({ code }) => code),
    ['workflow/semantic-node-reference'],
  );
  assert.equal(result.diagnostics[0].subject.path, '/semanticChecks/requiredEdges/0/from');
});
```

