diff --git a/claude.md b/claude.md index 92ed5a6..517b64b 100644 --- a/claude.md +++ b/claude.md @@ -124,6 +124,38 @@ A library of reusable **steps** (bash or PowerShell scripts with declared inputs Default steps are seeded per org at boot (`SeedDefaultSteps`) from `VANTAGE_DEFAULT_STEPS_DIR`, which `server/Dockerfile` bakes to `/opt/default-steps` from the repo's `default_steps/`. Deliberately **not** under `/data` — that is a bind mount, so the library would be editable from the host. Adding a step there means committing a file and rebuilding, which is why `default_steps/` is in the `server` rebuild trigger. **Steps with `source: "default"` are read-only**: `UpdateStep`/`DeleteStep` refuse with `ErrDefaultStep` (409), because seeding rewrites them on every boot, so an edit would silently revert and a delete would come back. `web/` mirrors this — the step modal opens read-only, Delete is hidden, and the designer's per-step script override is `readOnly` for a default library step — but as elsewhere, the API is the boundary and the UI is the courtesy. Seeding writes straight to the collection rather than through `UpdateStep`, so the guard does not lock out the seeder. Logs are swept by retention (`workflow_log_retention_days`; nil = 30 days, 0 = forever). +### Server tags and workflow targeting + +A server carries `tags map[string]string` — lowercase `[a-z0-9_-]`, key ≤32, +value ≤64, 20 per server, `sys:` reserved. **There is no `tags` collection**: a +tag is a property of a server, not an entity, so `KnownTags` aggregates over +`servers` rather than reading a registry that would need reference counting to +know when a tag stopped existing. `PUT /api/servers/:id/tags` replaces the whole +map — last-write-wins over a small map beats merge semantics between two people +editing one server. The index is `{instance_id: 1, "tags.$**": 1}`, wildcard +because the queried key is chosen by the user at request time and cannot be named +in advance; `EnsureServerIndexes` warns rather than being fatal, since a missing +index degrades tag filtering to a scan of a small collection and is no reason to +refuse to serve the fleet list. + +`services.ResolveTargets` is the **single** answer to which servers a workflow +touches — the run path and validation both go through it, so the readout and the +dispatch cannot disagree. It is the distinct union of `target_server_ids` and +`target_tags` (AND across keys), ordered by the fleet rather than by the +arguments, so two runs naming the same servers differently are still comparable +line by line. **An empty selector matches nothing** on purpose: "matches +everything" turns a cleared field in the designer into a fleet-wide run. Both +empty is `ErrNoTargets` (400), not a success over zero servers. Offline servers +are **not** filtered out — the dispatcher already answers 503 per server, and a +patch run that silently omits an unreachable machine is worse than one that +visibly fails on it. + +`web/app/(app)/workflows/[id]/page.tsx` **duplicates that match logic in +TypeScript** to draw the resolved count without a round trip, since the browser +already holds the fleet. It is a second implementation of `UnionTargets` / +`MatchesTags` and must change in the same commit as the Go one — the same shape +of hazard as the mirrored token blocks. + ### Monitors HTTP, TCP, ICMP and TLS checks. Each monitor has a `runner`: `"server"` (executed by the server-side scheduler) or a `server_id` (pushed to that agent, which runs it locally and reports results). Consecutive failures beyond `retries` flip state to `down`, open an `Incident`, and notify. Hourly `Rollup` documents back the uptime graphs. diff --git a/web/app/(app)/workflows/page.tsx b/web/app/(app)/workflows/page.tsx index a743d2c..24fa261 100644 --- a/web/app/(app)/workflows/page.tsx +++ b/web/app/(app)/workflows/page.tsx @@ -64,7 +64,7 @@ export default function WorkflowsPage() {