Deployment and the privilege boundary
Normative where marked. Rationale otherwise.
The engine runs with the least privilege the standard needs, and anything that needs more is kept out of it. This is the most consequential architectural decision in the local stack, so the reasoning is written out rather than assumed.
1. The engine#
SGA Engine — core. Least-privilege. Bundled freely.#
A long-running background service under a least-privilege service account. Never LocalSystem, never a kernel driver, never promiscuous capture. Installing it takes elevation once, to register the service; running it never does. The precise privilege rules are in §5.
It is a service because the bay outlives any one program running on it. A player closes one simulator and opens another; a title crashes and is restarted; somebody logs out at the end of a shift. None of that should cost the room its pairings. Devices stay enrolled, bound and connected across all of it, because the engine was never a child of the simulator in the first place. The alternative — a process tied to a user session or launched by a title — re-discovers and re-pairs every device every time anything restarts, which is exactly the fragility this standard exists to remove.
Does: discovery (mDNS, BLE GATT), capability handshake, connection matrix, routing, local journal, cloud sync, session and bay binding, bundle loading.
This is the whole standard. Everything in specs 00–04 works here. This is what a vendor embeds in their installer, what a facility deploys to two hundred bays, and what runs on a phone.
Equipment that does not speak the standard#
Some simulator software and launch monitors do not speak this standard and may never speak it. Their vendors can be integrated by separate agreement, outside this standard. Data that arrives that way reaches the SGA Engine as an ordinary contributor, marked with the provenance and confidence its method actually earns, never better.
Nothing in such an integration is bundled with the engine or required by it, and nothing of it is required for a certified device: a device that implements this standard has no reason to be observed.
2. Why not one privileged service#
The proposal on the table was a single engine running with system privileges, auto-updating from SGA, bundled into third-party vendor installers, doing network and Bluetooth sniffing. Each of those is defensible alone. Together they compose into something we should not ship.
It is a supply-chain weapon aimed at ourselves. SYSTEM privilege, plus silent auto-update, plus distribution through every partner's installer, is the exact architecture of a software supply-chain attack — with SGA holding the key. One compromised signing key or build server yields root on every certified bay in the country. SGA's entire value proposition is being the trusted neutral layer; this is the one design that converts a key compromise into an industry-wide incident with our name on it.
Vendor security review will reject it. A launch monitor manufacturer's security team will not approve embedding a third party's SYSTEM-level, self-updating, packet-sniffing service into their shipping installer. We would be asking them to accept our breach risk into their product liability. The design that never gets adopted is not the secure design or the insecure one — it is just the one nobody ships.
Endpoint protection will quarantine it. Promiscuous capture plus silent self-update plus SYSTEM is a textbook EDR detection. Support cost on a portable, unattended, thumb-drive deployment would be brutal.
Commercial facility IT will refuse it. The hundred-bay operator is the customer we most need. Their network team does not permit promiscuous mode on production VLANs, and their standard build does not grant SYSTEM to a vendor service. This is the deployment where privilege is least available and least necessary.
And it is not needed. This is the part worth dwelling on.
3. Privilege is only needed for devices that do not speak the standard#
| capability | needs privilege? |
|---|---|
| Find engines and devices on the LAN (mDNS) | no |
| Bind a port for the engine | no, above 1024 |
| BLE central: scan, pair, GATT | no on Windows, macOS, Linux, iOS, Android |
| Talk to a paired device over IP | no |
| Read the vendor's own local socket | no |
| Write the local journal | no |
| Sync to cloud | no |
| Promiscuous packet capture | yes, and a driver install |
| Wi-Fi monitor mode / MAC harvesting | yes, plus platform-specific work |
Everything the standard describes — pair a device, find it on the LAN, negotiate, exchange data — sits in the unprivileged half. The goal of "the hardware vendor just ships a device, no software install" is fully achievable at user privilege on every target OS.
Sniffing is a migration tool, not a runtime feature. You need it to reverse a device that has not implemented SGA-OIS yet. Once it has, there is nothing to sniff — the data arrives declared, signed, and structured. Building the privileged path into the core makes the permanent architecture pay for a transitional need. It has no place in the engine, and the need for it shrinks with every vendor that adopts the standard.
4. Wi-Fi and Bluetooth MAC harvesting for presence#
Worth addressing separately, because it was raised as a way to verify a player is physically at the bay during a tournament — and because it no longer works.
MAC randomization has been on by default since iOS 14 and Android 10 (2020). Phones present a different randomized MAC per network, rotated on a schedule. Harvesting a MAC from the air today yields an identifier that is not stable across sessions and not linkable to a person. As a presence signal it is close to worthless, and it degrades further every OS release.
It also carries real cost. Under GDPR and several US state statutes a device identifier tied to an individual is personal data; passive collection of identifiers from non-consenting bystanders in a commercial venue is a compliance problem we would be creating on purpose, in exchange for a signal that does not work.
Presence that does work, in increasing strength:
- Bay pairing token — the QR placard or NFC tag. Proves the phone was physically at the bay at check-in. Already in our design.
- BLE proximity to a bay beacon, sampled during play with the member's own app in the loop and consenting. Proves continued presence, uses no privileged capture, and involves only the player's own device.
- Shot-cadence correlation — the member's app confirming activity against the shot stream. Free, no new hardware.
- Camera verification for championship-tier events, which we already anticipate. The only one that actually proves a person rather than a phone.
All four are consented, none needs promiscuous capture, and together they are strictly stronger than MAC harvesting was even before randomization.
4a. Runtime and configuration are separate programs#
The engine has two parts with different lifetimes, and separating them is what lets a certified bay be left alone.
The runtime — a service, and headless#
Starts on boot, runs with no one logged in, survives a logout, has no user interface. This is what runs 99% of the time: relaying, journaling, syncing. A correctly configured bay never needs anything else touched.
The configuration application — separate, run on demand#
Shipped by the same installer, launched only when something needs attaching or changing: a camera, a device that does not natively speak to the engine, a bay re-pairing. Configure, save, exit. Changes apply to the running service immediately; nothing restarts and no round is interrupted.
It must cover at least:
| Bay identity | pair with a bay, show the current binding |
| Devices | what is connected, what it declared, operator priority between equivalent sources |
| Cameras | attach by RTSP URL or local device, position and coverage, pre/post-roll (11) |
| Network | transport, discovery mode, explicit endpoints for large facilities |
| Consent and privacy | per-session capture and upload controls |
| Bundle | version in force, where it came from, when it last refreshed |
| Diagnostics | live manifest, unmet wants, clock skew, measured engine latency |
It must lock#
Configuration MUST be protectable by an operator-set password, and a certified bay MUST have it set.
Standing at the machine is not authorization. On a commercial bay the people standing at the machine are customers. A certified configuration is a thing an operator has paid to establish and a curious player can undo in thirty seconds. It needs a lock.
One exception: the privacy opt-out MUST work without the password. A player who does not want their round captured cannot be made to find a manager first. It is time-limited — it applies to the current session and expires with it — so it cannot be used to quietly disable a bay, and every use is journaled.
It also owns the bay's permanent equipment#
Devices bolted to the room — a ceiling camera, an environment sensor, a fixed club rack — are enrolled here rather than paired per session, and the roster is added to, renamed, and removed from in this application. Full rules in 13 §4.
Enrollment belongs behind the password for the reason everything else here does: it binds a device to a bay permanently, and that is an operator's decision, not a passer-by's.
And the bay's certificate#
The certificate is installed, viewed and removed here too (14 §5) — tier, whether it is in force, in grace or lapsed, the expiry, and any shortfalls with which of the two problems each one is: nothing provides the field, or something provides it and cannot be trusted with it. Those call for different actions, and collapsing them to "unmet" throws away the half the operator needed.
How the two programs talk#
Separate programs still have to reach each other, or "changes apply to the running service" is not true. The service publishes a loopback control endpoint and writes its address and a session token to a file in the operator's own profile, readable only by them; the configuration application reads that file and connects.
Loopback is the security model here, not a deployment detail. Authority is standing at the bay machine. An endpoint bound anywhere else would turn bay configuration into remote administration for a facility's whole floor, which nobody asked for and no operator would expect.
A stopped service removes its control file, so a configuration app fails immediately rather than waiting on a port nothing is listening to.
Two constraints on the configuration application#
- Loopback-bound. It listens only on the machine itself, never on the network, so nobody on the facility Wi-Fi can reach it. Combined with the password, that is two independent barriers: you must be at the machine and know the password.
- It cannot alter recorded data. Configuration changes the future, never the journal.
Privilege, precisely#
Running as a service is fine and is better than the alternative. The objection in §2 was never to a service — it was to SYSTEM, plus silent code self-update, plus promiscuous capture, composing into a supply-chain weapon.
So:
- The installer needs elevation once, to register the service. Ordinary.
- The service then runs under a least-privilege service account, not LocalSystem. It needs a socket, a directory and a network connection. Nothing more.
- A least-privilege service cannot do promiscuous capture, which is correct: the engine never needs it (§3), and anything that does capture is separately installed and separately consented (§5).
One real wrinkle worth designing around now. On Windows, services run in session 0 and are isolated from the interactive desktop session, which makes access to session-bound devices — cameras in particular — awkward or impossible. The practical arrangement is a small user-session agent for anything that must touch such a device, with the service doing everything else. Discovering this after building camera capture into the service would be an expensive week.
4b. Bay identity, and claiming a bay#
A bay has to be identifiable. Not trusted — the engine is a router and nothing downstream believes it — but nameable, so that somebody can claim a room, so that notices about failing equipment reach the person who can unplug it (02 §3a.4, 12 §6), and so that the rounds from one room can be told from the rounds from the next.
A note on the word. This is a claim, not a pairing. Pairing is already spoken for in this standard: a device pairs with a bay (03 §3.4c, 13 §4) — a launch monitor presents a bay pairing token, a club sensor is paired at the rack. Using one verb for both made "the bay is paired" a sentence with two readings. Claim is the word this standard already uses for the other act of taking something on: a member claiming a slot (00 §6).
That is a different requirement from authentication, and conflating the two produces a system that is worse at both.
4b.1 The installation identity#
An engine MUST mint, on first run, an installation identity: a random value of at least 128 bits, stored with its journals, and reused for the life of the installation.
- It is an identifier, never a credential. It is self-asserted, it is not secret, and a server MUST NOT accept anything on the strength of a client presenting one.
- An engine MUST NOT use a hardware identifier — a machine GUID, an SMBIOS UUID, a platform UUID — as this value. Each is a strong cross-application fingerprint, and storing one where it authenticates nothing is a privacy cost paid for no benefit. They are also less stable than they appear: every one of them changes on an operating-system reinstall, and several are duplicated across machines imaged from a single master, which is precisely how a multi-bay facility is built.
4b.2 Telling two machines apart#
An installation identity kept in a file travels when the file is copied. Image twelve bay machines from one master and all twelve answer to one name, arriving as a single bay whose rounds interleave.
So an engine MUST record, alongside the identity, a machine fingerprint, and MUST mint a new identity when it starts on a machine whose fingerprint does not match. The fingerprint need not be unique, secret, or hardware-derived — it is asked only to differ between two machines — and it SHOULD be stored hashed, so that the file does not become an inventory of somebody's estate.
An engine SHOULD report that a remint occurred, because it is the explanation for the bay needing to be claimed again.
4b.3 Claiming a bay#
A bay is unclaimed until a party takes responsibility for it. An unclaimed bay is a working bay: it records, it ships, and its rounds are stored. What it does not do is count.
- A platform MUST accept data from an unclaimed bay and MUST NOT discard it.
- Rounds from an unclaimed bay MUST NOT be eligible for rating or for sanctioned competition.
- On being claimed, rounds the bay has already sent MUST become eligible. A facility that plays all weekend and claims the room on Monday does not lose the weekend, and this is the reason the data is kept rather than refused.
This is the same shape as a member claiming a slot (00 §6), and it holds for the same reason: unclaimed data is storage, not evidence. Refusing an unknown bay at the door would trade that for nothing — a secret on a machine the player administers cannot establish that a round is real, and a bay unboxed on a Saturday would lose the day to a configuration step.
4b.3.1 There is more than one kind of owner#
A platform MUST NOT assume the party claiming a bay is a commercial operator. There are at least two kinds, with different needs, and a model that admits only one of them excludes a case that is not unusual:
- A member who owns the simulator in their home. They are the owner, the operator and, most days, the only golfer in the room. Requiring them to register a business so that their own rounds could count would be absurd, and would suppress exactly the players an association most wants recording.
- A commercial operator: a business with bays across one or more locations, staff who need to see their status, and notices that must reach whoever is on site rather than whoever signed the contract.
A claim is therefore an association between a bay and one owning party of either kind, and a platform MUST be able to say which kind a given bay has. It MUST hold the claim in a form that permits a bay to change hands — a home bay sold to a facility, a facility that closes — without rewriting the record of who was answerable for rounds already played.
4b.3.2 The claim is an undertaking#
Claiming a bay asserts that the equipment in it is what it reports itself to be. That assertion is what makes the rounds count, so:
- A platform MUST obtain the claimant's agreement to the owner's terms before recording a claim, and MUST record which version of those terms was agreed. An acceptance that cannot be tied to a version of the document is not an acceptance of anything.
- A platform MUST NOT record a claim from a request that did not come from an identified party, and MUST NOT complete one by an unsafe method: a scan is a
GET, and anything that follows links — a preview fetcher, a safety scanner — must not be able to claim a bay by looking at a message. - A claimed bay MUST be releasable by its owner, and a release MUST NOT retract the eligibility of rounds already played.
4b.3.3 The code on the screen#
An engine MUST be able to produce a claim code without its runtime running, and doing so MUST NOT require the network, a listening port, or write access to a journal. The claim code is derived from the installation identity on disk, which is the reason that identity is minted to a file rather than to a session: a bay being set up before its router is out of the box can still print the code that names it, and a bay midway through a round must not have to be stopped to be claimed.
An engine that has a display SHOULD also render a claim code while the bay is unclaimed, and MUST stop rendering it once the bay is claimed: a claimed room that keeps showing one is inviting the next person through the door to take it. The claim code and the check-in code (03 §3.4c) MUST be distinct — they are different acts by different parties, and one code serving both would make the second scan of the day silently mean something nobody intended.
The engine does not know what kind of party will scan it, and MUST NOT need to: it renders a code naming the installation, and the platform decides what kind of account is on the other end.
4c. Operating a bay from the portal#
A facility with twelve bays has a screen at the front desk, and the thing that screen is for is the party who left at 9pm with the golf still running. Somebody has to walk down and clear it. Doing that from the desk is an obvious convenience, and it is also the point in this design where the cloud starts telling a computer in someone else's building what to do. That is worth being slow about.
The portal does not have a vocabulary of its own. It makes the same requests every other participant makes (03 §1.1), under the same table, having declared the same role:
| request | does |
|---|---|
request.end_round | ends the round in progress, with or without releasing the players |
request.add_player | seats a player in the round |
request.remove_player | takes a player out of it |
A portal acting on a claimed bay speaks as operator, which is what the table permits those to. There is no portal-only message and no portal-only permission, and that is the point: one list of things a bay can be asked, one rule about who may ask, and a front desk is simply one of the things that may.
It is opt-in at the bay#
A bay accepts being operated remotely by admitting a participant that holds the operator role. A bay that admits none cannot be operated remotely at all, and that MUST remain a supported configuration forever — a home bay in somebody'"'"'s garage has no front desk and no reason to accept one.
Only the operator, and only a claimed bay#
- A directive MUST be authorized against the bay's claim (§4b.3): the member who claimed it, or a user of the commercial account that did.
- An unclaimed bay has no operator and MUST NOT accept either directive. There is nobody whose authority it could be acting on.
- Every directive MUST be journaled at the bay — what was asked, by which account, and when. A round that ended because somebody at a desk pressed a button must say so, or it looks like the round simply stopped.
- The bay MUST be able to refuse, and a refusal is not an error. The machine in the room is the final authority on what the machine in the room does.
Ending a round, and what happens to the players#
request.end_round carries one choice, because the two cases are genuinely different:
release_players | for |
|---|---|
false | the group is still there — a stuck round, a restart, an operator fixing something. The roster survives and they carry on. |
true | the group has gone. The roster is cleared and the bay returns to registration, ready for whoever is next. |
Releasing players MUST NOT alter any record. The round that was played is the round that was played; the members keep their cards, their shots keep their slots, and the positions are closed rather than erased. "Released" means the next group starts clean, not that the last group stops having been there.
5. Normative requirements#
- An implementation MUST provide full standard conformance without elevated privileges. A device or engine that requires administrative rights for normal operation MUST NOT be certified.
- Privileged capture MUST be a separately installed component, independently uninstallable, and MUST NOT be a dependency of the core.
- Privileged capture MUST be operator-initiated, purpose-stated, time-boxed, and written to the audit log at start and stop.
- An engine MUST NOT silently update its own executable code. Bundles, yes — see 04-bundle-distribution. Binaries, no.
- Passive collection of identifiers from devices not enrolled in the session MUST NOT occur.
- An engine MUST provide a configuration application meeting §4a, separate from the runtime service, and both MUST function with no network connectivity.
- An engine MUST provide, in the configuration application, a bay roster supporting add, rename, remove and status for permanently attached devices (13 §4), and MUST journal each change.
- An engine MUST refuse a bay certificate issued to a different
bay_id, and MUST continue operating normally when one expires (14 §4). - Configuration MUST be lockable by an operator-set password, and a certified bay MUST have it set. The privacy opt-out MUST remain available without it, time-limited to the current session and journaled.
- The runtime service MUST NOT run as LocalSystem or equivalent. Elevation is permitted at install time only.
- An engine MUST mint and persist an installation identity meeting §4b.1, MUST NOT derive it from a hardware identifier, and MUST re-mint it on a machine whose fingerprint does not match the one recorded beside it.
- A platform MUST accept and store data from an unclaimed bay, MUST NOT let it count toward a player rating or an event, and MUST make it count retroactively when the bay is claimed.
- An engine MUST be able to produce a claim code offline, with its runtime not running or running concurrently, without binding a port or writing to a journal.
- A platform MUST support bay owners of more than one kind — at minimum an individual member and a commercial operator — MUST record the version of the owner's terms accepted at the time of a claim, and MUST NOT complete a claim by an unsafe request method.
6. Platform targets#
| platform | engine |
|---|---|
| Windows 10/11 | user-level, portable, no install required |
| macOS | user-level, notarized |
| Linux | user-level |
| Android | app or foreground service |
| iOS | app, BLE central |
| embedded | not a target. A device that speaks SGA-OIS natively embeds a client — open a socket, send session.describe, receive session.manifest, send record.shot — not the engine. Anything that runs the engine has an operating system. |
The mobile targets are what let a phone plus a portable launch monitor record a live outdoor round against the same standard — which is the same reason the core has to work without privilege. There is no such thing as a SYSTEM service on iOS. A design that needs one cannot follow the player onto the course, and following the player everywhere is the product.