6 Commits

Author SHA1 Message Date
Joseph Doherty 3797af7f0f chore: mark rename plan tasks 0-4 complete 2026-05-31 21:59:57 -04:00
Joseph Doherty a47317d010 docs: record git-ignored deploy/ scrub gap + resolution (incl. LDAP directory rename) 2026-05-31 21:58:49 -04:00
Joseph Doherty c899cb162c refactor: scrub residual ScadaLink refs → ScadaBridge (env vars, config keys, assembly name, SQL login)
Renames the 13 SCADALINK_* runtime env vars → SCADABRIDGE_*, the ScadaLink__
.NET config keys → ScadaBridge__, the stale ScadaLink.Host.exe assembly name
→ ZB.MOM.WW.ScadaBridge.Host.exe, the scadalink_app SQL login → scadabridge_app,
and residual identifiers/comments/docs. Migration records (prior rename
tooling/design, DB-rename helper, this scrub script) carved out.

Adds tools/scrub-scadalink-refs.sh.
2026-05-31 21:50:38 -04:00
Joseph Doherty d69031dd08 docs: reorganize CLAUDE.md project-structure section 2026-05-31 21:41:29 -04:00
Joseph Doherty d317c07ea5 docs: add folder/repo rename implementation plan + task file 2026-05-31 21:39:23 -04:00
Joseph Doherty e01f3bdabe docs: add folder/repo rename design doc (scadalink-design → ScadaBridge) 2026-05-31 21:32:07 -04:00
39 changed files with 669 additions and 125 deletions
+15 -1
View File
@@ -6,17 +6,31 @@ When a change is requested, the default assumption is: update the design doc *an
## Project Structure
### Top-level directories
- `src/` — C#/.NET implementation, one project per component (e.g. `ZB.MOM.WW.ScadaBridge.AuditLog`, `ZB.MOM.WW.ScadaBridge.NotificationOutbox`, `ZB.MOM.WW.ScadaBridge.SiteCallAudit`, `ZB.MOM.WW.ScadaBridge.CentralUI`, `ZB.MOM.WW.ScadaBridge.Host`, …). Solution file: `ZB.MOM.WW.ScadaBridge.slnx`.
- `tests/` — Test projects (unit + integration).
- `docs/` — Design documentation: `docs/requirements/` (high-level + per-component specs), `docs/test_infra/` (test infrastructure), `docs/plans/` (design-decision and implementation-plan docs). The spec the code implements.
- `docker/` — 8-node cluster topology (2 central + 3 sites), `deploy.sh`, per-node `appsettings.*.json`. See [`docker/README.md`](docker/README.md) for setup, ports, and management commands. Rebuild + redeploy with `bash docker/deploy.sh`.
- `docker-env2/` — Minimal second cluster topology (2 central + 1 site × 2 nodes), runs concurrently with `docker/` on host ports 91XX. Built specifically for testing the Transport (#24) feature with two real environments. See [`docker-env2/README.md`](docker-env2/README.md). Rebuild + redeploy with `bash docker-env2/deploy.sh`.
- `infra/` — Docker Compose for local test services (LDAP, MS SQL, OPC UA, SMTP, REST API, Traefik).
- `deploy/` — Production/on-host deployment artifacts (e.g. `deploy/wonder-app-vd03/`: `appsettings.Central.json`, `appsettings.Site.json`, `install.ps1`/`uninstall.ps1`, `RUNBOOK.md`).
- `deployments/` — Deployment topology notes (`docker-cluster.md`, `docker-cluster-env2.md`, `README.md`).
- `code-reviews/` — Per-component code-review notes (one folder per component, plus `_template`).
- `tools/` — Repo maintenance/utility scripts (e.g. `rename-to-scadabridge.sh`).
- `AkkaDotNet/` — Akka.NET reference documentation and best-practices notes.
- `deprecated/` — Retired docs/notes kept for reference.
- `logs/` — Local runtime log output.
- `vendor/` — Vendored third-party assets (currently an empty placeholder).
- `.claude/` — Claude Code project config (settings, skills, agents).
### Key documents
- `README.md` — Master index with component table and architecture diagrams.
- `docs/requirements/HighLevelReqs.md` — Complete high-level requirements covering all functional areas.
- `docs/requirements/Component-*.md` — Individual component design documents (one per component) — the spec the code implements.
- `docs/test_infra/test_infra.md` — Master test infrastructure doc (OPC UA, LDAP, MS SQL, SMTP, REST API, Traefik).
- `docs/plans/` — Design decision and implementation-plan documents from refinement sessions.
- `AkkaDotNet/` — Akka.NET reference documentation and best practices notes.
## Sister Projects
+9 -9
View File
@@ -22,7 +22,7 @@ The dominant theme is **graceful-degradation gaps**: several user-supplied input
URLs, malformed `--bindings`/`--overrides` JSON, non-JSON success bodies) are deserialized
or constructed without `try/catch`, so a normal user mistake surfaces as an unhandled
exception with a stack trace instead of a clean error message and exit code 1. A second
theme is **dead configuration**: the `SCADALINK_FORMAT` environment variable and the
theme is **dead configuration**: the `SCADABRIDGE_FORMAT` environment variable and the
`defaultFormat` config-file field are loaded by `CliConfig` but never consulted by any
command, so the documented format-precedence chain does not work. The third theme is
**substantial design-document drift**: `Component-CLI.md` describes a name-keyed,
@@ -126,7 +126,7 @@ _Re-review (2026-05-28, `1eb6e97`):_
## Findings
### CLI-001 — `SCADALINK_FORMAT` env var and config-file format are dead; format precedence broken
### CLI-001 — `SCADABRIDGE_FORMAT` env var and config-file format are dead; format precedence broken
| | |
|--|--|
@@ -137,7 +137,7 @@ _Re-review (2026-05-28, `1eb6e97`):_
**Description**
`CliConfig.Load()` reads `SCADALINK_FORMAT` and the `defaultFormat` config-file field into
`CliConfig.Load()` reads `SCADABRIDGE_FORMAT` and the `defaultFormat` config-file field into
`CliConfig.DefaultFormat`, and `Component-CLI.md` documents a format-precedence chain
(command-line option → env var → config file). However, every command resolves the format
with `var format = result.GetValue(formatOption) ?? "json";` and `formatOption` is created
@@ -145,7 +145,7 @@ in `Program.cs:11` with `DefaultValueFactory = _ => "json"`. `GetValue` therefor
returns a non-null value ("json" when the flag is absent), so the `?? "json"` fallback never
fires and `config.DefaultFormat` is never consulted. The env var and config-file format
settings are dead code: `scadabridge site list` always outputs JSON regardless of
`SCADALINK_FORMAT=table` or a `defaultFormat` entry in `~/.scadabridge/config.json`. The
`SCADABRIDGE_FORMAT=table` or a `defaultFormat` entry in `~/.scadabridge/config.json`. The
documented behaviour silently does not work.
**Recommendation**
@@ -312,14 +312,14 @@ wrong element types, and JSON null).
Credentials are supplied only via `--username` / `--password`. A password on the command
line is visible to any local user via the process list (`ps`, `/proc/<pid>/cmdline`) and is
typically persisted into shell history. Unlike the management URL — which can also come
from `SCADALINK_MANAGEMENT_URL` or the config file — there is no environment-variable
from `SCADABRIDGE_MANAGEMENT_URL` or the config file — there is no environment-variable
fallback, no `--password-stdin`, and no interactive prompt for the password. For a tool
explicitly intended for CI/CD automation this materially increases the chance of credential
leakage.
**Recommendation**
Add a `SCADALINK_PASSWORD` environment variable fallback and/or a `--password-stdin`
Add a `SCADABRIDGE_PASSWORD` environment variable fallback and/or a `--password-stdin`
option (read the password from stdin), and document that `--password` on the command line
is discouraged. Optionally prompt interactively when stdin is a TTY and no password was
supplied.
@@ -327,7 +327,7 @@ supplied.
**Resolution**
Resolved 2026-05-16 (commit pending). Root cause confirmed — credentials had no
non-command-line source. Added `SCADALINK_USERNAME` / `SCADALINK_PASSWORD` environment
non-command-line source. Added `SCADABRIDGE_USERNAME` / `SCADABRIDGE_PASSWORD` environment
fallbacks: `CliConfig.Load` now reads them into new `CliConfig.Username` / `Password`
properties (credentials are sourced from environment variables only, never the config
file, so they are not persisted). `CommandHelpers.ResolveCredential` resolves precedence
@@ -387,7 +387,7 @@ bind-connections`/`assign-area`, `data-connection assign/unassign`, `security ap
enable/disable`) are removed; previously-omitted commands (`instance alarm-override
set/delete/list`, `external-system method` subgroup, `site deploy-artifacts`) are added.
A note now points to `src/ZB.MOM.WW.ScadaBridge.CLI/README.md` as the authoritative reference. The
Configuration section also documents the new `SCADALINK_USERNAME`/`SCADALINK_PASSWORD`
Configuration section also documents the new `SCADABRIDGE_USERNAME`/`SCADABRIDGE_PASSWORD`
env vars (see CLI-006).
### CLI-008 — `--format` value is not validated
@@ -817,7 +817,7 @@ and a caller-supplied success handler. In duplicating it, two contracts that
`2` = authorization failure," so this is a contract regression.
2. **Error-message phrasing drift.** The two duplicated error paths
(`bundle:258-260`, `:264-266`) emit shorter messages that omit the
`SCADALINK_MANAGEMENT_URL` / `SCADALINK_USERNAME` env-var hints the canonical paths
`SCADABRIDGE_MANAGEMENT_URL` / `SCADABRIDGE_USERNAME` env-var hints the canonical paths
give — confusing if the user is trying to debug what's missing.
**Recommendation**
@@ -230,7 +230,7 @@ Resolved 2026-05-16 (commit pending). Root cause confirmed against source: the f
fell back to a literal `User Id=sa;Password=YourPassword;...` connection string when no
configured value was found. Removed the hardcoded fallback entirely. The factory now
resolves the connection string from the Host's appsettings files or, when those are not
present, from the `SCADALINK_DESIGNTIME_CONNECTIONSTRING` environment variable, and
present, from the `SCADABRIDGE_DESIGNTIME_CONNECTIONSTRING` environment variable, and
throws a clear `InvalidOperationException` (naming both the config key and the env var)
when neither yields a value. Also hardened `SetBasePath` to be applied only when the
`ZB.MOM.WW.ScadaBridge.Host` directory exists, so the factory degrades cleanly instead of throwing
+4 -4
View File
@@ -3,7 +3,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-env2-central-a
environment:
SCADALINK_CONFIG: Central
SCADABRIDGE_CONFIG: Central
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: "http://+:5000"
ports:
@@ -20,7 +20,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-env2-central-b
environment:
SCADALINK_CONFIG: Central
SCADABRIDGE_CONFIG: Central
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: "http://+:5000"
ports:
@@ -37,7 +37,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-env2-site-x-a
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9121:8082" # Akka remoting
- "9123:8083" # gRPC streaming
@@ -53,7 +53,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-env2-site-x-b
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9122:8082" # Akka remoting
- "9124:8083" # gRPC streaming
+8 -8
View File
@@ -3,7 +3,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-central-a
environment:
SCADALINK_CONFIG: Central
SCADABRIDGE_CONFIG: Central
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: "http://+:5000"
ports:
@@ -20,7 +20,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-central-b
environment:
SCADALINK_CONFIG: Central
SCADABRIDGE_CONFIG: Central
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: "http://+:5000"
ports:
@@ -37,7 +37,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-site-a-a
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9021:8082" # Akka remoting (host access for debugging)
- "9023:8083" # gRPC streaming
@@ -53,7 +53,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-site-a-b
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9022:8082" # Akka remoting
- "9024:8083" # gRPC streaming
@@ -69,7 +69,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-site-b-a
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9031:8082" # Akka remoting
- "9033:8083" # gRPC streaming
@@ -85,7 +85,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-site-b-b
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9032:8082" # Akka remoting
- "9034:8083" # gRPC streaming
@@ -101,7 +101,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-site-c-a
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9041:8082" # Akka remoting
- "9043:8083" # gRPC streaming
@@ -117,7 +117,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-site-c-b
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9042:8082" # Akka remoting
- "9044:8083" # gRPC streaming
@@ -165,7 +165,7 @@ Five indexes with explicit names:
- `scadabridge_audit_writer`: GRANT INSERT ON AuditLog; GRANT SELECT ON AuditLog. (No UPDATE, no DELETE.)
- `scadabridge_audit_purger`: GRANT ALTER ON SCHEMA::dbo; GRANT SELECT ON AuditLog. (Enables ALTER PARTITION FUNCTION SWITCH and SWITCH PARTITION.)
- `Down()` drops indexes, table, scheme, function, then both roles.
- Create: `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Migrations/AddAuditLogTableMigrationTests.cs` — uses a fixture connecting to the running `infra/mssql` container via the connection string in `infra/mssql/.env` (or skips with `Skip.If` when the env var `SCADALINK_MSSQL_TEST_CONN` is unset, so CI without the container still passes).
- Create: `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Migrations/AddAuditLogTableMigrationTests.cs` — uses a fixture connecting to the running `infra/mssql` container via the connection string in `infra/mssql/.env` (or skips with `Skip.If` when the env var `SCADABRIDGE_MSSQL_TEST_CONN` is unset, so CI without the container still passes).
Integration test assertions:
- `sys.partition_functions` contains `pf_AuditLog_Month`.
@@ -191,10 +191,10 @@ Integration test assertions:
**Notes for the implementer:**
- Use `Microsoft.Data.SqlClient` directly in the test fixture (not EF) to issue raw SQL for grant assertions.
- `Skip.If(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SCADALINK_MSSQL_TEST_CONN")), "MSSQL not available")` — keeps tests CI-safe.
- `Skip.If(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SCADABRIDGE_MSSQL_TEST_CONN")), "MSSQL not available")` — keeps tests CI-safe.
- Test database name: `ScadaBridgeAuditMigrationTest_<guid>` (created per fixture, dropped on dispose).
**Bundle C acceptance:** Migration applied to a fresh test DB on the `infra/mssql` container creates the partition function/scheme/table/indexes/roles. Smoke test confirms UPDATE is denied for the writer role. All migration tests pass when `SCADALINK_MSSQL_TEST_CONN` is set; skip cleanly when unset.
**Bundle C acceptance:** Migration applied to a fresh test DB on the `infra/mssql` container creates the partition function/scheme/table/indexes/roles. Smoke test confirms UPDATE is denied for the writer role. All migration tests pass when `SCADABRIDGE_MSSQL_TEST_CONN` is set; skip cleanly when unset.
---
+4 -4
View File
@@ -414,7 +414,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-env2-central-a
environment:
SCADALINK_CONFIG: Central
SCADABRIDGE_CONFIG: Central
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: "http://+:5000"
ports:
@@ -431,7 +431,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-env2-central-b
environment:
SCADALINK_CONFIG: Central
SCADABRIDGE_CONFIG: Central
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: "http://+:5000"
ports:
@@ -448,7 +448,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-env2-site-x-a
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9121:8082" # Akka remoting
- "9123:8083" # gRPC streaming
@@ -464,7 +464,7 @@ services:
image: scadabridge:latest
container_name: scadabridge-env2-site-x-b
environment:
SCADALINK_CONFIG: Site
SCADABRIDGE_CONFIG: Site
ports:
- "9122:8082" # Akka remoting
- "9124:8083" # gRPC streaming
+1 -1
View File
@@ -81,7 +81,7 @@ Exactly one of `content.json` or `content.enc` is present.
"createdAtUtc": "2026-05-24T12:34:56Z",
"sourceEnvironment": "dev-cluster-a",
"exportedBy": "alice@corp.example",
"scadaLinkVersion": "1.4.2",
"scadaBridgeVersion": "1.4.2",
"contentHash": "sha256:...",
"encryption": {
"algorithm": "AES-256-GCM",
+1 -1
View File
@@ -734,7 +734,7 @@ Tests:
**Step 2-5:** Run-fail → implement → run-pass → commit.
`ManifestBuilder` accepts: `sourceEnvironment, exportedBy, scadaLinkVersion, encryption?, contents[], contentBytes` and returns a `BundleManifest` with `ContentHash = SHA-256(contentBytes)`.
`ManifestBuilder` accepts: `sourceEnvironment, exportedBy, scadaBridgeVersion, encryption?, contents[], contentBytes` and returns a `BundleManifest` with `ContentHash = SHA-256(contentBytes)`.
`ManifestValidator.Validate(BundleManifest manifest, byte[] contentBytes)` returns a `ValidationResult` enum (`Ok | UnsupportedFormatVersion | ContentHashMismatch | MalformedManifest`).
@@ -0,0 +1,123 @@
# Folder + Repo Rename `scadalink-design``ScadaBridge` — Design
**Date:** 2026-05-31
**Status:** Approved, ready for implementation planning
**Scope:** Rename the repo folder `~/Desktop/scadalink-design``~/Desktop/ScadaBridge`, rename the Gitea repository `dohertj2/scadalink-design``dohertj2/ScadaBridge`, and scrub every residual `scadalink`/`ScadaLink` reference inside the repo to its `ScadaBridge` equivalent.
This reverses the earlier decision (in `2026-05-28-scadabridge-rename-design.md`) to leave the folder name diverged from the product name, and completes the residual reference scrub that the earlier effort deliberately left out (the `SCADALINK_*` runtime env vars and the production deploy script).
## Context
The product was already renamed from "ScadaLink" to "ScadaBridge" in the `2026-05-28` effort: the solution (`ZB.MOM.WW.ScadaBridge.slnx`), all `src/`/`tests/` projects, namespaces, MS SQL databases, docker containers/network/image, the CLI config dir (`~/.scadabridge/`), `CLAUDE.md`, and `README.md` already say ScadaBridge. What remained untouched were the runtime environment variables (`SCADALINK_*`), one `.NET` config-key convention (`ScadaLink__*`), a stale assembly-name reference in the production deploy script, a few cosmetic identifiers, and historical doc/code-review mentions.
## Decisions
| # | Decision | Selected option |
|---|----------|-----------------|
| 1 | Folder-name collision (`~/Desktop/ScadaBridge` already exists — a separate, older, non-git project) | Rename the existing folder aside to `~/Desktop/ScadaBridge-old` (non-destructive); nothing deleted |
| 2 | Reference scope | Full scrub — env vars, config keys, assembly name, SQL login, cosmetic identifiers, and historical docs/code-reviews |
| 3 | Env-var cutover style | Hard cutover (no backward-compat fallback); dev stack is redeployed |
| 4 | New env-var/identifier prefix | Short product name `SCADABRIDGE_` / `ScadaBridge__` (no `ZB.MOM.WW`), consistent with existing runtime artifacts (`scadabridge-*` containers, `~/.scadabridge/`) |
| 5 | Gitea server-side rename | User renames via the Gitea web UI; Claude updates the local `origin` remote and verifies |
| 6 | Execution mechanism | Scripted multi-pass `sed` over `git ls-files`, most-specific-first, with a completeness-gate `git grep` |
| 7 | Migration records | Carved out of the scrub so their before→after meaning survives (see §2) |
## Section 1 — Order of operations
1. **Safety check** — confirm the working tree is clean (commit/stash WIP first); record the current branch.
2. **Clear the collision**`mv ~/Desktop/ScadaBridge ~/Desktop/ScadaBridge-old`.
3. **Move the folder**`mv ~/Desktop/scadalink-design ~/Desktop/ScadaBridge`. Plain OS move; git tracks contents, not the repo-root directory name, so history is untouched. `.idea/` and `.claude/` ride along inside the moved folder.
4. **Scrub references** — run the scripted multi-pass `sed` (§2).
5. **Build + test gate**`dotnet build ZB.MOM.WW.ScadaBridge.slnx`, unit tests, completeness `git grep`.
6. **Commit** (§5).
7. **Runtime cutover** — rebuild + redeploy docker so running containers read the new env-var names (§4). No DB changes.
8. **Gitea rename** — user web-UI rename; Claude updates local `origin` + verifies fetch/push.
9. **Write/commit this design doc** (already produced by the brainstorm).
## Section 2 — The scrub
A new `tools/scrub-scadalink-refs.sh` operating on `git ls-files -z | xargs -0 sed -i ''` (skips `.git/` and `bin/obj` build artifacts), applied **most-specific-first** so a broad rule cannot double-replace an earlier result:
```
1. ScadaLink.Host.exe → ZB.MOM.WW.ScadaBridge.Host.exe (stale assembly name, deploy/install.ps1)
2. ScadaLink__ → ScadaBridge__ (.NET hierarchical config keys, deploy/install.ps1)
3. SCADALINK_ → SCADABRIDGE_ (13 runtime env vars)
4. scadaLinkVersion → scadaBridgeVersion (Transport param/local var — cosmetic)
5. scadalink_app → scadabridge_app (SQL login)
6. ScadaLink → ScadaBridge (residual)
7. scadalink → scadabridge (residual)
```
### The 13 `SCADALINK_*` env vars in scope
`SCADALINK_CONFIG`, `SCADALINK_FORMAT`, `SCADALINK_USERNAME`, `SCADALINK_PASSWORD`, `SCADALINK_MANAGEMENT_URL`, `SCADALINK_CONFIGURATIONDB_CONNECTION_STRING`, `SCADALINK_DESIGNTIME_CONNECTIONSTRING`, `SCADALINK_MSSQL_TEST_CONN`, `SCADALINK_PLAYWRIGHT_DB`, `SCADALINK_JWT_SIGNING_KEY`, `SCADALINK_LDAP_SERVICE_ACCOUNT_PASSWORD`, `SCADALINK_AUDIT_FILTER_4KB_P95_US`, `SCADALINK_AUDIT_FILTER_RAW_P95_US`.
They are read by code (`Environment.GetEnvironmentVariable(...)` in Host/CLI/tests) **and** set by `docker/docker-compose.yml`, `docker-env2/docker-compose.yml`, `appsettings.Central.json` (`${...}` placeholders), and `deploy/wonder-app-vd03/install.ps1`. All sites change together in one pass.
### Carved out (excluded — migration records)
Scrubbing these would destroy their before→after meaning:
- `tools/rename-to-scadabridge.sh` — prior rename tooling.
- `tools/scrub-scadalink-refs.sh` — this scrub script (its substitution rules are the mapping).
- `docker/rename-databases.sh` — DB rename helper (`ALTER DATABASE ScadaLinkConfig MODIFY NAME = ScadaBridgeConfig`, `ALTER LOGIN [scadalink_app] ...`).
- `docs/plans/2026-05-28-scadabridge-rename-design.md` — prior rename design.
- `docs/plans/2026-05-31-folder-repo-rename-scadabridge-design.md`**this document** (also a before→after record).
- `docs/plans/2026-05-31-folder-repo-rename-scadabridge-plan.md` — the implementation plan (its substitution table and embedded script are the mapping).
### Completeness gate
Must return only the six carve-outs:
```bash
git grep -niE "scadalink" -- . \
':!tools/rename-to-scadabridge.sh' \
':!tools/scrub-scadalink-refs.sh' \
':!docker/rename-databases.sh' \
':!docs/plans/2026-05-28-scadabridge-rename-design.md' \
':!docs/plans/2026-05-31-folder-repo-rename-scadabridge-design.md' \
':!docs/plans/2026-05-31-folder-repo-rename-scadabridge-plan.md'
```
## Section 3 — Collision folder
The existing `~/Desktop/ScadaBridge` (non-git, older project containing `akka.md`, `netstd.md`, `CommentChecker`, its own `src/`/`tests/`/`tsdb/`) is renamed to `~/Desktop/ScadaBridge-old`. Nothing is deleted. If the user later confirms it is dead, they delete it themselves.
## Section 3a — Git-ignored `deploy/` tree (added during implementation)
`/deploy/` is git-ignored, so the `git grep`-based scrub script (and its completeness gate) structurally could not reach it — a gap surfaced by code review. The `deploy/wonder-app-vd03/` production artifacts (`install.ps1`, `appsettings.Central.json`, `appsettings.Site.json`, `RUNBOOK.md`, `ldap/glauth.cfg`) still held old references that would break against the renamed binary: `SCADALINK_*` env vars, `ScadaLink__*` config keys, the `"ScadaLink"` config-section root, `ScadaLink.Host.exe` (now `ZB.MOM.WW.ScadaBridge.Host.exe`), `scadalink.exe` (CLI `AssemblyName` is `scadabridge`), and `akka.tcp://scadalink@…` seed URIs (the actor system name in code is `scadabridge`).
Per user decision, this tree was scrubbed by hand with the **same** substitution set, **including** the internal LDAP directory domain (`dc=scadalink,dc=local``dc=scadabridge,dc=local` and `@scadalink.local``@scadabridge.local`) applied in lockstep across both `appsettings` and `glauth.cfg` so LDAP login stays consistent. Because `deploy/` is git-ignored, these edits are **local-only / not committed** — they fix the on-disk artifacts for the next `wonder-app-vd03` deploy. Verified clean with `grep -rniI scadalink deploy/`.
## Section 4 — Runtime cutover
Hard cutover. After the scrub, rebuild the image and `docker compose up -d --force-recreate` on both clusters (`docker/`, `docker-env2/`) so the running containers read `SCADABRIDGE_*`. **No database rename or wipe** — database names were already migrated in the `2026-05-28` effort and are unchanged here. If no stack is currently running, the cutover applies on the next `bash docker/deploy.sh`.
## Section 5 — Commits
Two staged commits on the current branch, `git diff`-reviewed between them:
1. `refactor: scrub residual ScadaLink refs → ScadaBridge (env vars, config keys, assembly name, SQL login)` — the scrub + any build/test fixes.
2. `docs: add folder/repo rename design doc` — this document.
The folder `mv` and the Gitea rename are not git commits.
## Section 6 — Risks & rollback
- **Missed env var** → silent config-read failure. Mitigated by the completeness-gate grep + build/test + redeploy smoke check.
- **Folder move breaks editor/Claude session paths** — the concern that drove the `2026-05-28` "don't rename the folder" decision; accepted by the user this time.
- **Gitea redirect** — Gitea auto-redirects the old repo URL after rename, so pushes do not hard-fail even before the remote is updated; `origin` is updated regardless.
- **Rollback** — pre-scrub state is the last git commit (`git reset --hard`); folder moves reverse with `mv`.
## Section 7 — Verification
- `dotnet build ZB.MOM.WW.ScadaBridge.slnx` clean; unit tests green.
- Completeness grep returns only the four carve-outs.
- `git remote -v` shows `https://gitea.dohertylan.com/dohertj2/ScadaBridge.git`; `git ls-remote origin` / `git fetch` succeeds.
- (If a stack is running) CLI smoke against the redeployed cluster.
## Out of scope
- Renaming the MS SQL databases, docker containers/network, or CLI config dir — already done in the `2026-05-28` effort.
- Sister repos `~/Desktop/MxAccessGateway`, `~/Desktop/OtOpcUa`.
- Deleting `~/Desktop/ScadaBridge-old` — left to the user.
@@ -0,0 +1,349 @@
# Folder + Repo Rename `scadalink-design``ScadaBridge` Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.
**Goal:** Rename the repo folder `~/Desktop/scadalink-design``~/Desktop/ScadaBridge`, rename the Gitea repository `dohertj2/scadalink-design``dohertj2/ScadaBridge`, and scrub every residual `scadalink`/`ScadaLink` reference inside the repo to its `ScadaBridge` equivalent.
**Architecture:** A scripted, most-specific-first `sed` pass over git-tracked text files does the content scrub; the folder rename is a plain OS `mv` (git tracks contents, not the repo-root directory name); the Gitea rename is a user web-UI action followed by a local `origin` URL update. Migration records (the prior rename tooling/design and the DB-rename helper) are carved out so their before→after meaning survives. Hard env-var cutover, no DB changes.
**Tech Stack:** bash 3.2 (macOS), BSD `sed -i ''`, git, `git grep`, dotnet (`ZB.MOM.WW.ScadaBridge.slnx`), docker compose.
**Design doc:** `docs/plans/2026-05-31-folder-repo-rename-scadabridge-design.md`
> **CRITICAL — path change after Task 0:** Task 0 moves the repo. From Task 1 onward, the repo root is `/Users/dohertj2/Desktop/ScadaBridge` (NOT `scadalink-design`). Every command below `cd`s there.
---
### Task 0: Safety checkpoint, clear the collision, move the folder
**Classification:** standard
**Estimated implement time:** ~3 min
**Parallelizable with:** none
**Files:** (filesystem operations only — no file edits)
- Move: `~/Desktop/ScadaBridge``~/Desktop/ScadaBridge-old`
- Move: `~/Desktop/scadalink-design``~/Desktop/ScadaBridge`
**Step 1: Confirm the working tree is clean and record the branch**
Run:
```bash
cd /Users/dohertj2/Desktop/scadalink-design && git status --short && git branch --show-current
```
Expected: no output from `git status --short` (clean tree) and a branch name (e.g. `main`). If there is uncommitted work, STOP and ask the user whether to commit or stash before proceeding.
**Step 2: Verify both move endpoints are safe**
Run:
```bash
ls -ld /Users/dohertj2/Desktop/ScadaBridge /Users/dohertj2/Desktop/ScadaBridge-old /Users/dohertj2/Desktop/scadalink-design 2>&1
```
Expected: `ScadaBridge` exists (the old non-git project), `scadalink-design` exists, and `ScadaBridge-old` does **not** exist (`No such file or directory`). If `ScadaBridge-old` already exists, STOP and ask the user for a different aside-name.
**Step 3: Move the existing ScadaBridge aside, then move the repo into place**
Run:
```bash
mv /Users/dohertj2/Desktop/ScadaBridge /Users/dohertj2/Desktop/ScadaBridge-old
mv /Users/dohertj2/Desktop/scadalink-design /Users/dohertj2/Desktop/ScadaBridge
```
Expected: no output (success).
**Step 4: Verify the move and that git still works in the new location**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git status --short && git log --oneline -1
```
Expected: clean tree; the HEAD commit is the design-doc commit (`docs: add folder/repo rename design doc ...`). Confirms git history survived the folder move.
**Step 5: No commit** — filesystem moves are not git changes. Proceed to Task 1.
**Acceptance:** `~/Desktop/ScadaBridge` is the git repo (clean tree, intact history); `~/Desktop/ScadaBridge-old` holds the former non-git project; `~/Desktop/scadalink-design` no longer exists.
---
### Task 1: Create the scrub script
**Classification:** small
**Estimated implement time:** ~3 min
**Parallelizable with:** none
**Files:**
- Create: `/Users/dohertj2/Desktop/ScadaBridge/tools/scrub-scadalink-refs.sh`
**Step 1: Write the script**
Create `tools/scrub-scadalink-refs.sh` with exactly this content:
```bash
#!/usr/bin/env bash
# One-time scrub of residual ScadaLink/scadalink references → ScadaBridge.
# Operates on git-tracked TEXT files only (git grep -I skips binaries),
# minus carve-out migration records whose before→after meaning must survive.
# Substitutions are applied most-specific-first so a broad rule cannot
# double-replace an earlier result. Idempotent: re-running is a no-op.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
# Carve-outs (migration records): prior rename tooling/design, the DB-rename
# helper, this script itself, and the two rename design docs.
EXCLUDES_RE='^(tools/rename-to-scadabridge\.sh|tools/scrub-scadalink-refs\.sh|docker/rename-databases\.sh|docs/plans/2026-05-28-scadabridge-rename-design\.md|docs/plans/2026-05-31-folder-repo-rename-scadabridge-design\.md)$'
files=()
while IFS= read -r f; do
[[ "$f" =~ $EXCLUDES_RE ]] && continue
files+=("$f")
done < <(git grep -liI 'scadalink' -- .)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No files to scrub."
exit 0
fi
printf 'Scrubbing %d file(s):\n' "${#files[@]}"
printf ' %s\n' "${files[@]}"
sed -i '' \
-e 's/ScadaLink\.Host\.exe/ZB.MOM.WW.ScadaBridge.Host.exe/g' \
-e 's/ScadaLink__/ScadaBridge__/g' \
-e 's/SCADALINK_/SCADABRIDGE_/g' \
-e 's/scadaLinkVersion/scadaBridgeVersion/g' \
-e 's/scadalink_app/scadabridge_app/g' \
-e 's/ScadaLink/ScadaBridge/g' \
-e 's/scadalink/scadabridge/g' \
"${files[@]}"
echo "Done."
```
**Step 2: Make it executable**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && chmod +x tools/scrub-scadalink-refs.sh
```
Expected: no output.
**Step 3: Dry-run preview (list candidate files WITHOUT editing)**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git grep -liI 'scadalink' -- . | grep -Ev '^(tools/rename-to-scadabridge\.sh|tools/scrub-scadalink-refs\.sh|docker/rename-databases\.sh|docs/plans/2026-05-28-scadabridge-rename-design\.md|docs/plans/2026-05-31-folder-repo-rename-scadabridge-design\.md)$'
```
Expected: a list of ~27 tracked source/config/test/doc files (the same set surfaced in the design doc), and NONE of the five carve-outs. No binary files (e.g. `site_events.db`) appear. If a carve-out appears, the script's `EXCLUDES_RE` is wrong — fix before running.
**Step 4: No commit yet** — the script is committed together with the scrub in Task 4.
**Acceptance:** `tools/scrub-scadalink-refs.sh` exists, is executable, and the dry-run lists only the intended files.
---
### Task 2: Run the scrub and pass the completeness gate
**Classification:** high-risk
**Estimated implement time:** ~3 min
**Parallelizable with:** none
**Files:** (the script edits ~27 tracked files in place; see Task 1 dry-run)
**Step 1: Run the scrub**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && bash tools/scrub-scadalink-refs.sh
```
Expected: `Scrubbing N file(s):` followed by the file list and `Done.`
**Step 2: Completeness gate — only the five carve-outs may still match**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git grep -niI 'scadalink' -- . \
':!tools/rename-to-scadabridge.sh' \
':!tools/scrub-scadalink-refs.sh' \
':!docker/rename-databases.sh' \
':!docs/plans/2026-05-28-scadabridge-rename-design.md' \
':!docs/plans/2026-05-31-folder-repo-rename-scadabridge-design.md'
```
Expected: **no output** (zero hits outside the carve-outs). If any line prints, inspect it — it is either a new case variant the substitution list missed (add a rule and re-run) or a file that should have been carved out.
**Step 3: Sanity-check the new env-var names landed**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git grep -nE 'SCADABRIDGE_CONFIG|ScadaBridge__|scadabridge_app|ZB\.MOM\.WW\.ScadaBridge\.Host\.exe' -- src/ deploy/ docker/ docker-env2/ | head
```
Expected: hits showing `SCADABRIDGE_CONFIG` in compose/Host, `ScadaBridge__` and `ZB.MOM.WW.ScadaBridge.Host.exe` in `deploy/wonder-app-vd03/install.ps1`.
**Step 4: Review the diff**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git diff --stat
```
Expected: ~27 files changed, all content-only. Spot-check `git diff docker/docker-compose.yml src/ZB.MOM.WW.ScadaBridge.Host/Program.cs` shows clean `SCADALINK_``SCADABRIDGE_` swaps.
**Step 5: No commit yet** — commit after the build/test gate (Task 4).
**Acceptance:** completeness gate returns zero hits outside the five carve-outs; new names present in code/config; diff is content-only.
---
### Task 3: Build, run unit tests, fix any stragglers
**Classification:** standard
**Estimated implement time:** ~5 min
**Parallelizable with:** none
**Files:** (no planned edits; only stragglers surfaced by the build, if any)
**Step 1: Restore + build the solution**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && dotnet build ZB.MOM.WW.ScadaBridge.slnx 2>&1 | tail -20
```
Expected: `Build succeeded` with 0 errors. The scrub only renamed env-var string literals, a method parameter (`scadaLinkVersion``scadaBridgeVersion`), comments, and docs — no type/namespace changes — so the build should be clean. If an error appears (e.g. a parameter referenced by name elsewhere), fix that single reference and rebuild.
**Step 2: Run the CLI + Transport + ConfigurationDatabase unit tests (the projects whose code changed)**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && dotnet test tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/ tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/ 2>&1 | tail -25
```
Expected: all tests pass. These projects assert on the env-var name string literals (`SCADABRIDGE_FORMAT`, `SCADABRIDGE_USERNAME`, …) and the `scadaBridgeVersion` parameter — they were scrubbed in lockstep with the code, so they stay consistent. If a test references an env var the code no longer sets (or vice-versa), reconcile the pair.
**Step 3: No commit yet** — commit in Task 4.
**Acceptance:** `dotnet build` clean; the changed projects' unit tests green.
---
### Task 4: Commit the scrub
**Classification:** trivial
**Estimated implement time:** ~1 min
**Parallelizable with:** none
**Files:** (commit only)
**Step 1: Stage and commit**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git add -A && git commit -m "refactor: scrub residual ScadaLink refs → ScadaBridge (env vars, config keys, assembly name, SQL login)
Renames the 13 SCADALINK_* runtime env vars → SCADABRIDGE_*, the ScadaLink__
.NET config keys → ScadaBridge__, the stale ScadaLink.Host.exe assembly name
→ ZB.MOM.WW.ScadaBridge.Host.exe, the scadalink_app SQL login → scadabridge_app,
and residual identifiers/comments/docs. Migration records (prior rename
tooling/design, DB-rename helper, this scrub script) carved out.
Adds tools/scrub-scadalink-refs.sh."
```
Expected: commit succeeds; `~28` files changed (the 27 scrubbed + the new script).
**Step 2: Verify**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git log --oneline -2 && git status --short
```
Expected: the refactor commit on top of the design-doc commit; clean tree.
**Acceptance:** scrub committed; clean working tree.
---
### Task 5: Update the local Git remote after the Gitea web-UI rename
**Classification:** small
**Estimated implement time:** ~2 min
**Parallelizable with:** Task 6
**Files:** (git remote config only)
**Step 1: Manual gate — user renames the repo in the Gitea web UI**
Ask the user to rename the repository at `https://gitea.dohertylan.com/dohertj2/scadalink-design` to `ScadaBridge` via **Settings → Repository Name**, and to confirm when done. Do NOT proceed until confirmed.
**Step 2: Point `origin` at the new URL**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git remote set-url origin https://gitea.dohertylan.com/dohertj2/ScadaBridge.git && git remote -v
```
Expected: both fetch and push lines show `.../dohertj2/ScadaBridge.git`.
**Step 3: Verify the remote is reachable and refs match**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git ls-remote origin HEAD
```
Expected: prints a commit SHA for `HEAD` (auth via macOS keychain succeeds). If it fails with not-found, the web-UI rename has not propagated — re-confirm with the user.
**Step 4: Push the new commits**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && git push origin HEAD
```
Expected: push succeeds; the design-doc + scrub commits land on the renamed remote.
**Acceptance:** `origin` points at `dohertj2/ScadaBridge`; `git ls-remote` and `git push` succeed.
---
### Task 6: Runtime cutover — rebuild + redeploy so containers read the new env vars (conditional)
**Classification:** high-risk
**Estimated implement time:** ~5 min
**Parallelizable with:** Task 5
**Files:** (no edits — runtime only)
**Step 1: Check whether a stack is running**
Run:
```bash
docker ps --format '{{.Names}}' | grep -E 'scadabridge|central|site' || echo "no stack running"
```
Expected: either a list of running containers, or `no stack running`. **If `no stack running`, SKIP this task** — the new env-var names apply automatically on the next `bash docker/deploy.sh`. Note this to the user and mark the task done.
**Step 2: Rebuild the image and recreate the primary cluster**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && bash docker/deploy.sh 2>&1 | tail -30
```
Expected: image rebuilds; containers recreated. (`deploy.sh` already force-recreates.) Containers now receive `SCADABRIDGE_*` from the scrubbed `docker/docker-compose.yml`.
**Step 3: Recreate the second cluster (if it was running)**
Run:
```bash
cd /Users/dohertj2/Desktop/ScadaBridge && bash docker-env2/deploy.sh 2>&1 | tail -30
```
Expected: env2 cluster recreated from the scrubbed `docker-env2/docker-compose.yml`.
**Step 4: Smoke-test readiness + CLI**
Run:
```bash
sleep 20
curl -s http://localhost:9000/health/ready && echo
cd /Users/dohertj2/Desktop/ScadaBridge && dotnet run --project src/ZB.MOM.WW.ScadaBridge.CLI -- --username multi-role --password password template list 2>&1 | tail -15
```
Expected: `/health/ready` reports healthy; the CLI returns a template list (proves the Host read its config via the renamed `SCADABRIDGE_*` env vars). No DB rename/wipe is needed — database names are unchanged by this effort.
**Acceptance:** either the stack is confirmed not-running (skipped), or the redeployed cluster is healthy and the CLI smoke passes against the new env-var names.
---
## Execution Notes
- Tasks are strictly sequential 0 → 1 → 2 → 3 → 4, then 5 and 6 may run in parallel (both depend only on 4).
- The completeness gate in Task 2 and the build/test gate in Task 3 are the safety net for a missed reference.
- Rollback: `git reset --hard HEAD~1` (or `~2`) undoes the scrub commit; `mv` reverses the folder moves; `git remote set-url` reverts the remote.
@@ -0,0 +1,13 @@
{
"planPath": "docs/plans/2026-05-31-folder-repo-rename-scadabridge-plan.md",
"tasks": [
{"id": 0, "subject": "Task 0: Safety checkpoint, clear collision, move folder", "status": "completed"},
{"id": 1, "subject": "Task 1: Create the scrub script", "status": "completed", "blockedBy": [0]},
{"id": 2, "subject": "Task 2: Run scrub + completeness gate", "status": "completed", "blockedBy": [1]},
{"id": 3, "subject": "Task 3: Build, run unit tests, fix stragglers", "status": "completed", "blockedBy": [2]},
{"id": 4, "subject": "Task 4: Commit the scrub", "status": "completed", "blockedBy": [3]},
{"id": 5, "subject": "Task 5: Update local Git remote after Gitea web-UI rename", "status": "pending", "blockedBy": [4]},
{"id": 6, "subject": "Task 6: Runtime cutover redeploy (conditional)", "status": "pending", "blockedBy": [4]}
],
"lastUpdated": "2026-06-01T01:59:34Z"
}
+3 -3
View File
@@ -310,9 +310,9 @@ Configuration is resolved in the following priority order (highest wins):
1. **Command-line options**: `--url`, `--username`, `--password`, `--format`.
2. **Environment variables**:
- `SCADALINK_MANAGEMENT_URL` — Management API URL (e.g., `http://central-host:5000`).
- `SCADALINK_FORMAT` — Default output format (`json` or `table`).
- `SCADALINK_USERNAME` / `SCADALINK_PASSWORD` — LDAP credentials. Preferred over
- `SCADABRIDGE_MANAGEMENT_URL` — Management API URL (e.g., `http://central-host:5000`).
- `SCADABRIDGE_FORMAT` — Default output format (`json` or `table`).
- `SCADABRIDGE_USERNAME` / `SCADABRIDGE_PASSWORD` — LDAP credentials. Preferred over
`--password` on the command line, which is visible in process listings and shell
history. Credentials are never read from the config file.
3. **Configuration file**: `~/.scadabridge/config.json` — Persistent defaults for management URL and output format only (never credentials).
+3 -3
View File
@@ -53,7 +53,7 @@ Exactly one of `content.json` or `content.enc` is present.
"createdAtUtc": "2026-05-24T12:34:56Z",
"sourceEnvironment": "dev-cluster-a",
"exportedBy": "alice@corp.example",
"scadaLinkVersion": "1.4.2",
"scadaBridgeVersion": "1.4.2",
"contentHash": "sha256:...",
"encryption": {
"algorithm": "AES-256-GCM",
@@ -325,7 +325,7 @@ The `manifest.json` file is always present in the ZIP root and is never encrypte
"createdAtUtc": "2026-05-24T12:34:56Z",
"sourceEnvironment": "dev-cluster-a",
"exportedBy": "alice@corp.example",
"scadaLinkVersion": "1.4.2",
"scadaBridgeVersion": "1.4.2",
"contentHash": "sha256:abc123...",
"encryption": {
"algorithm": "AES-256-GCM",
@@ -371,7 +371,7 @@ The `manifest.json` file is always present in the ZIP root and is never encrypte
| `createdAtUtc` | ISO-8601 UTC timestamp of when the export was created. |
| `sourceEnvironment` | The `SourceEnvironment` name of the exporting cluster (from `TransportOptions`). Displayed in the import wizard and required to be retyped at the confirm step. |
| `exportedBy` | Authenticated username of the person who performed the export. |
| `scadaLinkVersion` | Application version of the exporting node. Used for diagnostic display only. |
| `scadaBridgeVersion` | Application version of the exporting node. Used for diagnostic display only. |
| `contentHash` | `sha256:<hex>` — SHA-256 of the raw `content.json` or `content.enc` bytes (pre-encryption). Verified on upload before any decryption. |
| `encryption` | Present only when a passphrase was supplied. Contains the KDF parameters and the per-bundle random salt and IV needed to re-derive the key and decrypt. Omitted for plaintext bundles. |
| `encryption.algorithm` | Always `"AES-256-GCM"` in v1. |
+1 -1
View File
@@ -531,7 +531,7 @@ Sites log operational events locally, including:
- The CLI sends user credentials via HTTP Basic Auth. The server authenticates against **LDAP/AD** and resolves roles before dispatching commands to the ManagementActor.
- CLI commands mirror all Management Service operations: templates, instances, sites, data connections, deployments, external systems, notifications, security (API keys and role mappings), audit log queries, and health status.
- Output is **JSON by default** (machine-readable, suitable for scripting) with an optional `--format table` flag for human-readable tabular output.
- Configuration is resolved from command-line options, **environment variables** (`SCADALINK_MANAGEMENT_URL`, `SCADALINK_FORMAT`), or a **configuration file** (`~/.scadabridge/config.json`).
- Configuration is resolved from command-line options, **environment variables** (`SCADABRIDGE_MANAGEMENT_URL`, `SCADABRIDGE_FORMAT`), or a **configuration file** (`~/.scadabridge/config.json`).
- The CLI is a separate executable from the Host binary — it is deployed on any machine with HTTP access to a central node.
## 14. General Conventions
+6 -6
View File
@@ -13,14 +13,14 @@ public class CliConfig
public string DefaultFormat { get; set; } = "json";
/// <summary>
/// LDAP username from the <c>SCADALINK_USERNAME</c> environment variable, if set.
/// LDAP username from the <c>SCADABRIDGE_USERNAME</c> environment variable, if set.
/// Credentials are intentionally only sourced from environment variables (or the
/// command line) — never from the config file — so they are not persisted to disk.
/// </summary>
public string? Username { get; set; }
/// <summary>
/// LDAP password from the <c>SCADALINK_PASSWORD</c> environment variable, if set.
/// LDAP password from the <c>SCADABRIDGE_PASSWORD</c> environment variable, if set.
/// Provides a safer alternative to <c>--password</c>, which leaks into process
/// listings and shell history.
/// </summary>
@@ -69,20 +69,20 @@ public class CliConfig
}
// Override from environment variables
var envUrl = Environment.GetEnvironmentVariable("SCADALINK_MANAGEMENT_URL");
var envUrl = Environment.GetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL");
if (!string.IsNullOrEmpty(envUrl))
config.ManagementUrl = envUrl;
var envFormat = Environment.GetEnvironmentVariable("SCADALINK_FORMAT");
var envFormat = Environment.GetEnvironmentVariable("SCADABRIDGE_FORMAT");
if (!string.IsNullOrEmpty(envFormat))
config.DefaultFormat = envFormat;
// Credentials from environment variables only (never the config file).
var envUsername = Environment.GetEnvironmentVariable("SCADALINK_USERNAME");
var envUsername = Environment.GetEnvironmentVariable("SCADABRIDGE_USERNAME");
if (!string.IsNullOrEmpty(envUsername))
config.Username = envUsername;
var envPassword = Environment.GetEnvironmentVariable("SCADALINK_PASSWORD");
var envPassword = Environment.GetEnvironmentVariable("SCADABRIDGE_PASSWORD");
if (!string.IsNullOrEmpty(envPassword))
config.Password = envPassword;
@@ -76,7 +76,7 @@ public static class AuditCommandHelpers
if (string.IsNullOrWhiteSpace(url))
{
return AuditConnection.Fail(
"No management URL specified. Use --url, set SCADALINK_MANAGEMENT_URL, or add 'managementUrl' to ~/.scadabridge/config.json.",
"No management URL specified. Use --url, set SCADABRIDGE_MANAGEMENT_URL, or add 'managementUrl' to ~/.scadabridge/config.json.",
"NO_URL");
}
@@ -93,7 +93,7 @@ public static class AuditCommandHelpers
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
return AuditConnection.Fail(
"Credentials required. Use --username/--password or set SCADALINK_USERNAME/SCADALINK_PASSWORD.",
"Credentials required. Use --username/--password or set SCADABRIDGE_USERNAME/SCADABRIDGE_PASSWORD.",
"NO_CREDENTIALS");
}
@@ -51,7 +51,7 @@ internal static class CommandHelpers
if (string.IsNullOrWhiteSpace(url))
{
OutputFormatter.WriteError(
"No management URL specified. Use --url, set SCADALINK_MANAGEMENT_URL, or add 'managementUrl' to ~/.scadabridge/config.json.",
"No management URL specified. Use --url, set SCADABRIDGE_MANAGEMENT_URL, or add 'managementUrl' to ~/.scadabridge/config.json.",
"NO_URL");
return 1;
}
@@ -65,14 +65,14 @@ internal static class CommandHelpers
}
// Resolve credentials: command-line options take precedence, then the
// SCADALINK_USERNAME / SCADALINK_PASSWORD environment variables.
// SCADABRIDGE_USERNAME / SCADABRIDGE_PASSWORD environment variables.
var username = ResolveCredential(result.GetValue(usernameOption), config.Username);
var password = ResolveCredential(result.GetValue(passwordOption), config.Password);
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
OutputFormatter.WriteError(
"Credentials required. Use --username/--password or set SCADALINK_USERNAME/SCADALINK_PASSWORD.",
"Credentials required. Use --username/--password or set SCADABRIDGE_USERNAME/SCADABRIDGE_PASSWORD.",
"NO_CREDENTIALS");
return 1;
}
@@ -57,7 +57,7 @@ public static class DebugCommands
if (string.IsNullOrWhiteSpace(url))
{
OutputFormatter.WriteError(
"No management URL specified. Use --url, set SCADALINK_MANAGEMENT_URL, or add 'managementUrl' to ~/.scadabridge/config.json.",
"No management URL specified. Use --url, set SCADABRIDGE_MANAGEMENT_URL, or add 'managementUrl' to ~/.scadabridge/config.json.",
"NO_URL");
return 1;
}
@@ -76,7 +76,7 @@ public static class DebugCommands
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
OutputFormatter.WriteError(
"Credentials required. Use --username/--password or set SCADALINK_USERNAME/SCADALINK_PASSWORD.",
"Credentials required. Use --username/--password or set SCADABRIDGE_USERNAME/SCADABRIDGE_PASSWORD.",
"NO_CREDENTIALS");
return 1;
}
+5 -5
View File
@@ -15,7 +15,7 @@ The output binary is `scadabridge` (or `scadabridge.exe` on Windows).
Every command requires a connection to a running Central node. The management URL can be supplied three ways, evaluated in this priority order:
1. `--url` flag on the command line
2. `SCADALINK_MANAGEMENT_URL` environment variable
2. `SCADABRIDGE_MANAGEMENT_URL` environment variable
3. `managementUrl` field in `~/.scadabridge/config.json`
```sh
@@ -57,10 +57,10 @@ For the Docker test environment, see `docker/README.md` for a ready-to-use confi
| Variable | Description |
|----------|-------------|
| `SCADALINK_MANAGEMENT_URL` | Management API URL (overrides config file) |
| `SCADALINK_FORMAT` | Default output format (overrides config file) |
| `SCADALINK_USERNAME` | LDAP username (fallback when `--username` is not supplied) |
| `SCADALINK_PASSWORD` | LDAP password (fallback when `--password` is not supplied). Preferred over `--password` on the command line, which leaks into process listings and shell history. |
| `SCADABRIDGE_MANAGEMENT_URL` | Management API URL (overrides config file) |
| `SCADABRIDGE_FORMAT` | Default output format (overrides config file) |
| `SCADABRIDGE_USERNAME` | LDAP username (fallback when `--username` is not supplied) |
| `SCADABRIDGE_PASSWORD` | LDAP password (fallback when `--password` is not supplied). Preferred over `--password` on the command line, which leaks into process listings and shell history. |
## Output
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
/// Factory for creating DbContext instances at design time (used by dotnet ef tooling).
/// Resolves the connection string from the Host's appsettings files, or — for environments
/// where those files are not present — from the
/// <c>SCADALINK_DESIGNTIME_CONNECTIONSTRING</c> environment variable.
/// <c>SCADABRIDGE_DESIGNTIME_CONNECTIONSTRING</c> environment variable.
/// </summary>
/// <remarks>
/// There is deliberately no hardcoded fallback connection string. A credential literal in
@@ -19,7 +19,7 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
/// </remarks>
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ScadaBridgeDbContext>
{
private const string EnvironmentVariableName = "SCADALINK_DESIGNTIME_CONNECTIONSTRING";
private const string EnvironmentVariableName = "SCADABRIDGE_DESIGNTIME_CONNECTIONSTRING";
private const string ConfigurationKey = "ScadaBridge:Database:ConfigurationDb";
/// <summary>
+2 -2
View File
@@ -22,9 +22,9 @@ using ZB.MOM.WW.ScadaBridge.TemplateEngine;
using ZB.MOM.WW.ScadaBridge.Transport;
using Serilog;
// SCADALINK_CONFIG determines which role-specific config to load (Central or Site)
// SCADABRIDGE_CONFIG determines which role-specific config to load (Central or Site)
// DOTNET_ENVIRONMENT/ASPNETCORE_ENVIRONMENT stay as "Development" for dev tooling (static assets, EF migrations, etc.)
var scadabridgeConfig = Environment.GetEnvironmentVariable("SCADALINK_CONFIG")
var scadabridgeConfig = Environment.GetEnvironmentVariable("SCADABRIDGE_CONFIG")
?? Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
?? "Production";
@@ -9,7 +9,7 @@
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development",
"ASPNETCORE_ENVIRONMENT": "Development",
"SCADALINK_CONFIG": "Central"
"SCADABRIDGE_CONFIG": "Central"
}
},
"ScadaBridge Site": {
@@ -19,7 +19,7 @@
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development",
"ASPNETCORE_ENVIRONMENT": "Development",
"SCADALINK_CONFIG": "Site"
"SCADABRIDGE_CONFIG": "Site"
}
}
}
@@ -20,7 +20,7 @@
},
"_secrets": "Host-003: Secrets are NOT committed in this file. Supply them via environment variables, which the Host's configuration builder (AddEnvironmentVariables) overlays over this file. Required: ScadaBridge__Database__ConfigurationDb, ScadaBridge__Security__LdapServiceAccountPassword, ScadaBridge__Security__JwtSigningKey. The ${...} placeholders below are intentionally non-functional and must be overridden per environment.",
"Database": {
"ConfigurationDb": "${SCADALINK_CONFIGURATIONDB_CONNECTION_STRING}"
"ConfigurationDb": "${SCADABRIDGE_CONFIGURATIONDB_CONNECTION_STRING}"
},
"Security": {
"LdapServer": "localhost",
@@ -29,8 +29,8 @@
"AllowInsecureLdap": true,
"LdapSearchBase": "dc=scadabridge,dc=local",
"LdapServiceAccountDn": "cn=admin,dc=scadabridge,dc=local",
"LdapServiceAccountPassword": "${SCADALINK_LDAP_SERVICE_ACCOUNT_PASSWORD}",
"JwtSigningKey": "${SCADALINK_JWT_SIGNING_KEY}",
"LdapServiceAccountPassword": "${SCADABRIDGE_LDAP_SERVICE_ACCOUNT_PASSWORD}",
"JwtSigningKey": "${SCADABRIDGE_JWT_SIGNING_KEY}",
"JwtExpiryMinutes": 15,
"IdleTimeoutMinutes": 30
},
@@ -115,7 +115,7 @@ public sealed class BundleExporter : IBundleExporter
var templateManifest = _manifestBuilder.Build(
sourceEnvironment: sourceEnvironment,
exportedBy: user,
scadaLinkVersion: assemblyVersion,
scadaBridgeVersion: assemblyVersion,
encryption: encryptionSeed,
summary: summary,
contents: resolved.ContentManifest,
@@ -20,7 +20,7 @@ public sealed class ManifestBuilder
/// </summary>
/// <param name="sourceEnvironment">Environment label identifying where the bundle was exported from.</param>
/// <param name="exportedBy">Username of the operator who performed the export.</param>
/// <param name="scadaLinkVersion">ScadaBridge version string stamped in the manifest.</param>
/// <param name="scadaBridgeVersion">ScadaBridge version string stamped in the manifest.</param>
/// <param name="encryption">Encryption metadata when the content is encrypted; null for plain bundles.</param>
/// <param name="summary">High-level summary of artifact counts.</param>
/// <param name="contents">Per-entry content table describing each artifact in the bundle.</param>
@@ -29,7 +29,7 @@ public sealed class ManifestBuilder
public BundleManifest Build(
string sourceEnvironment,
string exportedBy,
string scadaLinkVersion,
string scadaBridgeVersion,
EncryptionMetadata? encryption,
BundleSummary summary,
IReadOnlyList<ManifestContentEntry> contents,
@@ -37,7 +37,7 @@ public sealed class ManifestBuilder
{
ArgumentNullException.ThrowIfNull(sourceEnvironment);
ArgumentNullException.ThrowIfNull(exportedBy);
ArgumentNullException.ThrowIfNull(scadaLinkVersion);
ArgumentNullException.ThrowIfNull(scadaBridgeVersion);
ArgumentNullException.ThrowIfNull(summary);
ArgumentNullException.ThrowIfNull(contents);
ArgumentNullException.ThrowIfNull(contentBytes);
@@ -50,7 +50,7 @@ public sealed class ManifestBuilder
CreatedAtUtc: DateTimeOffset.UtcNow,
SourceEnvironment: sourceEnvironment,
ExportedBy: exportedBy,
ScadaBridgeVersion: scadaLinkVersion,
ScadaBridgeVersion: scadaBridgeVersion,
ContentHash: contentHash,
Encryption: encryption,
Summary: summary,
@@ -8,13 +8,13 @@ public class CliConfigTests
[Fact]
public void Load_DefaultFormat_IsJson()
{
var origUrl = Environment.GetEnvironmentVariable("SCADALINK_MANAGEMENT_URL");
var origFormat = Environment.GetEnvironmentVariable("SCADALINK_FORMAT");
var origUrl = Environment.GetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL");
var origFormat = Environment.GetEnvironmentVariable("SCADABRIDGE_FORMAT");
try
{
Environment.SetEnvironmentVariable("SCADALINK_MANAGEMENT_URL", null);
Environment.SetEnvironmentVariable("SCADALINK_FORMAT", null);
Environment.SetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL", null);
Environment.SetEnvironmentVariable("SCADABRIDGE_FORMAT", null);
var config = CliConfig.Load();
@@ -23,18 +23,18 @@ public class CliConfigTests
}
finally
{
Environment.SetEnvironmentVariable("SCADALINK_MANAGEMENT_URL", origUrl);
Environment.SetEnvironmentVariable("SCADALINK_FORMAT", origFormat);
Environment.SetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL", origUrl);
Environment.SetEnvironmentVariable("SCADABRIDGE_FORMAT", origFormat);
}
}
[Fact]
public void Load_ManagementUrl_FromEnvironment()
{
var orig = Environment.GetEnvironmentVariable("SCADALINK_MANAGEMENT_URL");
var orig = Environment.GetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL");
try
{
Environment.SetEnvironmentVariable("SCADALINK_MANAGEMENT_URL", "http://central:5000");
Environment.SetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL", "http://central:5000");
var config = CliConfig.Load();
@@ -42,17 +42,17 @@ public class CliConfigTests
}
finally
{
Environment.SetEnvironmentVariable("SCADALINK_MANAGEMENT_URL", orig);
Environment.SetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL", orig);
}
}
[Fact]
public void Load_Format_FromEnvironment()
{
var orig = Environment.GetEnvironmentVariable("SCADALINK_FORMAT");
var orig = Environment.GetEnvironmentVariable("SCADABRIDGE_FORMAT");
try
{
Environment.SetEnvironmentVariable("SCADALINK_FORMAT", "table");
Environment.SetEnvironmentVariable("SCADABRIDGE_FORMAT", "table");
var config = CliConfig.Load();
@@ -60,7 +60,7 @@ public class CliConfigTests
}
finally
{
Environment.SetEnvironmentVariable("SCADALINK_FORMAT", orig);
Environment.SetEnvironmentVariable("SCADABRIDGE_FORMAT", orig);
}
}
@@ -81,19 +81,19 @@ public class CliConfigTests
var origHome = Environment.GetEnvironmentVariable("HOME");
var origUserProfile = Environment.GetEnvironmentVariable("USERPROFILE");
var origUrl = Environment.GetEnvironmentVariable("SCADALINK_MANAGEMENT_URL");
var origFormat = Environment.GetEnvironmentVariable("SCADALINK_FORMAT");
var origUser = Environment.GetEnvironmentVariable("SCADALINK_USERNAME");
var origPass = Environment.GetEnvironmentVariable("SCADALINK_PASSWORD");
var origUrl = Environment.GetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL");
var origFormat = Environment.GetEnvironmentVariable("SCADABRIDGE_FORMAT");
var origUser = Environment.GetEnvironmentVariable("SCADABRIDGE_USERNAME");
var origPass = Environment.GetEnvironmentVariable("SCADABRIDGE_PASSWORD");
var origStderr = Console.Error;
try
{
Environment.SetEnvironmentVariable("HOME", tempHome);
Environment.SetEnvironmentVariable("USERPROFILE", tempHome);
Environment.SetEnvironmentVariable("SCADALINK_MANAGEMENT_URL", null);
Environment.SetEnvironmentVariable("SCADALINK_FORMAT", null);
Environment.SetEnvironmentVariable("SCADALINK_USERNAME", null);
Environment.SetEnvironmentVariable("SCADALINK_PASSWORD", null);
Environment.SetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL", null);
Environment.SetEnvironmentVariable("SCADABRIDGE_FORMAT", null);
Environment.SetEnvironmentVariable("SCADABRIDGE_USERNAME", null);
Environment.SetEnvironmentVariable("SCADABRIDGE_PASSWORD", null);
var stderrCapture = new StringWriter();
Console.SetError(stderrCapture);
@@ -112,10 +112,10 @@ public class CliConfigTests
Console.SetError(origStderr);
Environment.SetEnvironmentVariable("HOME", origHome);
Environment.SetEnvironmentVariable("USERPROFILE", origUserProfile);
Environment.SetEnvironmentVariable("SCADALINK_MANAGEMENT_URL", origUrl);
Environment.SetEnvironmentVariable("SCADALINK_FORMAT", origFormat);
Environment.SetEnvironmentVariable("SCADALINK_USERNAME", origUser);
Environment.SetEnvironmentVariable("SCADALINK_PASSWORD", origPass);
Environment.SetEnvironmentVariable("SCADABRIDGE_MANAGEMENT_URL", origUrl);
Environment.SetEnvironmentVariable("SCADABRIDGE_FORMAT", origFormat);
Environment.SetEnvironmentVariable("SCADABRIDGE_USERNAME", origUser);
Environment.SetEnvironmentVariable("SCADABRIDGE_PASSWORD", origPass);
try { Directory.Delete(tempHome, recursive: true); } catch { /* best-effort cleanup */ }
}
}
@@ -5,7 +5,7 @@ namespace ZB.MOM.WW.ScadaBridge.CLI.Tests;
/// <summary>
/// Regression tests for CLI-006 — credentials could only be supplied via the
/// <c>--password</c> command-line option, which leaks into process listings and
/// shell history. A <c>SCADALINK_PASSWORD</c> / <c>SCADALINK_USERNAME</c> environment
/// shell history. A <c>SCADABRIDGE_PASSWORD</c> / <c>SCADABRIDGE_USERNAME</c> environment
/// fallback gives CI/CD a safer alternative.
/// </summary>
[Collection("Environment")]
@@ -14,10 +14,10 @@ public class CredentialResolutionTests
[Fact]
public void Load_Password_FromEnvironment()
{
var orig = Environment.GetEnvironmentVariable("SCADALINK_PASSWORD");
var orig = Environment.GetEnvironmentVariable("SCADABRIDGE_PASSWORD");
try
{
Environment.SetEnvironmentVariable("SCADALINK_PASSWORD", "s3cret");
Environment.SetEnvironmentVariable("SCADABRIDGE_PASSWORD", "s3cret");
var config = CliConfig.Load();
@@ -25,17 +25,17 @@ public class CredentialResolutionTests
}
finally
{
Environment.SetEnvironmentVariable("SCADALINK_PASSWORD", orig);
Environment.SetEnvironmentVariable("SCADABRIDGE_PASSWORD", orig);
}
}
[Fact]
public void Load_Username_FromEnvironment()
{
var orig = Environment.GetEnvironmentVariable("SCADALINK_USERNAME");
var orig = Environment.GetEnvironmentVariable("SCADABRIDGE_USERNAME");
try
{
Environment.SetEnvironmentVariable("SCADALINK_USERNAME", "ci-user");
Environment.SetEnvironmentVariable("SCADABRIDGE_USERNAME", "ci-user");
var config = CliConfig.Load();
@@ -43,19 +43,19 @@ public class CredentialResolutionTests
}
finally
{
Environment.SetEnvironmentVariable("SCADALINK_USERNAME", orig);
Environment.SetEnvironmentVariable("SCADABRIDGE_USERNAME", orig);
}
}
[Fact]
public void Load_NoCredentialEnvVars_LeavesCredentialsNull()
{
var origUser = Environment.GetEnvironmentVariable("SCADALINK_USERNAME");
var origPass = Environment.GetEnvironmentVariable("SCADALINK_PASSWORD");
var origUser = Environment.GetEnvironmentVariable("SCADABRIDGE_USERNAME");
var origPass = Environment.GetEnvironmentVariable("SCADABRIDGE_PASSWORD");
try
{
Environment.SetEnvironmentVariable("SCADALINK_USERNAME", null);
Environment.SetEnvironmentVariable("SCADALINK_PASSWORD", null);
Environment.SetEnvironmentVariable("SCADABRIDGE_USERNAME", null);
Environment.SetEnvironmentVariable("SCADABRIDGE_PASSWORD", null);
var config = CliConfig.Load();
@@ -64,8 +64,8 @@ public class CredentialResolutionTests
}
finally
{
Environment.SetEnvironmentVariable("SCADALINK_USERNAME", origUser);
Environment.SetEnvironmentVariable("SCADALINK_PASSWORD", origPass);
Environment.SetEnvironmentVariable("SCADABRIDGE_USERNAME", origUser);
Environment.SetEnvironmentVariable("SCADABRIDGE_PASSWORD", origPass);
}
}
}
@@ -25,7 +25,7 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Audit;
/// Connection string mirrors the Docker cluster's <c>scadabridge_app</c> account
/// from <c>docker/central-node-a/appsettings.Central.json</c>, with the host
/// pointed at the host-exposed port (<c>localhost:1433</c>). The
/// <c>SCADALINK_PLAYWRIGHT_DB</c> env var lets CI override the connection
/// <c>SCADABRIDGE_PLAYWRIGHT_DB</c> env var lets CI override the connection
/// without recompiling.
/// </para>
/// </summary>
@@ -34,11 +34,11 @@ internal static class AuditDataSeeder
private const string DefaultConnectionString =
"Server=localhost,1433;Database=ScadaBridgeConfig;User Id=scadabridge_app;Password=ScadaBridge_Dev1#;TrustServerCertificate=true;Encrypt=false;Connect Timeout=5";
private const string EnvVar = "SCADALINK_PLAYWRIGHT_DB";
private const string EnvVar = "SCADABRIDGE_PLAYWRIGHT_DB";
/// <summary>
/// Connection string for the running cluster's configuration DB. Resolved
/// from <c>SCADALINK_PLAYWRIGHT_DB</c> when set, otherwise the local docker
/// from <c>SCADABRIDGE_PLAYWRIGHT_DB</c> when set, otherwise the local docker
/// dev defaults.
/// </summary>
public static string ConnectionString
@@ -30,7 +30,7 @@ public class AuditGridColumnTests
/// <summary>Skip reason shared by the DB-seeding tests when MSSQL is down.</summary>
private const string DbUnavailableSkipReason =
"AuditDataSeeder cannot reach MSSQL at localhost:1433 — bring up infra/docker-compose and docker/deploy.sh, " +
"or set SCADALINK_PLAYWRIGHT_DB to a reachable connection string.";
"or set SCADABRIDGE_PLAYWRIGHT_DB to a reachable connection string.";
private readonly PlaywrightFixture _fixture;
@@ -64,7 +64,7 @@ public class AuditLogPageTests
{
throw new InvalidOperationException(
"AuditDataSeeder cannot reach MSSQL at localhost:1433 — bring up infra/docker-compose and docker/deploy.sh, " +
"or set SCADALINK_PLAYWRIGHT_DB to a reachable connection string.");
"or set SCADABRIDGE_PLAYWRIGHT_DB to a reachable connection string.");
}
var runId = Guid.NewGuid().ToString("N");
@@ -28,11 +28,11 @@ internal static class SiteCallDataSeeder
private const string DefaultConnectionString =
"Server=localhost,1433;Database=ScadaBridgeConfig;User Id=scadabridge_app;Password=ScadaBridge_Dev1#;TrustServerCertificate=true;Encrypt=false;Connect Timeout=5";
private const string EnvVar = "SCADALINK_PLAYWRIGHT_DB";
private const string EnvVar = "SCADABRIDGE_PLAYWRIGHT_DB";
/// <summary>
/// Connection string for the running cluster's configuration DB. Resolved
/// from <c>SCADALINK_PLAYWRIGHT_DB</c> when set, otherwise the local docker
/// from <c>SCADABRIDGE_PLAYWRIGHT_DB</c> when set, otherwise the local docker
/// dev defaults.
/// </summary>
public static string ConnectionString
@@ -101,7 +101,7 @@ public class SiteCallsPageTests
/// <summary>Skip reason shared by the DB-seeding tests when MSSQL is down.</summary>
private const string DbUnavailableSkipReason =
"SiteCallDataSeeder cannot reach MSSQL at localhost:1433 — bring up infra/docker-compose and docker/deploy.sh, " +
"or set SCADALINK_PLAYWRIGHT_DB to a reachable connection string.";
"or set SCADABRIDGE_PLAYWRIGHT_DB to a reachable connection string.";
[SkippableFact]
public async Task FilterNarrowing_ChannelFilterShrinksGrid()
@@ -4,7 +4,7 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests;
public class DesignTimeDbContextFactoryTests : IDisposable
{
private const string EnvVar = "SCADALINK_DESIGNTIME_CONNECTIONSTRING";
private const string EnvVar = "SCADABRIDGE_DESIGNTIME_CONNECTIONSTRING";
private readonly string? _originalEnv;
public DesignTimeDbContextFactoryTests()
@@ -33,7 +33,7 @@ public sealed class MsSqlMigrationFixture : IDisposable
private const string DefaultAdminConnectionString =
"Server=localhost,1433;User Id=sa;Password=ScadaBridge_Dev1#;TrustServerCertificate=true;Encrypt=false;Connect Timeout=3";
private const string AdminEnvVar = "SCADALINK_MSSQL_TEST_CONN";
private const string AdminEnvVar = "SCADABRIDGE_MSSQL_TEST_CONN";
public string DatabaseName { get; }
@@ -110,10 +110,10 @@ public class HotPathLatencyTests
var p95Us = MeasureP95Microseconds(MeasureIterations, () => _ = filter.Apply(evt));
// Default budget 50 µs (spec target). Override via env for slow CI:
// SCADALINK_AUDIT_FILTER_4KB_P95_US — interpret as the regression
// SCADABRIDGE_AUDIT_FILTER_4KB_P95_US — interpret as the regression
// guard threshold. Print the observed value so a missed budget gives
// useful telemetry on the test output.
var threshold = GetThresholdMicroseconds("SCADALINK_AUDIT_FILTER_4KB_P95_US", 50d);
var threshold = GetThresholdMicroseconds("SCADABRIDGE_AUDIT_FILTER_4KB_P95_US", 50d);
Assert.True(p95Us < threshold,
$"4KB body filter p95 = {p95Us:F1} µs; threshold = {threshold:F1} µs");
}
@@ -137,7 +137,7 @@ public class HotPathLatencyTests
var p95Us = MeasureP95Microseconds(MeasureIterations, () => _ = filter.Apply(evt));
var threshold = GetThresholdMicroseconds("SCADALINK_AUDIT_FILTER_RAW_P95_US", 10d);
var threshold = GetThresholdMicroseconds("SCADABRIDGE_AUDIT_FILTER_RAW_P95_US", 10d);
Assert.True(p95Us < threshold,
$"Raw-event filter p95 = {p95Us:F1} µs; threshold = {threshold:F1} µs");
}
@@ -130,7 +130,7 @@ public sealed class BundleImporterLoadTests
var manifest = builder.Build(
sourceEnvironment: "dev",
exportedBy: "alice",
scadaLinkVersion: "1.0.0",
scadaBridgeVersion: "1.0.0",
encryption: null,
summary: new BundleSummary(content.Templates.Count, 0, 0, 0, 0, 0, 0, 0, 0),
contents: Array.Empty<ManifestContentEntry>(),
@@ -152,7 +152,7 @@ public sealed class BundleImporterLoadTests
var manifest = builder.Build(
sourceEnvironment: "dev",
exportedBy: "alice",
scadaLinkVersion: "1.0.0",
scadaBridgeVersion: "1.0.0",
encryption: seed,
summary: new BundleSummary(content.Templates.Count, 0, 0, 0, 0, 0, 0, 0, 0),
contents: Array.Empty<ManifestContentEntry>(),
@@ -471,7 +471,7 @@ public sealed class BundleImporterLoadTests
var manifest = rig.ManifestBuilder.Build(
sourceEnvironment: "dev",
exportedBy: "alice",
scadaLinkVersion: "1.0.0",
scadaBridgeVersion: "1.0.0",
encryption: null,
summary: new BundleSummary(1, 0, 0, 0, 0, 0, 0, 0, 0),
contents: Array.Empty<ManifestContentEntry>(),
@@ -28,7 +28,7 @@ public sealed class BundleSerializerTests
new ManifestBuilder().Build(
sourceEnvironment: "test-env",
exportedBy: "tester",
scadaLinkVersion: "1.0.0",
scadaBridgeVersion: "1.0.0",
encryption: encryption,
summary: new BundleSummary(0, 1, 1, 0, 0, 0, 0, 0, 0),
contents: Array.Empty<ManifestContentEntry>(),
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# One-time scrub of residual ScadaLink/scadalink references → ScadaBridge.
# Operates on git-tracked TEXT files only (git grep -I skips binaries),
# minus carve-out migration records whose before→after meaning must survive.
# Substitutions are applied most-specific-first so a broad rule cannot
# double-replace an earlier result. Idempotent: re-running is a no-op.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
# NOTE: this script only sees git-TRACKED files (via git grep). Git-ignored
# trees — notably /deploy/ (see .gitignore) — are out of its reach and were
# scrubbed separately, by hand, with the same substitution set. A git-based
# completeness gate likewise cannot see them; verify those trees with a plain
# `grep -rniI scadalink <dir>`.
# Carve-outs (migration records): prior rename tooling/design, the DB-rename
# helper, this script itself, and the rename design + plan docs (which document
# the old→new mapping and would be self-corrupted by the substitution).
EXCLUDES_RE='^(tools/rename-to-scadabridge\.sh|tools/scrub-scadalink-refs\.sh|docker/rename-databases\.sh|docs/plans/2026-05-28-scadabridge-rename-design\.md|docs/plans/2026-05-31-folder-repo-rename-scadabridge-design\.md|docs/plans/2026-05-31-folder-repo-rename-scadabridge-plan\.md)$'
files=()
while IFS= read -r f; do
[[ "$f" =~ $EXCLUDES_RE ]] && continue
files+=("$f")
done < <(git grep -liI 'scadalink' -- .)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No files to scrub."
exit 0
fi
printf 'Scrubbing %d file(s):\n' "${#files[@]}"
printf ' %s\n' "${files[@]}"
sed -i '' \
-e 's/ScadaLink\.Host\.exe/ZB.MOM.WW.ScadaBridge.Host.exe/g' \
-e 's/ScadaLink__/ScadaBridge__/g' \
-e 's/SCADALINK_/SCADABRIDGE_/g' \
-e 's/scadaLinkVersion/scadaBridgeVersion/g' \
-e 's/scadalink_app/scadabridge_app/g' \
-e 's/ScadaLink/ScadaBridge/g' \
-e 's/scadalink/scadabridge/g' \
"${files[@]}"
echo "Done."