chore: replace em dashes with hyphens, add no-em-dash rule to CLAUDE.md
This commit is contained in:
@@ -7,7 +7,7 @@ The private Go module every Vantage service imports. Extracted from the
|
||||
vantage-shared/
|
||||
├── grpc/pb/ # the agent↔control-plane wire types, hand-written
|
||||
├── grpc/codec/ # the JSON codec they travel over
|
||||
├── proto/vantage/v1/ # vantage.proto — documentation for the above
|
||||
├── proto/vantage/v1/ # vantage.proto - documentation for the above
|
||||
├── mail/ # the one email system: transport + templates
|
||||
├── license/ # payload, sign, verify, trusted keys, plans
|
||||
├── models/ # Instance, User, Settings
|
||||
@@ -18,7 +18,7 @@ vantage-shared/
|
||||
└── cmd/lkctl/ # issue and inspect licences by hand
|
||||
```
|
||||
|
||||
Module path is `gitea.hostxtra.co.uk/vantage/vantage-shared` — **lowercase
|
||||
Module path is `gitea.hostxtra.co.uk/vantage/vantage-shared` - **lowercase
|
||||
`vantage`**, though the Gitea org is canonically `Vantage`. Gitea serves both
|
||||
spellings; Go module paths are case-sensitive strings, and two spellings would
|
||||
cache as two modules. Keep the lowercase one.
|
||||
@@ -46,7 +46,7 @@ v0.2.0`. Consumers pin exact versions.
|
||||
`grpc/codec`. Nothing generates them, and `vantage.proto` is not compiled by any
|
||||
build. It is the readable statement of the wire contract, and it lives in the
|
||||
same repository as the Go types precisely so that a message added to one can be
|
||||
added to the other **in the same commit** — that co-location is the only thing
|
||||
added to the other **in the same commit** - that co-location is the only thing
|
||||
enforcing the match, so do not split them again.
|
||||
|
||||
There used to be two copies of `pb`, in the agent and the server, and they had
|
||||
@@ -57,7 +57,7 @@ sides. The agent links the server half as dead code, which the linker drops.
|
||||
**A wire change lands in three steps, in this order**: release this module, bump
|
||||
the pin in `vantage/server` (live at the next push to main), bump the pin in
|
||||
`vantage/agent` (live only at the next `agent/v*` tag). The control plane will
|
||||
be ahead of the fleet in between, which was true before too — it is just written
|
||||
be ahead of the fleet in between, which was true before too - it is just written
|
||||
down in two `go.mod` files now instead of implied by a shared directory.
|
||||
|
||||
## Things that mirror something outside this repository
|
||||
@@ -71,7 +71,7 @@ with a file in another repository:
|
||||
probe finds no ciphertext and reports "this database stores no ciphertext
|
||||
yet", so the one gate that catches what a key fingerprint cannot becomes a
|
||||
no-op. `settings` is deliberately in neither that map nor
|
||||
`CiphertextCollections()` — its ESO read token is a SHA-256 hash, not
|
||||
`CiphertextCollections()` - its ESO read token is a SHA-256 hash, not
|
||||
ciphertext.
|
||||
- **`mail/templates/layout.html.tmpl`** carries the control plane's dark theme
|
||||
values as **literal hex**. Email clients support neither `var()` nor a
|
||||
@@ -81,8 +81,12 @@ with a file in another repository:
|
||||
- **`provision`** is the single implementation of the slug rules that both the
|
||||
control plane and Vantage HQ depend on. It is the one place those two
|
||||
repositories must agree on behaviour, which is why it is here rather than
|
||||
copied — but it now also means a change to it is a release and two pin bumps.
|
||||
copied - but it now also means a change to it is a release and two pin bumps.
|
||||
|
||||
`mail/render_test.go` renders every template and fails if one exists that no
|
||||
case covers. The templates are parsed in `init()`, so without that test a
|
||||
mistyped field is a boot-time panic in three services.
|
||||
|
||||
## Writing style
|
||||
|
||||
Never use em dashes (the long dash character) anywhere: code, comments, UI copy, docs, commit messages. Use a plain hyphen ` - `, a comma, a colon, or split the sentence instead.
|
||||
|
||||
+2
-2
@@ -98,8 +98,8 @@ func indexMember(name string) string { return "indexes/" + name + ".json" }
|
||||
// Reader is an opened archive.
|
||||
//
|
||||
// Open extracts to a temporary directory rather than streaming, because gzip
|
||||
// offers no random access and the manifest — which carries the checksums every
|
||||
// other member is judged against — is written last. Verifying before writing a
|
||||
// offers no random access and the manifest - which carries the checksums every
|
||||
// other member is judged against - is written last. Verifying before writing a
|
||||
// single document to the target is worth one pass over local disk. This is why
|
||||
// the container image needs a /tmp.
|
||||
type Reader struct {
|
||||
|
||||
+2
-2
@@ -148,8 +148,8 @@ func dumpIndexes(ctx context.Context, w *Writer, db *mongo.Database, name string
|
||||
defer cur.Close(ctx)
|
||||
|
||||
// The specs are read as raw BSON and re-encoded as extended JSON, one
|
||||
// element per index, so key order and every option the server reported —
|
||||
// partialFilterExpression, collation, weights and the rest — survive
|
||||
// element per index, so key order and every option the server reported -
|
||||
// partialFilterExpression, collation, weights and the rest - survive
|
||||
// verbatim. Decoding into bson.M would lose compound key order, and
|
||||
// reconstructing an index from a hand-picked set of options would drop
|
||||
// whatever was not picked.
|
||||
|
||||
+4
-4
@@ -72,7 +72,7 @@ func (o RestoreOptions) warn(format string, args ...any) {
|
||||
// The order is fixed and every check that can refuse does so before the first
|
||||
// write: format, checksums (done by Open), key policy, then target inspection.
|
||||
// A restore that has begun writing and then fails leaves a partial database
|
||||
// which the next run refuses to touch, which is correct — the alternative is a
|
||||
// which the next run refuses to touch, which is correct - the alternative is a
|
||||
// silent merge, and merging two control planes reconciles nothing.
|
||||
func Restore(ctx context.Context, opt RestoreOptions) (RestoreResult, error) {
|
||||
m := opt.Archive.Manifest()
|
||||
@@ -302,14 +302,14 @@ func splitBSON(raw []byte) (bson.Raw, []byte, error) {
|
||||
// The specs are handed to the createIndexes command exactly as the source
|
||||
// server reported them, rather than reconstructed into a mongo.IndexModel from
|
||||
// a hand-picked set of options. Reconstruction dropped every option nobody had
|
||||
// thought to pick — partialFilterExpression above all, which this codebase
|
||||
// thought to pick - partialFilterExpression above all, which this codebase
|
||||
// relies on for partial unique indexes, and which replayed as a full unique
|
||||
// index fails on any real database. It also lost compound key order, which is
|
||||
// significant.
|
||||
//
|
||||
// A unique index that will not build means the restored data violates it, and
|
||||
// the unique indexes here — (instance_id, email), instance slug, settings
|
||||
// instance, the ESO token hash — are tenant-isolation properties rather than
|
||||
// the unique indexes here - (instance_id, email), instance slug, settings
|
||||
// instance, the ESO token hash - are tenant-isolation properties rather than
|
||||
// optimisations. That aborts. A non-unique index failing is a performance
|
||||
// problem and warns.
|
||||
func replayIndexes(ctx context.Context, opt RestoreOptions, coll *mongo.Collection, name string) (int, error) {
|
||||
|
||||
@@ -243,7 +243,7 @@ func TestRestoreAbortsWhenAUniqueIndexCannotBuild(t *testing.T) {
|
||||
|
||||
// Built by hand rather than dumped: two documents that collide on email
|
||||
// alongside an index specification declaring email unique. No live database
|
||||
// would let those coexist, which is exactly the point — this is the shape
|
||||
// would let those coexist, which is exactly the point - this is the shape
|
||||
// of a corrupted or hand-edited archive, and restore must refuse rather
|
||||
// than load the rows and leave the index missing.
|
||||
a, err := bson.Marshal(bson.M{"email": "a@example.com"})
|
||||
@@ -371,8 +371,8 @@ func TestIdIndexIsSkipped(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestRestoreReplaysPartialUniqueIndex is the regression guard for the defect
|
||||
// that made a restore abort on any real database: a partial unique index —
|
||||
// this codebase has them on workflow_steps and settings — replayed as a full
|
||||
// that made a restore abort on any real database: a partial unique index -
|
||||
// this codebase has them on workflow_steps and settings - replayed as a full
|
||||
// unique index hits duplicate keys, and a failing unique index is fatal.
|
||||
func TestRestoreReplaysPartialUniqueIndex(t *testing.T) {
|
||||
client, _ := testDB(t)
|
||||
|
||||
+7
-7
@@ -109,8 +109,8 @@ func probe(ctx context.Context, opt VerifyOptions, rep *VerifyReport) error {
|
||||
rep.ProbeDecrypted = true
|
||||
return nil
|
||||
}
|
||||
// No ciphertext anywhere is an ordinary state — a deployment that has
|
||||
// stored no secrets, keys or SSO configuration yet — and is not a failure.
|
||||
// No ciphertext anywhere is an ordinary state - a deployment that has
|
||||
// stored no secrets, keys or SSO configuration yet - and is not a failure.
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -121,13 +121,13 @@ func probe(ctx context.Context, opt VerifyOptions, rep *VerifyReport) error {
|
||||
// This map MIRRORS BY HAND the bson tags in server/internal/models, which this
|
||||
// package cannot import: shared/ is a separate module and models is under
|
||||
// server/internal. It must change in the same commit as any rename of the
|
||||
// fields below — the same mirrored-constant hazard as web/lib/targets.ts and
|
||||
// fields below - the same mirrored-constant hazard as web/lib/targets.ts and
|
||||
// services.MaxWorkloadLogLines. The sources are:
|
||||
//
|
||||
// keys — models/key.go: private_key_enc, passphrase_enc
|
||||
// secrets — models/secret.go: encrypted_value
|
||||
// auth_providers — models/auth_provider.go: client_secret_enc
|
||||
// console_sessions — models/console_session.go: rdp_user_enc, rdp_pass_enc
|
||||
// keys - models/key.go: private_key_enc, passphrase_enc
|
||||
// secrets - models/secret.go: encrypted_value
|
||||
// auth_providers - models/auth_provider.go: client_secret_enc
|
||||
// console_sessions - models/console_session.go: rdp_user_enc, rdp_pass_enc
|
||||
//
|
||||
// settings is deliberately absent: it holds no ciphertext at all. The ESO read
|
||||
// token is stored as a SHA-256 hash, which no key opens.
|
||||
|
||||
+2
-2
@@ -45,7 +45,7 @@ const (
|
||||
FeatureMCP = "mcp"
|
||||
)
|
||||
|
||||
// Support levels. Carried for display and enforced by nothing — there is no code
|
||||
// Support levels. Carried for display and enforced by nothing - there is no code
|
||||
// path anywhere that branches on these, and there must not be one. They are here
|
||||
// so an air-gapped install can tell its operator who to call without reaching
|
||||
// Vantage HQ.
|
||||
@@ -74,7 +74,7 @@ type Limits struct {
|
||||
// FillUnset replaces any zero field with the same field from base.
|
||||
//
|
||||
// This exists for one reason: a licence signed before a field existed decodes it
|
||||
// as 0, and 0 would read as the most restrictive possible value — no monitors,
|
||||
// as 0, and 0 would read as the most restrictive possible value - no monitors,
|
||||
// and an audit log trimmed to nothing. A blob we cannot re-sign must not be
|
||||
// allowed to mean that.
|
||||
//
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ package license
|
||||
//
|
||||
// This table is the seed. The admin service owns the authoritative copy in its
|
||||
// `plans` collection, and every issued licence snapshots the plan it was cut
|
||||
// from — so editing a plan never rewrites an existing licence, the same rule as
|
||||
// from - so editing a plan never rewrites an existing licence, the same rule as
|
||||
// workflow_runs.steps_snapshot.
|
||||
//
|
||||
// Limits here are the BASE allowance: what the tier grants before anything is
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ func Verify(blob string, opts VerifyOpts) Result {
|
||||
// Parse verifies the signature only, ignoring binding and expiry.
|
||||
//
|
||||
// Used to display a licence and to inspect a blob a customer has emailed in.
|
||||
// Never use it for enforcement — it does not check who the licence is for.
|
||||
// Never use it for enforcement - it does not check who the licence is for.
|
||||
func Parse(blob string) (License, error) {
|
||||
parsed, err := lk.LicenseFromB32String(blob)
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SendLicense delivers the blob inline. It is signed public data, not a secret —
|
||||
// SendLicense delivers the blob inline. It is signed public data, not a secret -
|
||||
// it is useless on any instance other than the one it names.
|
||||
func (s Sender) SendLicense(to, instanceName, blob string) error {
|
||||
return s.sendTemplate(to, "", "license", struct {
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ func normaliseText(s string) string {
|
||||
}
|
||||
|
||||
// funcs are shared by both template flavours. They exist so that a message
|
||||
// template never formats a date or builds a structure itself — two templates
|
||||
// template never formats a date or builds a structure itself - two templates
|
||||
// formatting the same date two ways is exactly the drift this package removes.
|
||||
var funcs = map[string]any{
|
||||
// dict builds a map for the layout's helper templates, which take more
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
// It owns three things that used to exist in three copies: the SMTP
|
||||
// conversation (including the 465-implicit-TLS case that net/smtp gets wrong),
|
||||
// the RFC 5322 envelope, and the rendered look of a Vantage email. Callers see
|
||||
// only typed Send* methods — nobody outside this package builds a subject line,
|
||||
// only typed Send* methods - nobody outside this package builds a subject line,
|
||||
// a MIME part or a colour.
|
||||
package mail
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
// timeout bounds the whole SMTP conversation. Without it a mail server that
|
||||
// accepts the connection and then stalls holds an HTTP request open until the
|
||||
// client gives up — and admin's signup rollback runs on that request's context.
|
||||
// client gives up - and admin's signup rollback runs on that request's context.
|
||||
const timeout = 15 * time.Second
|
||||
|
||||
// Sender is a configured SMTP destination. It is a value, not a singleton:
|
||||
@@ -92,7 +92,7 @@ func (s Sender) sendTemplate(to, replyTo, name string, data any) error {
|
||||
// Port 465 is implicit TLS: the server expects a TLS handshake immediately, so
|
||||
// the connection is wrapped BEFORE any SMTP is spoken. Every other port gets
|
||||
// plaintext then STARTTLS if offered. net/smtp.SendMail only does the latter,
|
||||
// which is why it fails against a 465 mail server — that bug silently stopped
|
||||
// which is why it fails against a 465 mail server - that bug silently stopped
|
||||
// every admin email from being delivered once already.
|
||||
func (s Sender) send(m message) error {
|
||||
if !s.Enabled() {
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
{{template "lead" (printf "You have been invited to join %s on Vantage." .AccountName)}}
|
||||
{{template "p" "Set your own password and finish joining:"}}
|
||||
{{template "button" (dict "label" "Set password and join" "url" .Link)}}
|
||||
{{template "p" "This link expires in 24 hours. If you were not expecting this, ignore it — nothing happens until you open the link."}}
|
||||
{{template "p" "This link expires in 24 hours. If you were not expecting this, ignore it - nothing happens until you open the link."}}
|
||||
{{end}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
{{template "lead" (printf "You have been invited to join %s on Vantage." .AccountName)}}
|
||||
{{template "p" "Set your own password and finish joining:"}}
|
||||
{{template "button" (dict "label" "Set password and join" "url" .Link)}}
|
||||
{{template "p" "This link expires in 24 hours. If you were not expecting this, ignore it — nothing happens until you open the link."}}
|
||||
{{template "p" "This link expires in 24 hours. If you were not expecting this, ignore it - nothing happens until you open the link."}}
|
||||
{{end}}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
Every colour in the email system lives in this file and nowhere else, in the
|
||||
same way no component in web/, site/ or adminsite/ carries a hex. The values
|
||||
are web/app/globals.css's tokens — an email is read before the recipient
|
||||
are web/app/globals.css's tokens - an email is read before the recipient
|
||||
clicks through to the control plane, so the two should not look like
|
||||
different products. They are written as literal hex here because email
|
||||
clients support neither var() nor a reliable prefers-color-scheme, so the
|
||||
@@ -18,7 +18,7 @@
|
||||
--ink-3 #71879f --well #04101f
|
||||
|
||||
Layout is tables and inline styles throughout, which is not a stylistic
|
||||
choice — it is the only thing Outlook renders predictably.
|
||||
choice - it is the only thing Outlook renders predictably.
|
||||
|
||||
A message file overrides "title", "pill" and "body"; the empty defaults below
|
||||
exist so that a message needing no pill does not have to define one.
|
||||
@@ -57,7 +57,7 @@
|
||||
</p>
|
||||
{{- end -}}
|
||||
|
||||
{{- /* well shows machine output — a licence blob, an install ID. Mirrors
|
||||
{{- /* well shows machine output - a licence blob, an install ID. Mirrors
|
||||
web/'s --well surface, the floor beneath the ground. */ -}}
|
||||
{{- define "well" -}}
|
||||
<pre style="margin:0 0 20px;padding:14px;background:#04101f;border:1px solid #1e3855;border-radius:4px;color:#9fb3ca;font-family:ui-monospace,'Cascadia Mono','SF Mono',Menlo,Consolas,monospace;font-size:12px;line-height:1.5;white-space:pre-wrap;word-break:break-all;">{{.}}</pre>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{- /*
|
||||
The plain-text counterpart of layout.html.tmpl.
|
||||
|
||||
It defines the same helper names — p, lead, button, well, note, rows, chip —
|
||||
It defines the same helper names - p, lead, button, well, note, rows, chip -
|
||||
so a message's txt file reads as the same document as its html one, and a
|
||||
helper added on one side is obvious by its absence on the other.
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
{{template "lead" (printf "Your licence for %s is below." .InstanceName)}}
|
||||
{{template "p" "Paste it into Settings → Licence on your Vantage install:"}}
|
||||
{{template "well" .Blob}}
|
||||
{{template "p" "The licence is signed public data, not a secret — it is useless on any instance other than the one it names."}}
|
||||
{{template "p" "The licence is signed public data, not a secret - it is useless on any instance other than the one it names."}}
|
||||
{{end}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
{{template "lead" (printf "Your licence for %s is below." .InstanceName)}}
|
||||
{{template "p" "Paste it into Settings > Licence on your Vantage install:"}}
|
||||
{{template "well" .Blob}}
|
||||
{{template "p" "The licence is signed public data, not a secret — it is useless on any instance other than the one it names."}}
|
||||
{{template "p" "The licence is signed public data, not a secret - it is useless on any instance other than the one it names."}}
|
||||
{{end}}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
{{define "title"}}{{.InstanceName}} is renewed{{end}}
|
||||
{{define "body"}}
|
||||
{{template "lead" (printf "Your Free licence for %s now runs until %s." .InstanceName (date .Expires))}}
|
||||
{{template "p" "Nothing else changes — your servers, agents and monitors carry on as they were."}}
|
||||
{{template "p" "Nothing else changes - your servers, agents and monitors carry on as they were."}}
|
||||
{{end}}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
{{define "title"}}{{.InstanceName}} is renewed{{end}}
|
||||
{{define "body"}}
|
||||
{{template "lead" (printf "Your Free licence for %s now runs until %s." .InstanceName (date .Expires))}}
|
||||
{{template "p" "Nothing else changes — your servers, agents and monitors carry on as they were."}}
|
||||
{{template "p" "Nothing else changes - your servers, agents and monitors carry on as they were."}}
|
||||
{{end}}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
{{template "lead" "Confirm this address to finish setting up your Vantage account."}}
|
||||
{{template "button" (dict "label" "Confirm email address" "url" .Link)}}
|
||||
{{template "p" (printf "The link works once and expires in %d hours." .TTLHours)}}
|
||||
{{template "p" "If you did not request this, ignore this email — nothing happens until the link is opened."}}
|
||||
{{template "p" "If you did not request this, ignore this email - nothing happens until the link is opened."}}
|
||||
{{end}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
{{template "lead" "Confirm this address to finish setting up your Vantage account."}}
|
||||
{{template "button" (dict "label" "Confirm email address" "url" .Link)}}
|
||||
{{template "p" (printf "The link works once and expires in %d hours." .TTLHours)}}
|
||||
{{template "p" "If you did not request this, ignore this email — nothing happens until the link is opened."}}
|
||||
{{template "p" "If you did not request this, ignore this email - nothing happens until the link is opened."}}
|
||||
{{end}}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
(dict "k" "Instance" "v" .InstanceName)
|
||||
(dict "k" "New findings" "v" .Count))}}
|
||||
{{range .Rows}}
|
||||
{{if .FixedIn}}{{template "well" (printf "%s (%s) — %s on %s, fixed in %s" .CVEID .Severity .PackageName .ServerName .FixedIn)}}{{else}}{{template "well" (printf "%s (%s) — %s on %s, no fix published" .CVEID .Severity .PackageName .ServerName)}}{{end}}
|
||||
{{if .FixedIn}}{{template "well" (printf "%s (%s) - %s on %s, fixed in %s" .CVEID .Severity .PackageName .ServerName .FixedIn)}}{{else}}{{template "well" (printf "%s (%s) - %s on %s, no fix published" .CVEID .Severity .PackageName .ServerName)}}{{end}}
|
||||
{{end}}
|
||||
{{if .More}}{{template "p" (printf "…and %d more." .More)}}{{end}}
|
||||
{{template "note" (printf "Scanned against a vulnerability database pulled %s ago." .DBAge)}}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{{define "body"}}
|
||||
{{template "lead" .Summary}}
|
||||
|
||||
{{range .Rows}}- {{.CVEID}} ({{.Severity}}) — {{.PackageName}} on {{.ServerName}}{{if .FixedIn}}, fixed in {{.FixedIn}}{{else}}, no fix published{{end}}
|
||||
{{range .Rows}}- {{.CVEID}} ({{.Severity}}) - {{.PackageName}} on {{.ServerName}}{{if .FixedIn}}, fixed in {{.FixedIn}}{{else}}, no fix published{{end}}
|
||||
{{end}}
|
||||
{{if .More}}...and {{.More}} more.{{end}}
|
||||
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ type VulnDigestRow struct {
|
||||
PackageName string
|
||||
ServerName string
|
||||
// FixedIn empty means no vendor fix has been published, which the template
|
||||
// says explicitly rather than leaving blank — it is a real state, not
|
||||
// says explicitly rather than leaving blank - it is a real state, not
|
||||
// missing data.
|
||||
FixedIn string
|
||||
}
|
||||
@@ -23,7 +23,7 @@ type VulnDigestRow struct {
|
||||
type VulnDigest struct {
|
||||
InstanceName string
|
||||
// Count is every newly opened finding in the batch, which may exceed
|
||||
// len(Rows) — Rows is capped and More carries the remainder.
|
||||
// len(Rows) - Rows is capped and More carries the remainder.
|
||||
Count int
|
||||
TopSeverity string
|
||||
Summary string
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
||||
// keys, workflows, monitors and secrets. It is the unit a licence attaches to.
|
||||
//
|
||||
// A paying customer may hold several. That grouping is called an Account and
|
||||
// lives only in the admin control plane — this service never sees it.
|
||||
// lives only in the admin control plane - this service never sees it.
|
||||
type Instance struct {
|
||||
ID bson.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
|
||||
InstanceID string `bson:"instance_id" json:"instance_id"`
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ type Settings struct {
|
||||
|
||||
// LocalLoginEnabled is a pointer because it is absent on every settings
|
||||
// document written before this feature existed, and a plain bool would read
|
||||
// absent as disabled — turning off password login for the entire fleet at
|
||||
// absent as disabled - turning off password login for the entire fleet at
|
||||
// upgrade. Nil means enabled.
|
||||
LocalLoginEnabled *bool `bson:"local_login_enabled,omitempty" json:"local_login_enabled,omitempty"`
|
||||
|
||||
@@ -43,7 +43,7 @@ type Settings struct {
|
||||
// APITokenMaxDays caps how long a newly created API token may live.
|
||||
//
|
||||
// A pointer for the same reason the retention fields are: absent must mean
|
||||
// the default, and the default here is no cap at all — never-expire tokens
|
||||
// the default, and the default here is no cap at all - never-expire tokens
|
||||
// are allowed until an instance decides otherwise, so an upgrade changes
|
||||
// nothing. Nil or 0 is no cap. A positive value refuses both a longer
|
||||
// expiry and a token with no expiry.
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ const (
|
||||
AuthOIDC = "oidc"
|
||||
// AuthHQ marks a user projected from a Vantage HQ account. Its role,
|
||||
// password and existence are owned by HQ, and the instance API refuses to
|
||||
// change any of them locally — a role editable in two places is a role with
|
||||
// change any of them locally - a role editable in two places is a role with
|
||||
// two answers.
|
||||
AuthHQ = "hq"
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ message SyncResponse {
|
||||
|
||||
// collect_packages tells the agent whether this instance's licence grants
|
||||
// vulnerability scanning. False means do not collect at all: no gRPC body,
|
||||
// no document, no storage. The server re-checks on ReportPackages — this
|
||||
// no document, no storage. The server re-checks on ReportPackages - this
|
||||
// flag is the optimisation, the server check is the boundary.
|
||||
//
|
||||
// Absent reads as false, which is the safe direction: an old server that
|
||||
@@ -246,7 +246,7 @@ message ServerCommand {
|
||||
// Behind an L7 proxy the agent's HTTP/2 connection terminates at the proxy, so
|
||||
// keepalive pings are answered by the proxy whether or not the server behind it
|
||||
// is still there. A pod that dies leaves the agent blocked in Recv on a stream
|
||||
// that will never produce another message and never error — commands are
|
||||
// that will never produce another message and never error - commands are
|
||||
// dispatched into it and silently lost. Only traffic that originates at the
|
||||
// server itself distinguishes a live stream from an orphaned one.
|
||||
message PingCmd {
|
||||
|
||||
@@ -30,8 +30,8 @@ func CreateInstance(ctx context.Context, db *mongo.Database, name string) (*mode
|
||||
// created before payment and provisioning happens on the confirmed-payment
|
||||
// webhook. Provisioning with the placeholder's own ID keeps the id stable, so
|
||||
// the subscription's custom_data never points at a rewritten row and later
|
||||
// webhooks still resolve it. If an instance with this ID already exists — a
|
||||
// webhook retried after a partial provision — it is returned as-is rather than
|
||||
// webhooks still resolve it. If an instance with this ID already exists - a
|
||||
// webhook retried after a partial provision - it is returned as-is rather than
|
||||
// duplicated.
|
||||
//
|
||||
// The count-then-insert loop is racy on its own. It is safe only because
|
||||
@@ -92,7 +92,7 @@ var ErrSlugTaken = errors.New("slug taken")
|
||||
// the slug it holds now.
|
||||
//
|
||||
// It returns the current slug unchanged when the name still derives to it, so a
|
||||
// cosmetic edit — capitalisation, punctuation, a trailing "Ltd." — is not a move
|
||||
// cosmetic edit - capitalisation, punctuation, a trailing "Ltd." - is not a move
|
||||
// and cannot collide with the instance's own slug.
|
||||
func RenameSlug(name, currentSlug string) (string, error) {
|
||||
base, err := BaseSlug(name)
|
||||
@@ -110,13 +110,13 @@ func RenameSlug(name, currentSlug string) (string, error) {
|
||||
// It returns the name and slug the control plane held BEFORE the write, and
|
||||
// those are the only correct values to unwind with. The caller's own copy of the
|
||||
// instance may be stale, and admin's copy stores slug with `omitempty`, so an
|
||||
// unwind driven from there can write an empty slug — which either mis-restores
|
||||
// unwind driven from there can write an empty slug - which either mis-restores
|
||||
// the tenant host or trips the unique index against every other slugless row.
|
||||
//
|
||||
// The count-then-update is racy on its own, and is safe for the same reason
|
||||
// CreateInstanceWithID's loop is: instances.slug carries a unique index, so a
|
||||
// lost race surfaces as a duplicate-key error. Unlike creation there is nothing
|
||||
// to retry with — the caller asked for one specific name — so it becomes
|
||||
// to retry with - the caller asked for one specific name - so it becomes
|
||||
// ErrSlugTaken. Do not remove the duplicate-key branch, and do not remove the
|
||||
// index.
|
||||
func RenameInstance(ctx context.Context, db *mongo.Database, instanceID, name string) (inst *models.Instance, prevName, prevSlug string, err error) {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// plane and sitesvc.
|
||||
//
|
||||
// These rules used to be duplicated: the control plane owned one copy and
|
||||
// sitesvc mirrored it by hand. The copies had already drifted — sitesvc retried
|
||||
// sitesvc mirrored it by hand. The copies had already drifted - sitesvc retried
|
||||
// on a lost slug race while the control plane returned an error. This package
|
||||
// is the single definition; neither service may reimplement any of it.
|
||||
package provision
|
||||
|
||||
Reference in New Issue
Block a user