All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Implements W2 of the wake/heartbeat canon (EPIC #892), honoring the CONVERGED-DESIGN contracts exactly. A2 store.sh — three-cursor durable queue (§1.2/§2.3/§2.4): - observed_seq authoritative monotonic cursor (detector assigns, lib records); SHAs are descriptors, not the cursor. - durable pending-inbox {observed_seq, locators, class, emit_ts, hmac}; hmac is an UNSIGNED placeholder (signing is W3/A5). - consumed_seq advances ONLY on a CONSUMED ack over a CONTIGUOUS gapless prefix <=N; never on enqueue/exit-0/paste. - coalesce: digest REPLACES the pending cumulative-state entry; actionable/human APPEND; ALL classes durable (durability never bypassed); absent class => actionable. - drain returns the deliverable set; every mutation is atomic write-tmp+rename. A4 ack.sh — RECEIVED/CONSUMED wrapper (§2.2): - RECEIVED dedups DELIVERY via wake_id (dup = re-RECEIVE, never re-action). - CONSUMED N local-write-only to the XDG ledger, cumulative, gapless-prefix enforced; background sync ships the ack (never blocks on network). - `embed` prints the one copy-run line a digest embeds. Watch-list SCHEMA contract (defaults/wake-watch-list.schema.json) with schema_version; wake manifest.txt declares component semver + supported schema range (schema_min/schema_max, Gate B — version metadata only, NOT path ownership). RED-first shell harness wired into test:framework-shell covers each invariant (three-cursor, coalesce-vs-append, contiguous-prefix CONSUMED, wake_id dedup, atomic tmp+rename crash-safety, durability-across-restart, ack-never-blocks-on-net). shellcheck clean; sanitization gate + operator firewall pass. Part of #892 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
167 lines
7.2 KiB
JSON
167 lines
7.2 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://mosaicstack.dev/schemas/wake-watch-list.schema.json",
|
|
"title": "Mosaic Wake Watch-List",
|
|
"description": "Declarative watch-list for the wake/heartbeat detector (EPIC #892). The SCHEMA is framework-owned; the VALUES are operator-supplied (repos, board files, lane anchors, per-class SLOs). This is the W2 schema contract only — the detector (W4) and digest renderer (W3) consume it. Per CONVERGED-DESIGN §1.4: 'operator repo; schema is framework, values are operator.'",
|
|
"type": "object",
|
|
"required": ["schema_version", "watches"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"schema_version": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"description": "Watch-list schema version. The wake component's manifest.txt declares the supported range (schema_min/schema_max, Gate B); a watch-list outside that range is rejected by the component, not silently coerced."
|
|
},
|
|
"host": {
|
|
"type": "string",
|
|
"description": "Optional operator label for the host this watch-list serves. Per-host single-instance detector (§1.1). Operator-supplied; no semantic meaning to the schema."
|
|
},
|
|
"repos": {
|
|
"type": "array",
|
|
"description": "Git repositories to watch. Source SHAs are descriptors, not the cursor (§2.4).",
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["id"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"id": {
|
|
"type": "string",
|
|
"description": "Operator-chosen stable identifier for this repo watch."
|
|
},
|
|
"remote": {
|
|
"type": "string",
|
|
"description": "Remote/clone locator (operator-supplied). No credentials inline; secrets are by-name via load_credentials."
|
|
},
|
|
"branches": {
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"description": "Branch refs to track. Empty => default branch."
|
|
},
|
|
"class": { "$ref": "#/$defs/class" },
|
|
"slo": { "$ref": "#/$defs/slo_ref" },
|
|
"aba_sensitive": {
|
|
"type": "boolean",
|
|
"default": false,
|
|
"description": "If true, this source needs an event-stream/webhook rather than poll-only (intra-poll ABA mitigation, §2.4 / gate G5). Poll-only remains a mitigation, not elimination."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"board_files": {
|
|
"type": "array",
|
|
"description": "Board / decision files whose edits must be caught (repo-section/anchor-scoped hashing, §1.1). Human-decision file edits, not just API-visible state.",
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["id", "path"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"id": { "type": "string" },
|
|
"repo": {
|
|
"type": "string",
|
|
"description": "Optional reference to a repos[].id this file lives in."
|
|
},
|
|
"path": {
|
|
"type": "string",
|
|
"description": "File path (operator-supplied). Locators are hard: repo/issue#/SHA/file:anchor (§2.1)."
|
|
},
|
|
"class": { "$ref": "#/$defs/class" },
|
|
"slo": { "$ref": "#/$defs/slo_ref" }
|
|
}
|
|
}
|
|
},
|
|
"lane_anchors": {
|
|
"type": "array",
|
|
"description": "In-file anchors (headings/markers) scoping a lane's obligations, so a file edit outside the lane's anchor does not wake it.",
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["id", "anchor"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"id": { "type": "string" },
|
|
"board_file": {
|
|
"type": "string",
|
|
"description": "Optional reference to a board_files[].id this anchor lives in."
|
|
},
|
|
"anchor": {
|
|
"type": "string",
|
|
"description": "Anchor text/marker delimiting the lane's section within the file."
|
|
},
|
|
"class": { "$ref": "#/$defs/class" },
|
|
"slo": { "$ref": "#/$defs/slo_ref" }
|
|
}
|
|
}
|
|
},
|
|
"slos": {
|
|
"type": "object",
|
|
"description": "Named per-class urgency SLO tiers. SYMBOLIC — the operator sets concrete durations; the schema only fixes the shape and the class ordering intent (§4: security/lease/CI = tight; board = tens of minutes; routine = hours). No numeric parameters are baked into the framework.",
|
|
"additionalProperties": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"class": { "$ref": "#/$defs/class" },
|
|
"fallback_bound": {
|
|
"type": "string",
|
|
"description": "Operator-supplied duration (e.g. '5m', '30m', '4h'). Symbolic tier is set by the operator, not the framework."
|
|
},
|
|
"quiet_hours_may_suppress": {
|
|
"type": "boolean",
|
|
"default": false,
|
|
"description": "If true, quiet-hours may suppress the cold fallback for this tier. MUST remain false for actionable/critical classes (§3: quiet-hours never gate an actionable/critical class)."
|
|
},
|
|
"measure_to": {
|
|
"type": "string",
|
|
"enum": ["consumed", "qualified-action-or-handoff"],
|
|
"description": "Terminal the SLO is measured to (§4/G8): CONSUMED measures reading; qualified-action-or-handoff measures doing. Actionable/critical classes measure to the action terminal."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"watches": {
|
|
"type": "array",
|
|
"description": "The declared source-coverage inventory: a lane-by-lane list of every operational source the lane depends on, so an omitted source cannot make the retirement vector pass vacuously (§4/G3 parity inventory). Each entry references a source declared above by kind+id.",
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["lane", "sources"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"lane": {
|
|
"type": "string",
|
|
"description": "Operator lane identifier this watch serves."
|
|
},
|
|
"sources": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["kind", "id"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"kind": {
|
|
"type": "string",
|
|
"enum": ["repo", "board_file", "lane_anchor"],
|
|
"description": "Which top-level collection the id refers to."
|
|
},
|
|
"id": {
|
|
"type": "string",
|
|
"description": "Reference to repos[].id / board_files[].id / lane_anchors[].id."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"$defs": {
|
|
"class": {
|
|
"type": "string",
|
|
"enum": ["digest", "actionable", "human", "terminal-log", "reaction"],
|
|
"description": "Wake class (§2.3). Only `digest` coalesces (cumulative-state replace); actionable/human APPEND. ALL classes are durable. Absent class => the consumer treats it as `actionable` (fail-safe)."
|
|
},
|
|
"slo_ref": {
|
|
"type": "string",
|
|
"description": "Name of an entry in the top-level `slos` map to apply to this source."
|
|
}
|
|
}
|
|
}
|