SGA‑OISOpen Interoperability Standard
1.0 Draft

SGA-OIS conformance kit

Public, runnable offline, no network access, no SGA account.

Know you pass before you ever contact us. Passing the kit is the technical floor for certification. Trust levels — A1 and up — come from the partner agreement and signing, not from the kit (trust levels).

What the kit reads, and what it doesn't. It checks documents: your capability descriptor and, for a simulator, scorecards, round boundaries and the course document. It cannot see the frames you send live (record.shot, record.state); watch those with sgapoke or sgaengine -livedata (testing your integration).

Run it#

The kit is Python 3.10 or newer, with no virtualenv required, so it runs the same on a build server as on your bench.

shell
# from sga-ois/conformance/
python -m sgaconform your-descriptor.json          # your device
python -m sgaconform your-scorecard.json --manifest m.json
python -m sgaconform --list-rules                  # every rule, by document kind
python -m pytest tests/ -q                         # the kit's own tests (maintainers)

Optional, for shape checking as well as semantics:

shell
pip install jsonschema referencing

Without it, JSON Schema validation is skipped with a notice and the semantic rules still run. The semantic rules are the ones that catch real interoperability bugs, so the kit is useful either way.

Two passes#

passchecksneeds
schemashape — types, required fields, ranges, enumsjsonschema
rulesmeaning — the things that are structurally valid and still wrongstdlib

What the rules cover#

Run python -m sgaconform --list-rules for the list your copy enforces.

ruleapplies tochecks
R001capability-descriptorcalculated must say what it was calculated from.
R002capability-descriptorinferred must name and version a model.
R003capability-descriptorA calculated field's inputs should be obtainable.
R004shot-recordProvenance may be downgraded at runtime, never upgraded.
R010every documentPresence of x- fields must never itself be an error.
R011shot-recordEvery core numeric field name ends in its unit.
R012every document'draw' and 'fade' are handedness-dependent readings of one number.
R013every documentAn absolute timestamp must carry its UTC offset.
R014every documentt_ms must stay inside the 24-hour epoch, because it rolls over.
R015shot-recordWall clock orders across sessions; round_id joins them.
R016capability-descriptorInformational. A wildcard is valid and works; it just says less.
R017capability-descriptorrequired has to name a field.
R020shot-record
R021shot-record
R022shot-record
R023shot-recordThe three places elevation appears must agree.
R024shot-record
R030shot-record
R031shot-record
R032shot-record
R033shot-record
R040capability-descriptorA2 and A3 sign; a claim of either needs a signature over the session nonce.
R041capability-descriptorA firmware version without a digest is a self-report.
R042capability-descriptorWith one sensor, attribution is obvious. With several, only the device knows.
R043capability-descriptorA field the unit is not licensed for must not read as available.
R044shot-recordA putt block on a shot played from a fairway is a mapping error.
R045shot-recordConfigured green speed and measured roll deceleration should broadly match.
R046shot-recordA club id with no stated method cannot support per-club statistics.
R047shot-recordA UI selection is a claim about the club, not an observation of it.
R048shot-recordA shot may not claim an identification method the device cannot perform.
R049shot-recordA standing value carried into a shot should say how old it was.
R050event-rules-packageA sanctioned event pins its course by digest, not by name.
R051event-rules-packageCamera capture an event requires is disclosed at registration.
R052event-rules-packageAn event restricted to launch-monitor input requires a launch monitor.
R060scorecardA scorecard must agree with its own arithmetic.
R061scorecardCheckpoints are emitted per hole and must not skip one.
R062scorecardThe card and the shot stream must agree, exactly.
R063scorecardA screen name must never look like a member identifier.
R064scorecardA card with no scores must be declared as one, and vice versa.
R064shot-recordA stroke added without a swing still has to say where the ball is now.
R065capability-descriptorA standing field with no max_age_ms can go stale silently.
R065courseThe course must cover its holes, and rank each one exactly once.
R066courseA tee that skips a hole is a tee nobody can play a round from.
R066event-rules-packageA deadline checked against the bay clock is not a deadline.
R067courseTwo tees with one name cannot be told apart.
R067session-manifestAfter the fact, it must be answerable what time the engine believed.
R068capability-descriptorA simulator that cannot restore a round must say so.
R068round-boundaryA round that somebody ended has a card; one that was cut off has none.
R069bay-profileA bay cannot host a deadline it has no trusted clock for.
R069round-boundaryA round that does not say what it is being played over cannot be scored.
R070bay-profileEquipment changes, calibration expires, connectivity degrades.
R070requestEnding a round is a decision about somebody's afternoon.
R071bay-profileA rear view shows a person swinging, not which person.
R071requestA request that cannot say on whose authority it was made is a request
R072shot-recordA sim set for the wrong side mirrors the shot, silently, for a whole round.
R073bay-profileThe second camera earns its cost on every shot, not just for lefties.
R074capability-descriptorAn unrecognized condition cannot be checked by anyone but its author.
R075shot-recordZero is a measurement. Absent is the truth.
R076shot-recordA cold ball is measurably slower, and it is seasonal and systematic.
R079capability-descriptorA device that measures a person must say so.
R080bay-profileEnrollment IS the binding, so a token alongside it means nothing extra.
R081bay-profileAn admission list is only as current as the bundle carrying it.
R082bay-profileA tier's floor is meaningless if the profile cannot show who resolves what.

The kit also refuses a descriptor that claims A2 or A3 without a signature (R040). It checks a claim; it cannot grant a level.

Severity is error, warning or info. Only error fails a run — warnings are for a human to look at, not a gate.

Some rules need context. R004 can only verify that a downgrade is a downgrade if it can see the declared provenance, so pass --manifest; without it the rule reports info rather than error instead of guessing.

Adding a rule#

One function with a decorator in sgaconform/rules.py (core) or rules_vendor.py (descriptor and vendor-specific rules):

Python
@rule("R0NN", "shot-record")
def my_check(doc, ctx):
    """One line, shown by --list-rules."""
    if bad:
        yield Finding("R0NN", "error", "path.to.field", "what and why")

Read rules.py as the list of things you will be tested on. That is the point of shipping it rather than describing it.

Fixtures#

fixtures/valid/ must all pass. fixtures/invalid/ each fail on a specific rule, asserted by name in tests/, so a rule cannot silently stop working.

Vendor extensions are exercised deliberately: the shot-record fixture carries unknown x- fields, and a run that rejects them fails. Tier-1 extensions ship without permission and must never break another implementation.

License#

The conformance kit is Apache 2.0 — see ../LICENSE-CODE.txt. That differs from the specification itself, which is CC BY 4.0; ../LICENSE says which is which and why.

Simulated Golf Association · SGA-OIS · generated from the specification sources