instar 1.3.709 → 1.3.711
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +228 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +25 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/HttpLeaseTransport.d.ts +9 -0
- package/dist/core/HttpLeaseTransport.d.ts.map +1 -1
- package/dist/core/HttpLeaseTransport.js +28 -2
- package/dist/core/HttpLeaseTransport.js.map +1 -1
- package/dist/core/MultiMachineCoordinator.d.ts +44 -0
- package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
- package/dist/core/MultiMachineCoordinator.js +52 -0
- package/dist/core/MultiMachineCoordinator.js.map +1 -1
- package/dist/core/PeerEndpointResolver.d.ts +0 -0
- package/dist/core/PeerEndpointResolver.d.ts.map +1 -1
- package/dist/core/PeerEndpointResolver.js +0 -0
- package/dist/core/PeerEndpointResolver.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +19 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/RopeRecoveryProber.d.ts +126 -0
- package/dist/core/RopeRecoveryProber.d.ts.map +1 -0
- package/dist/core/RopeRecoveryProber.js +266 -0
- package/dist/core/RopeRecoveryProber.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +12 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/ropeProbeContract.d.ts +56 -0
- package/dist/core/ropeProbeContract.d.ts.map +1 -0
- package/dist/core/ropeProbeContract.js +92 -0
- package/dist/core/ropeProbeContract.js.map +1 -0
- package/dist/core/tailscaleStatusParser.d.ts +46 -0
- package/dist/core/tailscaleStatusParser.d.ts.map +1 -0
- package/dist/core/tailscaleStatusParser.js +80 -0
- package/dist/core/tailscaleStatusParser.js.map +1 -0
- package/dist/core/types.d.ts +56 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/RopeHealthMonitor.d.ts +243 -0
- package/dist/monitoring/RopeHealthMonitor.d.ts.map +1 -0
- package/dist/monitoring/RopeHealthMonitor.js +659 -0
- package/dist/monitoring/RopeHealthMonitor.js.map +1 -0
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +45 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +9 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +2 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -1
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +19 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lint-scrape-fixture-realness.js +20 -0
- package/src/data/builtin-manifest.json +65 -65
- package/src/data/state-coherence-registry.json +17 -0
- package/src/scaffold/templates/jobs/instar/rope-health-digest.md +42 -0
- package/src/scaffold/templates.ts +9 -0
- package/upgrades/1.3.710.md +22 -0
- package/upgrades/1.3.711.md +79 -0
- package/upgrades/side-effects/tpo-breaker-flake-fix.md +16 -0
- package/upgrades/side-effects/u4-rope-probe-alerts.md +140 -0
- package/upgrades/tpo-breaker-flake-fix.eli16.md +11 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ropeProbeContract — the U4.3 recovery probe's payload + typed-response contract
|
|
3
|
+
* (docs/specs/u4-3-breaker-recovery-probe.md §2 "Probe success is the exact typed
|
|
4
|
+
* contract, never any-2xx").
|
|
5
|
+
*
|
|
6
|
+
* The probe reuses the G4 delivery-canary PAYLOAD contract: a signed, bogus-uid
|
|
7
|
+
* `deliverMessage` MeshRpc command the peer answers with a TYPED refusal per its
|
|
8
|
+
* role — `not-router` (403, RBAC: we are not the lease holder in the peer's view)
|
|
9
|
+
* or `sender-rejected` (200 ack, the peer's sender re-validation refused the
|
|
10
|
+
* bogus uid). Either typed refusal PROVES the whole path: transport connect +
|
|
11
|
+
* signed envelope verified + the peer's real dispatcher answered. A malformed,
|
|
12
|
+
* untyped, or unsigned 2xx (captive portal, wrong server) is a FAILURE.
|
|
13
|
+
*
|
|
14
|
+
* `parseProbeResponse` is the REGISTERED parser (Scrape/Parser Fixture Realness):
|
|
15
|
+
* it consumes the RAW response body bytes + HTTP status, and its tests feed it
|
|
16
|
+
* captured byte-for-byte fixtures of real /mesh/rpc responses (see
|
|
17
|
+
* tests/fixtures/captured/mesh-rpc-probe-responses/ + SCRAPE_PARSERS in
|
|
18
|
+
* scripts/lint-scrape-fixture-realness.js).
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* A deliberately-unresolvable sender uid (no Telegram uid is ever this value and
|
|
22
|
+
* the users registry can never contain it) — the "bogus uid" of the G4 canary
|
|
23
|
+
* payload contract. The peer's sender re-validation refuses it with the typed
|
|
24
|
+
* `sender-rejected` ack; it can never inject a real message.
|
|
25
|
+
*/
|
|
26
|
+
export const ROPE_PROBE_BOGUS_UID = 999_999_999_999;
|
|
27
|
+
/** The session-key prefix — non-numeric BY DESIGN so no topic-shaped consumer
|
|
28
|
+
* (working-set trigger, spawn bridge — both `Number(session)`-gated) can ever
|
|
29
|
+
* act on a probe, and the SenderRejectionNoticer's user-topic exclusion holds. */
|
|
30
|
+
export const ROPE_PROBE_SESSION_PREFIX = 'rope-probe:';
|
|
31
|
+
/**
|
|
32
|
+
* Build the probe command. `ownershipEpoch: 0` + an unknown session means the
|
|
33
|
+
* stale-ownership fence passes through to sender re-validation (ownerEpochOf on
|
|
34
|
+
* an unknown session is null), so the answer is deterministic per role.
|
|
35
|
+
*/
|
|
36
|
+
export function buildRopeProbeCommand(selfMachineId, nonce) {
|
|
37
|
+
return {
|
|
38
|
+
type: 'deliverMessage',
|
|
39
|
+
session: `${ROPE_PROBE_SESSION_PREFIX}${selfMachineId}`,
|
|
40
|
+
messageId: `rope-probe:${selfMachineId}:${nonce}`,
|
|
41
|
+
payload: { probe: 'rope-recovery', note: 'signed bogus-uid canary — expect a typed refusal' },
|
|
42
|
+
ownershipEpoch: 0,
|
|
43
|
+
// The §3.4 sender re-validation envelope — the bogus uid the peer refuses.
|
|
44
|
+
senderEnvelope: { userId: ROPE_PROBE_BOGUS_UID },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Classify a raw /mesh/rpc probe response (REGISTERED parser — fed captured
|
|
49
|
+
* byte-for-byte fixtures in tests). Success is the EXACT typed contract:
|
|
50
|
+
* - 403 with dispatcher reason `not-router` (peer refused our non-router send), OR
|
|
51
|
+
* - 200 ack `{ result: { accepted: 'sender-rejected' } }` (peer's sender
|
|
52
|
+
* re-validation refused the bogus uid).
|
|
53
|
+
* EVERYTHING else — an untyped 2xx, a malformed body, an unsigned/unknown-sender
|
|
54
|
+
* rejection, or an unexpected acceptance — records as FAILURE.
|
|
55
|
+
*/
|
|
56
|
+
export function parseProbeResponse(rawBody, status) {
|
|
57
|
+
let body;
|
|
58
|
+
try {
|
|
59
|
+
body = JSON.parse(rawBody);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// @silent-fallback-ok: an unparseable probe response IS the classification
|
|
63
|
+
// (malformed ⇒ failure) — the verdict carries it; nothing is swallowed.
|
|
64
|
+
return { typedSuccess: false, classification: 'malformed' };
|
|
65
|
+
}
|
|
66
|
+
const obj = (body && typeof body === 'object' ? body : {});
|
|
67
|
+
if (status === 200) {
|
|
68
|
+
const accepted = obj.result && typeof obj.result === 'object' ? obj.result.accepted : undefined;
|
|
69
|
+
if (obj.ok === true && accepted === 'sender-rejected') {
|
|
70
|
+
return { typedSuccess: true, classification: 'ack-sender-rejected', detail: 'sender-rejected' };
|
|
71
|
+
}
|
|
72
|
+
if (obj.ok === true && typeof accepted === 'string') {
|
|
73
|
+
// A typed ack that ACCEPTED the bogus probe (queued/duplicate/stale-ownership)
|
|
74
|
+
// is NOT the contract — e.g. a peer whose degenerate registry failed toward
|
|
75
|
+
// delivery. Recorded as failure (the strict any-2xx-never-closes rule).
|
|
76
|
+
return { typedSuccess: false, classification: 'accepted-not-refused', detail: accepted };
|
|
77
|
+
}
|
|
78
|
+
return { typedSuccess: false, classification: 'untyped-2xx' };
|
|
79
|
+
}
|
|
80
|
+
if (status >= 200 && status < 300) {
|
|
81
|
+
return { typedSuccess: false, classification: 'untyped-2xx' };
|
|
82
|
+
}
|
|
83
|
+
const reason = typeof obj.reason === 'string' ? obj.reason : undefined;
|
|
84
|
+
if (status === 403 && reason === 'not-router') {
|
|
85
|
+
return { typedSuccess: true, classification: 'refused-not-router', detail: reason };
|
|
86
|
+
}
|
|
87
|
+
if (status === 401) {
|
|
88
|
+
return { typedSuccess: false, classification: 'auth-rejected', detail: reason };
|
|
89
|
+
}
|
|
90
|
+
return { typedSuccess: false, classification: 'http-error', detail: reason ?? `status-${status}` };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=ropeProbeContract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ropeProbeContract.js","sourceRoot":"","sources":["../../src/core/ropeProbeContract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAEpD;;mFAEmF;AACnF,MAAM,CAAC,MAAM,yBAAyB,GAAG,aAAa,CAAC;AAEvD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,aAAqB,EAAE,KAAa;IACxE,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,GAAG,yBAAyB,GAAG,aAAa,EAAE;QACvD,SAAS,EAAE,cAAc,aAAa,IAAI,KAAK,EAAE;QACjD,OAAO,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,kDAAkD,EAAE;QAC7F,cAAc,EAAE,CAAC;QACjB,2EAA2E;QAC3E,cAAc,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE;KAClC,CAAC;AACnB,CAAC;AAmBD;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,MAAc;IAChE,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,wEAAwE;QACxE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;IAC9D,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAIxD,CAAC;IAEF,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QAChG,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI,IAAI,QAAQ,KAAK,iBAAiB,EAAE,CAAC;YACtD,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;QAClG,CAAC;QACD,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACpD,+EAA+E;YAC/E,4EAA4E;YAC5E,wEAAwE;YACxE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAC3F,CAAC;QACD,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;IAChE,CAAC;IAED,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;QAClC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;IAChE,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACvE,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;QAC9C,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACtF,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAClF,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,IAAI,UAAU,MAAM,EAAE,EAAE,CAAC;AACrG,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tailscaleStatusParser — U4.5's SECOND declared data source (R-r2-3): the
|
|
3
|
+
* Tailscale key-expiry tier reads a bounded exec of `tailscale status --json`
|
|
4
|
+
* (docs/specs/u4-5-rope-health-alerts.md §2). Key expiry is NOT in the U4.3
|
|
5
|
+
* resolver snapshot (the resolver never sees it), so it has its OWN parser.
|
|
6
|
+
*
|
|
7
|
+
* CONTENT-SCRUB BOUNDARY (spec §2 "Content scrub is a hard rule"): the raw
|
|
8
|
+
* tailscale JSON carries IPs, DNS names, tailnet names, account emails and
|
|
9
|
+
* public keys. NONE of that leaves this parser — the return shape carries key
|
|
10
|
+
* EXPIRY TIMES and role labels ONLY. Alert/digest text downstream is composed
|
|
11
|
+
* exclusively from this scrubbed shape.
|
|
12
|
+
*
|
|
13
|
+
* `parseTailscaleStatus` is a REGISTERED parser (Scrape/Parser Fixture
|
|
14
|
+
* Realness): its tests feed it captured byte-for-byte fixtures of real
|
|
15
|
+
* `tailscale status --json` output (tests/fixtures/captured/tailscale-status/
|
|
16
|
+
* + SCRAPE_PARSERS in scripts/lint-scrape-fixture-realness.js).
|
|
17
|
+
*/
|
|
18
|
+
/** One scrubbed key-expiry entry. `role` is the ONLY identity that survives. */
|
|
19
|
+
export interface TailscaleKeyExpiryEntry {
|
|
20
|
+
role: 'self' | 'peer';
|
|
21
|
+
/** ISO-8601 expiry, or null when the node has no expiring key (e.g. expiry disabled). */
|
|
22
|
+
keyExpiryIso: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface TailscaleStatusParse {
|
|
25
|
+
/** False ⇒ the body did not parse as the expected tailscale status JSON. */
|
|
26
|
+
parsed: boolean;
|
|
27
|
+
/** Scrubbed entries (self first, then peers in object order). Empty when !parsed. */
|
|
28
|
+
entries: TailscaleKeyExpiryEntry[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse raw `tailscale status --json` stdout into the scrubbed key-expiry
|
|
32
|
+
* shape. Tolerant of absent fields (a node without KeyExpiry yields null);
|
|
33
|
+
* a malformed body yields `{ parsed: false, entries: [] }` — the expiry tier
|
|
34
|
+
* is then silently absent for that pass (never an error state, spec R-r2-3).
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseTailscaleStatus(rawBody: string): TailscaleStatusParse;
|
|
37
|
+
/**
|
|
38
|
+
* The days-until-soonest-expiry summary the monitor's degraded tier consumes.
|
|
39
|
+
* Returns null when no entry carries an expiry.
|
|
40
|
+
*/
|
|
41
|
+
export declare function soonestKeyExpiry(parse: TailscaleStatusParse, nowMs: number): {
|
|
42
|
+
role: 'self' | 'peer';
|
|
43
|
+
expiresAtIso: string;
|
|
44
|
+
inDays: number;
|
|
45
|
+
} | null;
|
|
46
|
+
//# sourceMappingURL=tailscaleStatusParser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailscaleStatusParser.d.ts","sourceRoot":"","sources":["../../src/core/tailscaleStatusParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,gFAAgF;AAChF,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,yFAAyF;IACzF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,MAAM,EAAE,OAAO,CAAC;IAChB,qFAAqF;IACrF,OAAO,EAAE,uBAAuB,EAAE,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAiC1E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,oBAAoB,EAC3B,KAAK,EAAE,MAAM,GACZ;IAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAYxE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tailscaleStatusParser — U4.5's SECOND declared data source (R-r2-3): the
|
|
3
|
+
* Tailscale key-expiry tier reads a bounded exec of `tailscale status --json`
|
|
4
|
+
* (docs/specs/u4-5-rope-health-alerts.md §2). Key expiry is NOT in the U4.3
|
|
5
|
+
* resolver snapshot (the resolver never sees it), so it has its OWN parser.
|
|
6
|
+
*
|
|
7
|
+
* CONTENT-SCRUB BOUNDARY (spec §2 "Content scrub is a hard rule"): the raw
|
|
8
|
+
* tailscale JSON carries IPs, DNS names, tailnet names, account emails and
|
|
9
|
+
* public keys. NONE of that leaves this parser — the return shape carries key
|
|
10
|
+
* EXPIRY TIMES and role labels ONLY. Alert/digest text downstream is composed
|
|
11
|
+
* exclusively from this scrubbed shape.
|
|
12
|
+
*
|
|
13
|
+
* `parseTailscaleStatus` is a REGISTERED parser (Scrape/Parser Fixture
|
|
14
|
+
* Realness): its tests feed it captured byte-for-byte fixtures of real
|
|
15
|
+
* `tailscale status --json` output (tests/fixtures/captured/tailscale-status/
|
|
16
|
+
* + SCRAPE_PARSERS in scripts/lint-scrape-fixture-realness.js).
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Parse raw `tailscale status --json` stdout into the scrubbed key-expiry
|
|
20
|
+
* shape. Tolerant of absent fields (a node without KeyExpiry yields null);
|
|
21
|
+
* a malformed body yields `{ parsed: false, entries: [] }` — the expiry tier
|
|
22
|
+
* is then silently absent for that pass (never an error state, spec R-r2-3).
|
|
23
|
+
*/
|
|
24
|
+
export function parseTailscaleStatus(rawBody) {
|
|
25
|
+
let body;
|
|
26
|
+
try {
|
|
27
|
+
body = JSON.parse(rawBody);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// @silent-fallback-ok: an unparseable status IS the classification (the
|
|
31
|
+
// expiry tier is absent this pass) — the verdict carries it; nothing hidden.
|
|
32
|
+
return { parsed: false, entries: [] };
|
|
33
|
+
}
|
|
34
|
+
if (!body || typeof body !== 'object')
|
|
35
|
+
return { parsed: false, entries: [] };
|
|
36
|
+
const obj = body;
|
|
37
|
+
// Minimal shape check: real tailscale status JSON always carries BackendState.
|
|
38
|
+
// A captive portal / wrong-command JSON body without it is NOT the contract.
|
|
39
|
+
if (typeof obj.BackendState !== 'string')
|
|
40
|
+
return { parsed: false, entries: [] };
|
|
41
|
+
const entries = [];
|
|
42
|
+
const expiryOf = (node) => {
|
|
43
|
+
if (!node || typeof node !== 'object')
|
|
44
|
+
return null;
|
|
45
|
+
const raw = node.KeyExpiry;
|
|
46
|
+
if (typeof raw !== 'string')
|
|
47
|
+
return null;
|
|
48
|
+
const t = Date.parse(raw);
|
|
49
|
+
return Number.isFinite(t) ? new Date(t).toISOString() : null;
|
|
50
|
+
};
|
|
51
|
+
if (obj.Self && typeof obj.Self === 'object') {
|
|
52
|
+
entries.push({ role: 'self', keyExpiryIso: expiryOf(obj.Self) });
|
|
53
|
+
}
|
|
54
|
+
if (obj.Peer && typeof obj.Peer === 'object') {
|
|
55
|
+
for (const peer of Object.values(obj.Peer)) {
|
|
56
|
+
entries.push({ role: 'peer', keyExpiryIso: expiryOf(peer) });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { parsed: true, entries };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The days-until-soonest-expiry summary the monitor's degraded tier consumes.
|
|
63
|
+
* Returns null when no entry carries an expiry.
|
|
64
|
+
*/
|
|
65
|
+
export function soonestKeyExpiry(parse, nowMs) {
|
|
66
|
+
let best = null;
|
|
67
|
+
for (const e of parse.entries) {
|
|
68
|
+
if (!e.keyExpiryIso)
|
|
69
|
+
continue;
|
|
70
|
+
const t = Date.parse(e.keyExpiryIso);
|
|
71
|
+
if (!Number.isFinite(t))
|
|
72
|
+
continue;
|
|
73
|
+
const inDays = (t - nowMs) / 86_400_000;
|
|
74
|
+
if (!best || inDays < best.inDays) {
|
|
75
|
+
best = { role: e.role, expiresAtIso: e.keyExpiryIso, inDays };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return best;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=tailscaleStatusParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailscaleStatusParser.js","sourceRoot":"","sources":["../../src/core/tailscaleStatusParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAgBH;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,6EAA6E;QAC7E,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACxC,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC7E,MAAM,GAAG,GAAG,IAAkE,CAAC;IAC/E,+EAA+E;IAC/E,6EAA6E;IAC7E,IAAI,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAEhF,MAAM,OAAO,GAA8B,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAG,CAAC,IAAa,EAAiB,EAAE;QAChD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACnD,MAAM,GAAG,GAAI,IAAgC,CAAC,SAAS,CAAC;QACxD,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/D,CAAC,CAAC;IAEF,IAAI,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAA+B,CAAC,EAAE,CAAC;YACtE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAA2B,EAC3B,KAAa;IAEb,IAAI,IAAI,GAA2E,IAAI,CAAC;IACxF,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC,CAAC,CAAC,YAAY;YAAE,SAAS;QAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAS;QAClC,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,UAAU,CAAC;QACxC,IAAI,CAAC,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;QAChE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -2092,6 +2092,25 @@ export interface MeshTransportConfig {
|
|
|
2092
2092
|
maxProbeBackoffMs?: number;
|
|
2093
2093
|
/** Bind host override (Decision 17). Default: '0.0.0.0' when enabled else '127.0.0.1'. */
|
|
2094
2094
|
bindHost?: string;
|
|
2095
|
+
/**
|
|
2096
|
+
* Recovery-probe master switch. DELIBERATELY OMITTED from ConfigDefaults so the
|
|
2097
|
+
* developmentAgent gate resolves it (live-on-dev in dry-run day one, dark on
|
|
2098
|
+
* the fleet) — see DEV_GATED_FEATURES `ropeRecoveryProbe`.
|
|
2099
|
+
*/
|
|
2100
|
+
recoveryProbeEnabled?: boolean;
|
|
2101
|
+
/** Dry-run SENDS real probes (harmless typed-refusal contract) and logs
|
|
2102
|
+
* would-close verdicts but never mutates the HealthRecord. Default true. */
|
|
2103
|
+
recoveryProbeDryRun?: boolean;
|
|
2104
|
+
/** P19 floor cadence for a permanently-dead / slow-but-alive rope. Default 900000 (15 min). */
|
|
2105
|
+
recoveryProbeFloorMs?: number;
|
|
2106
|
+
/** Consecutive probe failures before the exhaustion transition + escalate-once. Default 20. */
|
|
2107
|
+
recoveryProbeExhaustAttempts?: number;
|
|
2108
|
+
/** A probe-close→re-death inside this window counts as a probe failure for backoff. Default 600000. */
|
|
2109
|
+
recoveryProbeReopenEpisodeWindowMs?: number;
|
|
2110
|
+
/** Mid-recovery episode cadence (never the resolver's trivially-true ~5s). Default 45000. */
|
|
2111
|
+
recoveryProbeMidIntervalMs?: number;
|
|
2112
|
+
/** Consecutive successful probes WITHOUT lastKnownGood reclaim before floor cadence. Default 20. */
|
|
2113
|
+
recoveryProbeMaxUnreclaimedSuccesses?: number;
|
|
2095
2114
|
}
|
|
2096
2115
|
export interface MultiMachineConfig {
|
|
2097
2116
|
/** Whether multi-machine is enabled */
|
|
@@ -4288,6 +4307,43 @@ export interface MonitoringConfig {
|
|
|
4288
4307
|
/** Self-heal line on a live respawn (default true). */
|
|
4289
4308
|
notifyUser?: boolean;
|
|
4290
4309
|
};
|
|
4310
|
+
/**
|
|
4311
|
+
* U4.5 — Rope-Health Alerts (docs/specs/u4-5-rope-health-alerts.md). The
|
|
4312
|
+
* in-server RopeHealthMonitor: its own bounded 30s evaluation loop over the
|
|
4313
|
+
* U4.3 PeerEndpointResolver.snapshot() seam, deterministic sleep-aware
|
|
4314
|
+
* classification (ok / degraded / peer-offline / urgent), episode-deduped
|
|
4315
|
+
* HIGH partition alerts, Tailscale key-expiry warnings, and the
|
|
4316
|
+
* GET /mesh/rope-health read surface the rope-health-digest job consumes.
|
|
4317
|
+
*
|
|
4318
|
+
* `enabled` is DELIBERATELY OPTIONAL (dev-gate convention): omitted from
|
|
4319
|
+
* ConfigDefaults so resolveDevAgentGate decides — LIVE on a development
|
|
4320
|
+
* agent day one, DARK on the fleet (the route 503s, no timer exists).
|
|
4321
|
+
*/
|
|
4322
|
+
ropeHealth?: {
|
|
4323
|
+
/** Dev-gate convention: omit to let resolveDevAgentGate decide. */
|
|
4324
|
+
enabled?: boolean;
|
|
4325
|
+
/** Urgent (HIGH-alert) tier master — rides the same dev gate (default: true). */
|
|
4326
|
+
urgentEnabled?: boolean;
|
|
4327
|
+
/** Time-pinned flap filter on the all-down condition (default: 60000). */
|
|
4328
|
+
urgentDebounceMs?: number;
|
|
4329
|
+
/** Continuous health required before an episode ends (default: 600000). */
|
|
4330
|
+
clearSustainMs?: number;
|
|
4331
|
+
/** Tailscale key expiry warning horizon in days (default: 14). */
|
|
4332
|
+
keyExpiryWarnDays?: number;
|
|
4333
|
+
/**
|
|
4334
|
+
* Telegram topic id the rope-health-digest job delivers to (mirrors the
|
|
4335
|
+
* burnDetection.alertTopicId precedent — R-r2-8). Default UNSET: the digest
|
|
4336
|
+
* job LOGS only; the operator sets their hub topic id to get delivery.
|
|
4337
|
+
*/
|
|
4338
|
+
digestTopicId?: number;
|
|
4339
|
+
/**
|
|
4340
|
+
* HARD CAP on self-wake urgent suppression (default: 300000). Bounded per
|
|
4341
|
+
* docs/audits/multi-machine-seamless-ux-audit-2026-07.md finding P1-A7:
|
|
4342
|
+
* SleepWakeDetector can emit FALSE wake events (event-loop stalls misread
|
|
4343
|
+
* as sleeps), so a "recently slept" signal is a short grace, never a veto.
|
|
4344
|
+
*/
|
|
4345
|
+
wakeGraceMaxMs?: number;
|
|
4346
|
+
};
|
|
4291
4347
|
/**
|
|
4292
4348
|
* AutonomousProgressHeartbeat — a hedged, change-gated, sparse liveness
|
|
4293
4349
|
* BACKSTOP that posts ONE purely-observational line when an autonomous run
|