Compare commits
1 Commits
docs/heart
...
fix/detect
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84d802cafe |
@@ -1,49 +0,0 @@
|
||||
# Framework-Layering Constraints for the Fleet Wake / Heartbeat-Efficiency Component
|
||||
|
||||
**From:** MS-LEAD (Matrix comms-evolution / framework-tooling lead)
|
||||
**To:** heartbeat/wake-efficiency designers (via docs/scratchpads/heartbeat-planning/)
|
||||
**Date:** 2026-07-25 · **Purpose:** one-page framework-layering input BEFORE design convergence
|
||||
**Grounded in:** RFC-001 (MACP/Matrix-native) + RFC-002 (open-source install/config/topology), `~/agent-work/matrix-impl/`
|
||||
|
||||
---
|
||||
|
||||
## 0. READ FIRST — the critical overlap (coherence, not duplication)
|
||||
|
||||
**The wake/heartbeat-efficiency work and the MACP presence/heartbeat model are the SAME problem domain.** RFC-001 §4.5 already defines a `mosaic.presence` heartbeat (fields: `status`, `seq`, `interval_ms`) whose *age vs `dark_threshold`* deterministically drives online/away/offline; §5 makes that same heartbeat the input to coordinator-dark escalation. P1 (PR #888) has this **built and dev-proven** (a hard-killed agent flips to offline within threshold, heartbeat-driven).
|
||||
|
||||
**Recommendation:** do NOT build a second, parallel heartbeat. Aim for **one liveness substrate** where a single heartbeat (a) drives presence, (b) feeds escalation, and (c) is the input to wake-efficiency scheduling. Wake-efficiency then = "schedule the next wake from liveness + pending-work signals" over the *same* heartbeat the presence layer already emits. Two competing heartbeats = drift, double the wasted wakes, and two sources of truth for "is this agent alive." Please align the heartbeat event schema + threshold keys with MACP (RFC-001 §4.5) — reuse `interval_ms` / `dark_threshold` naming so config and reasoning compose.
|
||||
|
||||
## 1. Framework vs product layering (RFC-002 §3)
|
||||
|
||||
- **Boundary rule:** *framework owns the agent/harness contract + anything needed at spin BEFORE product code exists; product owns deployed services/libraries.*
|
||||
- A wake/heartbeat scheduler is **agent-runtime-level → FRAMEWORK** (`~/.config/mosaic/tools/` + a guide). It must run without `/src/<product>` present.
|
||||
- If it reads/writes *shared fleet* liveness, split cleanly: the framework tool operates on a **framework-file layer** by default, with an **optional product-DB override** when the product is reachable (see §3).
|
||||
|
||||
## 2. State location
|
||||
|
||||
- **Framework/per-agent state → `~/.config/mosaic/`** (the established convention: tokens under `~/.config/mosaic/secrets/`, tool state under `~/.config/mosaic/`). Per-agent state that must survive a non-persistent shell goes on-disk here (mirrors the `git config mosaic.gitIdentity` persisted-per-worktree pattern from the identity patches).
|
||||
- **Fleet-shared liveness → the product DB (Postgres)**, when present.
|
||||
- **Do not invent a third store.** Framework file-plane + product DB-plane only.
|
||||
|
||||
## 3. Config schema conventions (RFC-002 §5)
|
||||
|
||||
- **Precedence:** `install-time → DB-override → compiled-default`, with **sane defaults compiled in** so a bare install works.
|
||||
- **Two config planes (state this explicitly):** a framework tool runs before the product DB exists, so it needs a **file-based framework config** (`~/.config/mosaic/*.json`, consistent with `~/.claude/hooks-config.json` / `settings.json` patterns) **plus** the product DB as authoritative override when reachable.
|
||||
- **Classify install-immutable vs runtime-tunable.** Heartbeat cadence + thresholds (`interval_ms`, `miss_tolerance`, `dark_threshold`, wake-min/max) are **runtime-tunable** — and should share keys with MACP's escalation thresholds (RFC-001 §5) so they're not defined twice.
|
||||
|
||||
## 4. Installer patterns (RFC-002 §6) — if it ships as an installable suite
|
||||
|
||||
- Guided installer: **detect existing state → suggest (never silent-default) → immutable-vs-tunable gate → validate BEFORE declaring success.**
|
||||
- **systemd timers are the sharp edge:** any drop-in that overrides cadence MUST emit the `OnUnitActiveSec=` **blank-reset** line on every override (this is literally batch item (4) — the ~88-wasted-wakes/day bug from a missing reset registering both timers). A *wake-efficiency* tool that gets this wrong recreates the exact bug it exists to solve. Treat the blank-reset as a hard invariant + a test.
|
||||
|
||||
## 5. #869 publish-gate + fail-closed discipline
|
||||
|
||||
- If the wake tool touches **enforcement paths** (lease/activation/timers that gate fleet behavior), the #869 publish-gate discipline applies: **fail-closed, no silent fallthrough** (cf. Patch 2b's fail-loud fix — an absent expected value must error, not silently degrade), and release/activation stays **non-autonomous**.
|
||||
|
||||
## 6. Coherence with tmux-P0
|
||||
|
||||
- Preserve the existing layering: **tmux = P0 fast-path**, Matrix/durable above it. Wake-efficiency should **reduce wasted wakes** without starving the presence heartbeat (same signal — §0). The win is fewer, better-timed wakes over one liveness model — not a new scheduling silo.
|
||||
|
||||
---
|
||||
|
||||
**The prize:** one coherent liveness/wake substrate — a single heartbeat that presence, escalation, and wake-efficiency all read — living in the framework layer, config in two planes (file + DB-override), thresholds shared with MACP, systemd blank-reset as a hard invariant. Happy to review the converging design against this and against RFC-001/002 directly.
|
||||
@@ -511,7 +511,11 @@ get_gitea_token() {
|
||||
# (pr-create, issue-create, …) authors under the right identity — matching the
|
||||
# git credential helper. Backward-compatible: nothing resolvable → shared logic below.
|
||||
local _ident="${MOSAIC_GIT_IDENTITY:-}"
|
||||
[[ -z "$_ident" ]] && _ident="$(git config --get mosaic.gitIdentity 2>/dev/null || true)"
|
||||
local _ident_src="MOSAIC_GIT_IDENTITY"
|
||||
if [[ -z "$_ident" ]]; then
|
||||
_ident="$(git config --get mosaic.gitIdentity 2>/dev/null || true)"
|
||||
_ident_src="git config mosaic.gitIdentity"
|
||||
fi
|
||||
if [[ -n "$_ident" ]]; then
|
||||
local _idpfx=""
|
||||
case "$host" in
|
||||
@@ -524,6 +528,15 @@ get_gitea_token() {
|
||||
cat "$_idtok"
|
||||
return 0
|
||||
fi
|
||||
# FAIL LOUD: an explicit git identity was requested for a recognized Gitea host,
|
||||
# but no per-slot token exists for THAT identity. Refuse to fall through to the
|
||||
# shared/default credential loader below — silently borrowing another slot's token
|
||||
# would post PRs/issues/reviews under the WRONG agent (e.g. rev2's review attributed
|
||||
# to coder3), corrupting Gate-16 author≠reviewer separation. Hard-stop instead so the
|
||||
# caller aborts loudly rather than acting as the wrong identity.
|
||||
echo "Error: git identity '$_ident' requested (via $_ident_src) for host '$host', but no per-slot token at $_idtok." >&2
|
||||
echo " Refusing to borrow another slot's token. Provision the per-slot token, or unset the identity to use shared credentials." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -10,10 +10,19 @@
|
||||
# 2. Correct per-slot token file path chosen per host
|
||||
# (gitea-usc-<id>.token vs gitea-mosaicstack-<id>.token).
|
||||
# 3. Per-slot token present -> that token is returned (agent-authored calls).
|
||||
# 4. Per-slot token absent -> falls back to the shared credential-loader
|
||||
# token (backward-compat / no-op for hosts without per-slot tokens).
|
||||
# 5. Unrelated host with no shared credentials configured -> failure
|
||||
# (unchanged, existing behavior).
|
||||
# 4. No identity requested -> shared credential-loader token (backward
|
||||
# compat, unchanged).
|
||||
# 5. Patch 2b — explicit identity + recognized Gitea host + ABSENT per-slot
|
||||
# token for that identity -> FAIL LOUD (nonzero return, empty stdout, a
|
||||
# stderr diagnostic naming identity/source/host/expected path). Must NOT
|
||||
# fall through to the shared/default token (Gate-16 author≠reviewer
|
||||
# integrity — never silently borrow another slot's credentials). Covered
|
||||
# for both identity sources (git config, MOSAIC_GIT_IDENTITY env) and
|
||||
# for a same-identity cross-host case (token exists for one host, not
|
||||
# the other).
|
||||
# 6. Scope containment: identity requested + an UNRECOGNIZED Gitea host (no
|
||||
# per-slot token scheme) -> Patch 2b does not apply; existing
|
||||
# fall-through behavior is unchanged.
|
||||
#
|
||||
# Uses a stubbed credentials.json + stubbed per-slot token files under a fake
|
||||
# HOME. NEVER reads real secrets or touches the real ~/.config/mosaic/secrets.
|
||||
@@ -95,24 +104,110 @@ out=$(call_get_gitea_token "git.mosaicstack.dev" MOSAIC_GIT_IDENTITY=agentB)
|
||||
assert_eq "env beats git-config identity token" "agentB-mosaicstack-token" "$out"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Identity resolves but has no per-slot token for THIS host -> falls back
|
||||
# to the shared token (per-agent identity is opt-in per host).
|
||||
# 4. FAIL LOUD (Patch 2b): an identity is explicitly requested (via git config
|
||||
# mosaic.gitIdentity, and separately via MOSAIC_GIT_IDENTITY env) for a
|
||||
# RECOGNIZED Gitea host, but no per-slot token exists for THAT identity.
|
||||
# Must NOT fall through to the shared/default token — silently borrowing
|
||||
# another slot's credentials would post PRs/issues/reviews as the WRONG
|
||||
# agent (Gate-16 author≠reviewer integrity break). Expect: nonzero return,
|
||||
# EMPTY stdout (no token — shared or otherwise — leaked), and a stderr
|
||||
# diagnostic naming the identity, its source, the host, and the expected
|
||||
# per-slot token path.
|
||||
# ---------------------------------------------------------------------------
|
||||
assert_failloud() {
|
||||
local desc="$1" host="$2" ident="$3" expected_tok_path="$4"; shift 4
|
||||
local stderr_file="$WORK_DIR/stderr.tmp"
|
||||
: > "$stderr_file"
|
||||
set +e
|
||||
local stdout
|
||||
stdout=$(call_get_gitea_token "$host" "$@" 2>"$stderr_file")
|
||||
local rc=$?
|
||||
set -e
|
||||
local stderr
|
||||
stderr=$(cat "$stderr_file")
|
||||
if [[ "$rc" -eq 0 ]]; then
|
||||
echo "FAIL: $desc — expected nonzero return, got 0 (stdout='$stdout')" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ -n "$stdout" ]]; then
|
||||
echo "FAIL: $desc — expected empty stdout (no token leaked), got '$stdout'" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$stderr" != *"$ident"* ]]; then
|
||||
echo "FAIL: $desc — stderr does not name the requested identity '$ident':" >&2
|
||||
echo "$stderr" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$stderr" != *"$host"* ]]; then
|
||||
echo "FAIL: $desc — stderr does not name the host '$host':" >&2
|
||||
echo "$stderr" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$stderr" != *"$expected_tok_path"* ]]; then
|
||||
echo "FAIL: $desc — stderr does not name the expected per-slot token path '$expected_tok_path':" >&2
|
||||
echo "$stderr" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$stderr" == *"shared"*"token"* ]]; then
|
||||
echo "FAIL: $desc — stderr unexpectedly mentions a shared token value:" >&2
|
||||
echo "$stderr" >&2
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
|
||||
# 4a. git config mosaic.gitIdentity source, recognized host (mosaicstack),
|
||||
# shared token IS present but must not be borrowed.
|
||||
git -C "$REPO_DIR" config mosaic.gitIdentity no-such-agent
|
||||
out=$(call_get_gitea_token "git.mosaicstack.dev")
|
||||
assert_eq "no per-slot token falls back to shared" "shared-mosaicstack-token" "$out"
|
||||
assert_failloud "fail-loud via git-config identity (recognized host)" \
|
||||
"git.mosaicstack.dev" "no-such-agent" \
|
||||
"$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-no-such-agent.token"
|
||||
git -C "$REPO_DIR" config --unset mosaic.gitIdentity
|
||||
|
||||
# 4b. MOSAIC_GIT_IDENTITY env source (takes priority over git config), same
|
||||
# recognized-host / absent-token scenario -> also fails loud.
|
||||
assert_failloud "fail-loud via MOSAIC_GIT_IDENTITY env (recognized host)" \
|
||||
"git.mosaicstack.dev" "no-such-agent-env" \
|
||||
"$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-no-such-agent-env.token" \
|
||||
MOSAIC_GIT_IDENTITY=no-such-agent-env
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Correct per-slot token PATH per host: same agent id, only a usc token
|
||||
# exists -> usc host returns it, mosaicstack host must NOT leak it and
|
||||
# instead falls back to the shared mosaicstack token.
|
||||
# exists. usc host returns it (happy path, unchanged). mosaicstack host
|
||||
# has NO per-slot token for this identity -> Patch 2b fail-loud applies
|
||||
# there too (must NOT fall back to the shared mosaicstack token, and must
|
||||
# NOT leak the agent's usc token either).
|
||||
# ---------------------------------------------------------------------------
|
||||
echo -n "agentD-usc-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-usc-agentD.token"
|
||||
git -C "$REPO_DIR" config mosaic.gitIdentity agentD
|
||||
out=$(call_get_gitea_token "git.uscllc.com")
|
||||
assert_eq "host-scoped token path (usc)" "agentD-usc-token" "$out"
|
||||
out=$(call_get_gitea_token "git.mosaicstack.dev")
|
||||
assert_eq "host-scoped token path (no cross-host leak)" "shared-mosaicstack-token" "$out"
|
||||
assert_failloud "fail-loud on cross-host absence (no fallback, no cross-host leak)" \
|
||||
"git.mosaicstack.dev" "agentD" \
|
||||
"$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentD.token"
|
||||
git -C "$REPO_DIR" config --unset mosaic.gitIdentity
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Scope containment: identity explicitly requested, but the host is NOT a
|
||||
# recognized Gitea host (no per-slot token scheme at all) -> Patch 2b does
|
||||
# NOT apply; existing fall-through behavior is unchanged (ends in the
|
||||
# pre-existing generic failure since no shared credentials match either,
|
||||
# NOT the fail-loud diagnostic path).
|
||||
# ---------------------------------------------------------------------------
|
||||
git -C "$REPO_DIR" config mosaic.gitIdentity no-such-agent
|
||||
set +e
|
||||
out=$(call_get_gitea_token "github.com" 2>"$WORK_DIR/stderr-scope.tmp")
|
||||
rc=$?
|
||||
set -e
|
||||
err=$(cat "$WORK_DIR/stderr-scope.tmp")
|
||||
if [[ "$rc" -eq 0 ]]; then
|
||||
echo "FAIL: unrecognized host + identity — expected nonzero (no credentials configured), got 0" >&2
|
||||
fail=1
|
||||
fi
|
||||
if [[ "$err" == *"no per-slot token at"* ]]; then
|
||||
echo "FAIL: unrecognized host + identity — fail-loud diagnostic must not fire for a host with no per-slot scheme:" >&2
|
||||
echo "$err" >&2
|
||||
fail=1
|
||||
fi
|
||||
git -C "$REPO_DIR" config --unset mosaic.gitIdentity
|
||||
|
||||
if [[ "$fail" -eq 0 ]]; then
|
||||
|
||||
Reference in New Issue
Block a user