ADR-015: stop shipping one site's values, and make the rule a gate
The scanner has been reporting the same count for weeks, which is what a rule that only prints becomes. It now FAILS the build, and it looks where the leaks actually were: PowerShell, the installer, the seeds, generated JSON, the frontend - case-insensitively, across plugins, shopdb, scripts, deploy, tools. A line that is deliberate declares itself with an ADR-015-OK marker and a reason, so the claim is visible in review instead of tolerated in silence. What it found, fixed here: - The shadow client wrote one site's ShopDB URL into HKLM whenever the registry disagreed. At the site it was written for that reads as healing drift; anywhere else it overwrites the site's own address on every enforce cycle, and the site cannot win because the cycle repeats. The bay's value now wins, an explicit -BaseUrl seeds it, and with neither there is nothing honest to write, so it says so and skips. - The kiosk dispatcher fell back to one plant's host when HKLM was unset, so a kiosk elsewhere quietly opened a server it has no business reaching. The fallback is now this site's site_base_url, baked in at seed time, and the dispatcher refuses rather than guessing when neither is set. Its legacy shortcut matcher derives the host from that URL instead of naming one. - The OpenAPI generator hardcoded a production hostname into every spec it generated, which then published to a public wiki. The relative mount is the only server it can honestly name; a site passes its own by environment. - Placeholders and examples in the UI and the client help offered real internal subnets and a real production URL. They now use documentation ranges. Both publication gates - the export scrub and the docs publishability test - carry the site patterns, which neither did. One plant's hostname, FQDN and internal networks are out of the documentation and the generated specs. Comments naming the reference site are reworded rather than deleted: the reasoning is worth keeping, the plant name is not what makes it true.
This commit is contained in:
@@ -316,8 +316,8 @@ def list_display_kiosks():
|
||||
ctype = ComputerType.query.filter_by(computertype=display_type_name).first()
|
||||
if not ctype:
|
||||
return success_response([])
|
||||
domain = (Setting.get('display_fqdn_domain', 'device.geaerospace.net')
|
||||
or 'device.geaerospace.net').strip().strip('.')
|
||||
domain = (Setting.get('display_fqdn_domain', 'device.geaerospace.net') # ADR-015-OK: GE Aerospace-wide domain, and only the DEFAULT of a documented setting every site can override.
|
||||
or 'device.geaerospace.net').strip().strip('.') # ADR-015-OK: GE Aerospace-wide domain, and only the DEFAULT of a documented setting every site can override.
|
||||
rows = (db.session.query(Computer).join(Asset)
|
||||
.filter(Computer.computertypeid == ctype.computertypeid,
|
||||
Asset.isactive == True)
|
||||
|
||||
@@ -65,8 +65,8 @@
|
||||
|
||||
.EXAMPLE
|
||||
.\Install-GEEnforce.ps1 -PCType gea-shopfloor-cmm -MachineNumber 0615 `
|
||||
-CmmVersion 2019 -ShareRoot \\server\share\dt\shopfloor -Site "West Jefferson" `
|
||||
-ShopdbUrl https://shopdb.site.geaerospace.net -ShopdbToken shopdb_pat_xxx `
|
||||
-CmmVersion 2019 -ShareRoot \\server\share\dt\shopfloor -Site "Main Plant" `
|
||||
-ShopdbUrl https://shopdb.example.net -ShopdbToken shopdb_pat_xxx `
|
||||
-EngineSource \\server\share\dt\shopfloor\common
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
|
||||
@@ -20,10 +20,30 @@
|
||||
# Runs as SYSTEM under GE-Enforce, from an entry gated to the test bays.
|
||||
# Fail-safe: exits 0 on every path - shadowing must never stop a bay enforcing.
|
||||
|
||||
param(
|
||||
# This site's ShopDB. Optional: a bay that already carries a BaseUrl in the
|
||||
# registry keeps it, so the fleet needs this only on the first cycle or when
|
||||
# the address changes. There is deliberately NO default - see below.
|
||||
[string] $BaseUrl = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
# RE-ENTRANCY GUARD. This entry lives IN the manifest the shadow run enforces,
|
||||
# so without it the thing recurses without bound: engine runs the manifest ->
|
||||
# reaches this entry -> we invoke the runner -> the runner runs the engine
|
||||
# against the SAME manifest -> reaches this entry again. Measured on the test
|
||||
# bays 2026-08-13: 347 nested cycles in 36 minutes, one every five seconds,
|
||||
# until the share unmounted. The task-based predecessor never hit this because
|
||||
# it registered a task instead of invoking the runner.
|
||||
#
|
||||
# An environment variable, because it is inherited by every child process and
|
||||
# therefore covers the nested engine and runner without a file to clean up or a
|
||||
# stale lock to age out. Set for THIS process tree only.
|
||||
if ($env:SHOPDB_SHADOW_ACTIVE -eq '1') { exit 0 }
|
||||
$env:SHOPDB_SHADOW_ACTIVE = '1'
|
||||
|
||||
$InstallDir = 'C:\Program Files\GE\Shopfloor'
|
||||
$BaseUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb'
|
||||
$LegacyTask = 'ShopDB GE-Enforce (shadow)'
|
||||
|
||||
function Write-ShadowLog {
|
||||
@@ -66,12 +86,30 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
# BaseUrl only when it differs, so a hand-set value is not churned.
|
||||
# THE BAY'S OWN VALUE WINS. This used to hold one site's ShopDB URL as a
|
||||
# compiled-in constant and write it whenever the registry disagreed. At the
|
||||
# site it was written for that reads as "heal drift"; anywhere else it reads
|
||||
# as "overwrite this site's address on every enforce cycle", and the site
|
||||
# cannot win because the cycle repeats. A second plant could not point its
|
||||
# own bays at its own server.
|
||||
#
|
||||
# So: an explicitly passed -BaseUrl seeds the value, an existing registry
|
||||
# value is never touched, and with neither there is nothing honest to write.
|
||||
$regPath = 'HKLM:\SOFTWARE\GE\ShopDB'
|
||||
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
|
||||
if ((Get-ItemProperty -Path $regPath -Name BaseUrl -ErrorAction SilentlyContinue).BaseUrl -ne $BaseUrl) {
|
||||
$current = (Get-ItemProperty -Path $regPath -Name BaseUrl -ErrorAction SilentlyContinue).BaseUrl
|
||||
if (-not $current) {
|
||||
if (-not $BaseUrl) {
|
||||
Write-ShadowLog "no BaseUrl in $regPath and none passed - set it, or pass -BaseUrl in the manifest entry. Skipping this cycle."
|
||||
exit 0
|
||||
}
|
||||
Set-ItemProperty -Path $regPath -Name BaseUrl -Value $BaseUrl
|
||||
Write-ShadowLog "BaseUrl set to $BaseUrl"
|
||||
Write-ShadowLog "BaseUrl seeded as $BaseUrl"
|
||||
} elseif ($BaseUrl -and $BaseUrl -ne $current) {
|
||||
# Say it, do not do it. A changed address is a real event, and it should
|
||||
# be a deliberate one, not a side effect of whatever the manifest last
|
||||
# carried.
|
||||
Write-ShadowLog "BaseUrl in registry is $current; -BaseUrl passed $BaseUrl. Keeping the registry value."
|
||||
}
|
||||
|
||||
Write-ShadowLog "shadowing $scope against $shareManifest"
|
||||
@@ -83,3 +121,8 @@ catch {
|
||||
Write-ShadowLog "FAILED: $_"
|
||||
exit 0
|
||||
}
|
||||
finally {
|
||||
# Clear it so the NEXT enforce cycle shadows again. Without this the guard
|
||||
# would latch for the life of the process tree and shadow would run once.
|
||||
$env:SHOPDB_SHADOW_ACTIVE = $null
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
a PC is never left unmanaged because the web app is down.
|
||||
|
||||
Config (params override registry): HKLM:\SOFTWARE\GE\ShopDB
|
||||
BaseUrl e.g. https://shopdb.example.geaerospace.net
|
||||
BaseUrl e.g. https://shopdb.example.net
|
||||
ApiToken a geenforce.fetch (+ geenforce.report) managed service token,
|
||||
provisioned the same way as SFLD creds (Azure DSC).
|
||||
#>
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
class="cidr-input"
|
||||
rows="5"
|
||||
spellcheck="false"
|
||||
placeholder="10.134.48.0/23 10.48.249.0/26"
|
||||
placeholder="192.0.2.0/24 198.51.100.0/26"
|
||||
></textarea>
|
||||
<small class="input-hint">
|
||||
One CIDR (or a plain IP) per line, or comma-separated. Example:
|
||||
<code>10.134.48.0/23, 10.48.249.0/26</code>. A caller from any of these
|
||||
<code>192.0.2.0/24, 198.51.100.0/26</code>. A caller from any of these
|
||||
networks may pull manifests + payloads and post reports with no token.
|
||||
</small>
|
||||
|
||||
|
||||
@@ -84,6 +84,20 @@ def _relaunch_window_targets_comment():
|
||||
return pairs
|
||||
|
||||
|
||||
def _site_base_url():
|
||||
"""This site's public base URL from settings, or '' when it is unset.
|
||||
|
||||
Wrapped because the script builders are also called with no application
|
||||
context (the seed tests build the text and check it compiles), and a
|
||||
settings lookup there raises rather than returning a default.
|
||||
"""
|
||||
try:
|
||||
from shopdb.api import Setting
|
||||
return (Setting.get('site_base_url', '') or '').strip().rstrip('/')
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
|
||||
def build_dispatcher_script():
|
||||
"""Return the inline dispatcher PowerShell as text.
|
||||
|
||||
@@ -96,6 +110,11 @@ def build_dispatcher_script():
|
||||
table_lines.append(f" '{display_type}' = '{route}'")
|
||||
table_body = ';\n'.join(table_lines)
|
||||
|
||||
# Baked in at seed time because the emitted script runs on a kiosk with no
|
||||
# route back to the database. Blank when the site has not set it, and the
|
||||
# dispatcher refuses rather than guessing - see the comment on $KioskBaseUrl.
|
||||
kiosk_base_url = _site_base_url()
|
||||
|
||||
return f"""# Invoke-DisplayKioskDispatch.ps1 -- gea-shopfloor-display dispatcher.
|
||||
#
|
||||
# Reads C:\\Enrollment\\display-type.txt and ensures an all-users Startup
|
||||
@@ -122,12 +141,19 @@ $DisplayTypeTargets = @{{
|
||||
|
||||
# Base URL the kiosk browser opens; the route from the table is appended. Read
|
||||
# from HKLM (the value the GE-Enforce client is configured with) so it stays
|
||||
# site-agnostic; falls back to the West Jefferson shopdb host.
|
||||
$KioskBaseUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb'
|
||||
# site-agnostic. The fallback is THIS site's site_base_url setting, baked in
|
||||
# when the scope was seeded - it used to be one particular plant's host, which
|
||||
# meant a kiosk anywhere else with no registry value quietly opened a server it
|
||||
# has no business reaching.
|
||||
$KioskBaseUrl = '{kiosk_base_url}'
|
||||
try {{
|
||||
$shopdbConfig = Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\GE\\ShopDB' -Name BaseUrl -ErrorAction Stop
|
||||
if ($shopdbConfig.BaseUrl) {{ $KioskBaseUrl = ([string]$shopdbConfig.BaseUrl).TrimEnd('/') }}
|
||||
}} catch {{}}
|
||||
if (-not $KioskBaseUrl) {{
|
||||
Write-Host 'no ShopDB base URL: set HKLM:\\SOFTWARE\\GE\\ShopDB BaseUrl, or set site_base_url in Settings and re-publish the display scope.'
|
||||
exit 0
|
||||
}}
|
||||
|
||||
# PREFERRED: resolve this display's role from the server by its FQDN, so a change
|
||||
# in Settings > Dashboard Defaults takes effect with no reimage / local edit. The
|
||||
@@ -136,7 +162,7 @@ try {{
|
||||
$path = $null
|
||||
$serial = ''
|
||||
try {{ $serial = ([string](Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SerialNumber).Trim() }} catch {{}}
|
||||
$fqdnDomain = 'device.geaerospace.net'
|
||||
$fqdnDomain = 'device.geaerospace.net' # ADR-015-OK: GE Aerospace-wide domain, and only the DEFAULT of a documented setting every site can override.
|
||||
try {{
|
||||
$ddom = Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\GE\\ShopDB' -Name DisplayFqdnDomain -ErrorAction Stop
|
||||
if ($ddom.DisplayFqdnDomain) {{ $fqdnDomain = ([string]$ddom.DisplayFqdnDomain).Trim().Trim('.') }}
|
||||
@@ -200,8 +226,15 @@ Get-ChildItem -LiteralPath $startup -Filter '*.lnk' -ErrorAction SilentlyContinu
|
||||
# -kiosk matches both '-kiosk' and '--kiosk'. Also catch any browser
|
||||
# shortcut pointing at a shopdb kiosk URL (incl. the dead
|
||||
# shopfloor-dashboard route + --app variants).
|
||||
# The host comes from $KioskBaseUrl, not from a literal: this used
|
||||
# to name one site's server, so at any other site a legacy shortcut
|
||||
# pointing at that site's own ShopDB was not recognised and never
|
||||
# cleaned up.
|
||||
$kioskHost = ''
|
||||
try {{ $kioskHost = ([uri]$KioskBaseUrl).Host }} catch {{}}
|
||||
$kioskArgs = ($existing.Arguments -match '(?i)-kiosk') -or `
|
||||
($existing.Arguments -match '(?i)tsgwp00525|/shopdb/|shopfloor-dashboard')
|
||||
($kioskHost -and $existing.Arguments -like "*$kioskHost*") -or `
|
||||
($existing.Arguments -match '(?i)/shopdb/|shopfloor-dashboard')
|
||||
if ($isBrowser -and $kioskArgs) {{ $drop = $true }}
|
||||
}} catch {{}}
|
||||
}}
|
||||
|
||||
@@ -97,7 +97,7 @@ import { withBase } from '@/utils/basePath'
|
||||
import TouchKeypad from '@/components/TouchKeypad.vue'
|
||||
|
||||
// Leading text on the physical labels, shown before the number box so operators
|
||||
// type only the digits. It was hardcoded to 'WJ', which is West Jefferson's tag
|
||||
// type only the digits. It was hardcoded to 'WJ', which is one site's tag
|
||||
// format and nobody else's - on another site's kiosk it told operators to expect
|
||||
// letters that are not on their labels. Display only: the lookup sends whatever
|
||||
// was typed, unprefixed.
|
||||
|
||||
@@ -136,8 +136,8 @@ class PrintedpartsPlugin(BasePlugin):
|
||||
'description': 'Leading text on the physical gage-lab labels, '
|
||||
'shown at the kiosk before the number box so '
|
||||
'operators type only the digits. Empty shows no '
|
||||
'prefix. Site-specific: West Jefferson labels '
|
||||
'read WJ',
|
||||
'prefix. Site-specific: set it to whatever this '
|
||||
"site's labels are printed with, e.g. WJ",
|
||||
},
|
||||
{
|
||||
'key': 'printedparts_default_threshold',
|
||||
|
||||
Reference in New Issue
Block a user