Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c86992f
EDGE-642: record a decision for every occurrence of both words
Thiago-Pio-Autonomy Sep 9, 2026
21230b3
EDGE-642: move the Orchestrator section to Devices, paths only
Thiago-Pio-Autonomy Sep 9, 2026
d08ae9c
EDGE-642: repoint the navigation at the Devices section
Thiago-Pio-Autonomy Sep 9, 2026
073ad86
EDGE-642: correct the census where a changing occurrence was recorded…
Thiago-Pio-Autonomy Sep 9, 2026
81e4bf2
EDGE-642: name the loose-alternation defect in the census record
Thiago-Pio-Autonomy Sep 9, 2026
e14886d
EDGE-642: rewrite the Devices section and device-not-connecting in th…
Thiago-Pio-Autonomy Sep 9, 2026
8413611
EDGE-642: quote the interface strings Edge actually ships
Thiago-Pio-Autonomy Sep 9, 2026
59c7f92
fix(docs): name the agent container orchestrator_agent, the name it a…
Thiago-Pio-Autonomy Sep 9, 2026
78fd764
EDGE-642: add D-PROSE-NEW, commit the link checker, tag every census …
Thiago-Pio-Autonomy Sep 9, 2026
4a73e93
EDGE-642: say Device and vPLC across platform, billing, account and r…
Thiago-Pio-Autonomy Sep 9, 2026
a1c6fc5
EDGE-642: say Device and vPLC on the getting-started pages, except qu…
Thiago-Pio-Autonomy Sep 9, 2026
13934dc
EDGE-642: rewrite quick-start and rename the six captures it quotes
Thiago-Pio-Autonomy Sep 9, 2026
0a312cd
EDGE-642: run the sense-2 pass over the 44 machine-sense occurrences
Thiago-Pio-Autonomy Sep 9, 2026
5492a81
EDGE-642: consolidate the glossary onto Device, Device Agent and vPLC
Thiago-Pio-Autonomy Sep 9, 2026
02206ee
EDGE-642: record what the reproducibility gate caught, and where Phas…
Thiago-Pio-Autonomy Sep 9, 2026
ff5215c
EDGE-642: say Edge Device throughout the OpenPLC Editor documentation
Thiago-Pio-Autonomy Sep 9, 2026
bac333b
EDGE-642: produce the image map, measured rather than inherited
Thiago-Pio-Autonomy Sep 9, 2026
bf0e933
EDGE-642: delete the 26 dead images, its own commit so it reviews alone
Thiago-Pio-Autonomy Sep 9, 2026
36e380e
EDGE-642: close the census gate on the final tree, and record what di…
Thiago-Pio-Autonomy Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# macOS
.DS_Store
**/.DS_Store

# Python bytecode from the docs tooling
__pycache__/
*.pyc
24 changes: 12 additions & 12 deletions docs/_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,33 +69,33 @@
]
},
{
"title": "Orchestrators",
"path": "orchestrators",
"title": "Devices",
"path": "devices",
"items": [
{
"title": "Overview",
"path": "overview"
},
{
"title": "Installing the agent",
"title": "Installing the Device Agent",
"path": "installing-the-agent"
},
{
"title": "Orchestrators list",
"path": "orchestrators-list"
"title": "Devices list",
"path": "devices-list"
},
{
"title": "Orchestrator detail",
"path": "orchestrator-detail"
"title": "Device detail",
"path": "device-detail"
},
{
"title": "Managing orchestrators",
"path": "managing-orchestrators"
"title": "Managing devices",
"path": "managing-devices"
}
]
},
{
"title": "vPLC devices",
"title": "vPLCs",
"path": "vplcs",
"items": [
{
Expand Down Expand Up @@ -863,8 +863,8 @@
"path": "troubleshooting",
"items": [
{
"title": "Orchestrator not connecting",
"path": "orchestrator-not-connecting"
"title": "Device not connecting",
"path": "device-not-connecting"
},
{
"title": "vPLC stuck in Stopped",
Expand Down
4 changes: 2 additions & 2 deletions docs/account/settings/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ To open it, click your avatar in the top-right, choose **Settings**, then **Acco

The page contains one section, labeled **Danger zone** in red, with the following copy:

> Deleting your account is permanent. Personal projects, orchestrators and devices will be removed. Forum posts, topics and direct messages remain visible, credited to **[deleted]**. If you own organizations, you must transfer them or delete them first.
> Deleting your account is permanent. Personal projects, Devices and vPLCs will be removed. Forum posts, topics and direct messages remain visible, credited to **[deleted]**. If you own organizations, you must transfer them or delete them first.

A red **Delete my account** button below the description.

Expand All @@ -19,7 +19,7 @@ A red **Delete my account** button below the description.
**Removed permanently:**

- Your personal projects (public and private).
- Your orchestrators and their vPLC devices.
- Your Devices and their vPLCs.
- Your stored payment methods.
- Your active subscription (canceled immediately; no refunds for unused time).
- Your profile fields (name, bio, avatar, location, time zone).
Expand Down
6 changes: 3 additions & 3 deletions docs/account/settings/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ When you run an AI Engineer job:

Below the ACU card, a **Usage** section shows your consumption against plan-imposed quotas. Three bars:

- **Orchestrators**: N / M used.
- **Devices**: N / M used.
- **vPLCs**: N / M used.
- **Private projects**: N / M used.

A bar turns **red** when you've reached or exceeded the limit. If you exceed (typically because of a recent downgrade), existing items continue working but you can't create new ones until you're back under the limit.
Expand All @@ -59,8 +59,8 @@ A small **refresh icon** at the top-right of the Usage card forces a recount.

| Limit | Counts |
|---|---|
| **Orchestrators** | The number of orchestrator entries in your workspace (Active + Inactive). |
| **Devices** | The number of vPLC entries across all your orchestrators. |
| **Devices** | The number of Device entries in your workspace (Active + Inactive). |
| **vPLCs** | The number of vPLC entries across all your Devices. |
| **Private projects** | Projects marked Private. Public projects don't count. |

## Increasing limits
Expand Down
758 changes: 758 additions & 0 deletions docs/exploration/RENAME-DECISION-RECORD.md

Large diffs are not rendered by default.

356 changes: 356 additions & 0 deletions docs/exploration/RENAME-IMAGE-MAP.md

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions docs/exploration/br03-spelling-check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""BR03 spelling gate for user-docs. Run: python3 br03-spelling-check.py <docs-dir>

BR03, from the EDGE-633 Requirements Gathering: "The spelling is vPLC, lowercase
v and uppercase PLC, plural vPLCs. The forms VPLC, Vplc and vPlc are not
accepted."

THE ONE THING THAT MAKES THIS GATE HONEST: it checks PROSE, so it masks

- fenced code blocks,
- inline `code` spans,
- link and image TARGETS, i.e. the part inside ](...)

before it looks, and it skips exploration/ entirely.

MEASURED, because a smaller figure was carried into Phase 10 and did not
reproduce. The Phase 7 record predicted 13 violations unmasked. Against the
finished tree it is:

no masking at all ................ 145
code spans masked only ........... 77
link targets masked only ......... 70
both masked ...................... 2
both masked, exploration/ skipped 0

So the inherited 13 is wrong by an order of magnitude, while the reason behind
it is right and then some: all 145 are lowercase paths such as
../platform/vplcs/overview, image filenames such as vplc-stopped.png, or
quoted literals. Those are URLs. BR03 governs the word the reader reads, not
the slug, and the section was deliberately published at platform/vplcs/ in
lowercase. A gate that reports them is a gate somebody learns to ignore.

exploration/ is skipped for the same reason the census codes it X-HIST: those
are dated internal planning records, not text a customer reads, so BR03 does
not reach them. Both hits that survive masking are in there and both are
substring false positives rather than prose: "D-VPLC" is the name of a census
decision code, and "Renamed to vplc" in the legend table names a path segment.
Anchoring would not have helped, since a hyphen is a word boundary; scope did.

The accepted set is exactly {vPLC, vPLCs}, which is stricter than BR03's letter.
BR03 names VPLC, Vplc and vPlc as rejected; matching case-insensitively also
catches a bare lowercase "vplc" left in running prose, and once the paths are
masked that is the only place such a match can come from.

Exits 1 when it finds a violation, 0 when clean, and prints the total either
way. Before trusting a clean verdict, run the negative control: write "VPLC" and
"vPlc" into any page as prose, confirm both are named, then take them out. This
script has no way to tell "checked 179 files and found nothing" apart from
"resolved no files at all", so it prints the file count it actually read.
"""
import re
import sys
from pathlib import Path

ACCEPTED = {"vPLC", "vPLCs"}
CANDIDATE = re.compile(r"v\s?plcs?", re.I)
FENCE = re.compile(r"^\s*(```|~~~)")
CODE_SPAN = re.compile(r"`[^`]*`")
LINK_TARGET = re.compile(r"(!?\[[^\]]*\]\()([^)]*)(\))")


def mask(line):
"""Blank out inline code spans and link/image targets, preserving columns.

Link TEXT is deliberately left visible: it is prose the reader reads, and a
"VPLC" typed inside [ ] is a real violation.
"""
line = CODE_SPAN.sub(lambda m: " " * len(m.group(0)), line)
line = LINK_TARGET.sub(
lambda m: m.group(1) + " " * len(m.group(2)) + m.group(3), line
)
return line


def main():
docs = Path(sys.argv[1]).resolve()
violations = []
files = 0

for md in sorted(docs.rglob("*.md")):
rel = md.relative_to(docs)
if rel.parts and rel.parts[0] == "exploration":
continue
files += 1
inside = False
for lineno, raw in enumerate(md.read_text(encoding="utf-8").splitlines(), 1):
if FENCE.match(raw):
inside = not inside
continue
if inside:
continue
for m in CANDIDATE.finditer(mask(raw)):
if m.group(0) not in ACCEPTED:
violations.append(
(str(md.relative_to(docs)), lineno, m.start() + 1,
m.group(0), raw.strip()[:100])
)

for f, ln, col, word, ctx in violations:
print(f"{f}:{ln}:{col} {word!r} {ctx}")
print(f"\nfiles read: {files}")
print(f"BR03 VIOLATIONS: {len(violations)}")
return 1 if violations else 0


if __name__ == "__main__":
sys.exit(main())
123 changes: 123 additions & 0 deletions docs/exploration/linkcheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""Link checker for user-docs. Run: python3 linkcheck.py <docs-dir>

Checks:
- every markdown link target that is not external resolves to a file on disk
- every image reference resolves to a file on disk
- every _config.json leaf path resolves to a markdown file

Handles the site-root form /docs/... that the docs use, and the extensionless
page-link form the docs use for markdown pages.

TWO WARNINGS, both of which have already cost somebody a wrong verdict on this
demand. Phase 10 depends on both.

1. THIS SCRIPT ALWAYS EXITS 0, even with broken links. Judge it by the
"TOTAL BROKEN" line, never by its exit code, and never by `&&`. It also
needs the docs directory as argv[1] and raises IndexError without it, so a
silent-looking run may have checked nothing at all. Before trusting a
verdict, run a negative control: append one broken link and one broken
image to any page, confirm the total moves and names them, then remove them.
The pre-existing baseline in this repository is 6.

2. Its sibling, rename-census.py, OVERWRITES exploration/rename-census.csv
when run from the working tree. That CSV is the base-commit record of what
was decided. Generate it from a checkout of the base commit; against the
working tree use --verify, which classifies and writes nothing.

Undoing a control experiment with `git checkout <file>` will also discard any
uncommitted edits to that file. Copy the file aside and copy it back instead.
"""
import json
import re
import sys
from pathlib import Path

LINK = re.compile(r"!?\[[^\]]*\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)")
FENCE = re.compile(r"^\s*(```|~~~)")

EXTERNAL = ("http://", "https://", "mailto:", "tel:", "#")


def strip_fences(text):
"""Yield (lineno, line) for lines outside fenced code blocks."""
inside = False
for n, line in enumerate(text.splitlines(), 1):
if FENCE.match(line):
inside = not inside
continue
if not inside:
yield n, line


def resolve(docs: Path, md: Path, target: str):
"""Return list of candidate paths that would satisfy the target."""
target = target.split("#", 1)[0].split("?", 1)[0]
if not target:
return None
if target.startswith("/docs/"):
base = docs / target[len("/docs/") :]
elif target.startswith("/"):
base = docs / target.lstrip("/")
else:
base = md.parent / target
cands = [base]
if not base.suffix:
cands += [base.with_suffix(".md"), base / "index.md", base / "README.md"]
return cands


def main():
docs = Path(sys.argv[1]).resolve()
broken = []

for md in sorted(docs.rglob("*.md")):
text = md.read_text(encoding="utf-8")
for lineno, line in strip_fences(text):
for m in LINK.finditer(line):
raw = m.group(1)
if raw.startswith(EXTERNAL):
continue
cands = resolve(docs, md, raw)
if cands is None:
continue
if not any(c.exists() for c in cands):
kind = "image" if m.group(0).startswith("!") else "link"
broken.append(
(kind, str(md.relative_to(docs)), lineno, raw)
)

cfg = docs / "_config.json"
if cfg.exists():
conf = json.loads(cfg.read_text(encoding="utf-8"))

def walk(node, prefix):
"""Accumulate path segments; only leaves (no items) are pages."""
if isinstance(node, list):
for v in node:
walk(v, prefix)
return
if not isinstance(node, dict):
return
path = node.get("path")
here = prefix + [path] if path else prefix
if "items" in node:
walk(node["items"], here)
return
if not path:
return
target = "/".join(here)
cands = resolve(docs, cfg, target)
if cands and not any(c.exists() for c in cands):
broken.append(("config", "_config.json", 0, target))

walk(conf.get("navigation", []), [])

for b in broken:
print(f"{b[0]:6} {b[1]}:{b[2]} -> {b[3]}")
print(f"\nTOTAL BROKEN: {len(broken)}")
return 0


if __name__ == "__main__":
sys.exit(main())
25 changes: 25 additions & 0 deletions docs/exploration/rename-census-overrides-final.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
file,line,col,code,rule
platform/devices/managing-devices.md,1,12,D-DOCTITLE,H17
platform/devices/device-detail.md,61,47,D-DOCTITLE,H17
platform/devices/devices-list.md,21,100,D-DOCTITLE,H17
platform/devices/devices-list.md,57,42,D-DOCTITLE,H17
platform/devices/installing-the-agent.md,103,195,D-DOCTITLE,H17
platform/devices/overview.md,43,57,D-DOCTITLE,H17
troubleshooting/device-not-connecting.md,116,49,D-DOCTITLE,H17
troubleshooting/plan-limit-reached.md,29,59,D-DOCTITLE,H17
platform/organizations/org-dashboard.md,26,34,D-LITERAL,H18
platform/devices/devices-list.md,3,151,D-LITERAL,H18
getting-started/dashboard-tour.md,24,108,D-LITERAL,H18
platform/devices/installing-the-agent.md,59,13,D-HOST,H05
platform/devices/installing-the-agent.md,97,28,D-HOST,H05
platform/devices/managing-devices.md,43,12,D-HOST,H05
platform/devices/overview.md,40,28,D-HOST,H05
troubleshooting/device-not-connecting.md,71,40,D-HOST,H05
troubleshooting/device-not-connecting.md,82,8,D-HOST,H05
troubleshooting/vplc-stuck-stopped.md,47,79,D-PLAIN,H02
troubleshooting/vplc-stuck-stopped.md,56,44,D-PLAIN,H02
troubleshooting/vplc-stuck-stopped.md,64,5,D-PLAIN,H02
troubleshooting/vplc-stuck-stopped.md,86,21,D-PLAIN,H02
getting-started/quick-start.md,26,249,O-SE,H11
getting-started/what-is-autonomy-edge.md,18,55,D-PROSE-NEW,H19
getting-started/what-is-autonomy-edge.md,20,25,D-PROSE-NEW,H19
Loading