Controller and share credentials are currently edited as cleartext into a script on the SFLD share, one value for the whole fleet, re-applied by GE-Enforce every cycle. Rotating means editing that file and hoping - nothing reports which bays picked the new value up - and sites need per-bay variation, which a manifest scope cannot express because scopes are per PC TYPE. A scope per bay is a spreadsheet, not a model. ShopDB therefore owns the definitions and the targeting, and a PC receives a decided answer rather than rules to evaluate. Targeting uses the axes GE-Enforce already filters on plus those the asset model knows: hostname (including -like globs), machine, model, controller OS, PC type with alias expansion, GE-Enforce profile, and a selector over the stored DNC projection - "every machine whose DataHost is that share" is answerable from data already held, and it also answers which machines need a credential at all. Overlapping matches are guaranteed rather than exceptional, so precedence is explicit: an integer priority, then scope specificity, then rule id for stability. The resolver must be able to explain which rule won and why, or nobody will trust it. It reuses plugins/geenforce/filters.py; there are already two implementations of this matching logic kept honest by a parity harness, and a third would drift silently. Targeting stays separate from detection, mirroring PCTypes versus DetectionMethod: who gets a credential is a server question, whether it is already applied is a local one, and conflating them rewrites the secret every cycle instead of healing drift. Stored Fernet-encrypted with the key OUTSIDE the database, write-only through the API and masked on read, fetched with a dedicated credentials.fetch scope so a leaked collector key does not yield controller passwords, and every fetch audited. PROPOSED, not accepted: it makes ShopDB a credential store, and the key becomes a single point of loss - restore the database without it and every credential is unrecoverable, which docs/BACKUP-RESTORE.md must state in the same change that implements this. Registry-only provisioning is recommended as a first step regardless, since it removes the cleartext from the share immediately and the client helper is identical either way.
Architecture Decision Records
Each ADR captures a single architectural decision: the context, the decision itself, the consequences, and the alternatives considered. ADRs are immutable once accepted. Superseded ADRs stay in this folder with a pointer to the newer ADR.
Status definitions
- PROPOSED: drafted, awaiting decision
- ACCEPTED: decision is in effect
- SUPERSEDED: replaced by a later ADR (link forward)
- DEPRECATED: no longer in effect, no replacement
Index
| ADR | Title | Status |
|---|---|---|
| 001 | Asset model is the platform contract | ACCEPTED |
| 002 | Plugin contract versioning (semver) | ACCEPTED |
| 003 | Plugin distribution model | ACCEPTED |
| 004 | Deployment topology (per-site instances) | ACCEPTED |
| 005 | Equipment vs measuringtools plugin scope | ACCEPTED |
| 006 | Plugin collector contract pattern | ACCEPTED |
| 007 | Product versioning and releases | ACCEPTED |
| 008 | Plugin migration ownership (per-plugin chains) | ACCEPTED |
| 009 | Frontend plugin route gating | ACCEPTED |
| 010 | Frontend plugin hook contract | ACCEPTED |
| 011 | Machines rename + modeltypes retyping | ACCEPTED |
| 012 | GE-Enforce manifest ownership in shopdb | ACCEPTED |
| 013 | Plugin catalog, curated shelf, and lean per-site builds | PROPOSED |
| 014 | Schema-lean per-site builds (retire cross-plugin FKs, prune not-installed plugin tables) | ACCEPTED |
| 015 | Where a site's own data is allowed to live (setting, site directory, or seed) | ACCEPTED |
| 016 | Credential delivery to the fleet (ShopDB-resolved targeting, encrypted at rest) | PROPOSED |
Authoring
When proposing a new decision, copy the most recent ADR as a template, increment the number, and update this index. Do not edit accepted ADRs in place; supersede them with a new one.