EQL 语法参考

edit

基本语法

edit

EQL 查询需要一个事件类别和一个匹配条件。where 关键字将它们连接起来。

event_category where condition

事件类别是事件类别字段的索引值。默认情况下,EQL搜索API使用来自Elastic Common Schema (ECS)event.category字段。您可以使用API的event_category_field参数指定另一个事件类别字段。

例如,以下 EQL 查询匹配事件类别为 processprocess.namesvchost.exe 的事件:

process where process.name == "svchost.exe"

匹配任意事件类别

edit

要匹配任何类别的事件,请使用any关键字。您还可以使用any关键字来搜索没有事件类别字段的文档。

例如,以下 EQL 查询匹配任何具有 network.protocol 字段值为 http 的文档:

any where network.protocol == "http"

转义事件类别

edit

使用封闭的双引号(")或三个封闭的双引号(""")来转义事件类别,这些类别:

  • 包含特殊字符,例如连字符(-)或点(.
  • 包含空格
  • 以数字开头
".my.event.category"
"my-event-category"
"my event category"
"6eventcategory"

""".my.event.category"""
"""my-event-category"""
"""my event category"""
"""6eventcategory"""

转义字段名称

edit

使用反引号(`)来转义以下字段名称:

  • 包含一个连字符 (-)
  • 包含一个空格
  • 以数字开头
`my-field`
`my field`
`6myfield`

使用双反引号 (``) 来转义字段名称中的任何反引号 (`)。

my`field -> `my``field`

条件

edit

条件由一个或多个事件必须匹配的标准组成。 您可以使用以下运算符指定和组合这些标准。大多数 EQL 运算符默认情况下区分大小写。

比较运算符

edit
<   <=   ==   :   !=   >=   >
< (less than)
如果运算符左侧的值小于右侧的值,则返回true。否则返回false
<= (less than or equal)
如果运算符左侧的值小于或等于右侧的值,则返回 true。否则返回 false
== (equal, case-sensitive)
返回true如果运算符左右两边的值相等。 否则返回false。不支持通配符。
: (equal, case-insensitive)
如果运算符左右两侧的字符串相等,则返回true。否则返回false。只能用于比较字符串。支持通配符列表查找
!= (not equal, case-sensitive)
如果运算符左右两侧的值不相等,则返回 true。否则返回 false。不支持通配符。
>= (greater than or equal)
如果运算符左侧的值大于或等于右侧的值,则返回true。否则返回false。在比较字符串时,运算符使用区分大小写的字典顺序。
> (greater than)
返回true如果运算符左侧的值大于右侧的值。否则返回false。当比较字符串时,运算符使用区分大小写的字典顺序。

= 不支持作为等号运算符。请使用 ==: 代替。

模式比较关键词

edit
my_field like  "VALUE*"         // case-sensitive wildcard matching
my_field like~ "value*"         // case-insensitive wildcard matching

my_field regex  "VALUE[^Z].?"   // case-sensitive regex matching
my_field regex~ "value[^z].?"   // case-insensitive regex matching
like (case-sensitive)
如果关键字左侧的字符串与右侧的 通配符模式匹配,则返回true。支持 列表查找。只能用于比较 字符串。对于不区分大小写的匹配,请使用like~
regex (case-sensitive)
如果关键字左侧的字符串与右侧的正则表达式匹配,则返回true。有关支持的正则表达式语法,请参阅 正则表达式语法。支持列表查找。只能用于比较字符串。对于不区分大小写的匹配,请使用regex~
比较的局限性
edit

你不能链接比较。相反,在比较之间使用逻辑运算符。例如,foo < bar <= baz 是不支持的。然而,你可以将表达式重写为 foo < bar and bar <= baz,这是支持的。

您也不能将一个字段与另一个字段进行比较,即使这些字段使用了函数进行了更改。

示例
以下EQL查询将process.parent_name字段值与静态值foo进行比较。此比较是受支持的。

然而,查询还将process.parent.name字段值与process.name字段进行比较。这种比较是不被支持的,并且会导致整个查询返回错误。

process where process.parent.name == "foo" and process.parent.name == process.name

相反,您可以重写查询,将 process.parent.nameprocess.name 字段与静态值进行比较。

process where process.parent.name == "foo" and process.name == "foo"

逻辑运算符

edit
and  or  not
and
仅当左侧和右侧的条件返回true时,才返回true。 否则返回false
or
返回true,如果左侧或右侧的条件之一为true。 否则返回false
not
返回true如果右侧的条件是false

查找操作符

edit
my_field in ("Value-1", "VALUE2", "VAL3")                 // case-sensitive
my_field in~ ("value-1", "value2", "val3")                // case-insensitive

my_field not in ("Value-1", "VALUE2", "VAL3")             // case-sensitive
my_field not in~ ("value-1", "value2", "val3")            // case-insensitive

my_field : ("value-1", "value2", "val3")                  // case-insensitive

my_field like  ("Value-*", "VALUE2", "VAL?")              // case-sensitive
my_field like~ ("value-*", "value2", "val?")              // case-insensitive

my_field regex  ("[vV]alue-[0-9]", "VALUE[^2].?", "VAL3") // case-sensitive
my_field regex~  ("value-[0-9]", "value[^2].?", "val3")   // case-insensitive
in (case-sensitive)
如果值包含在提供的列表中,则返回true。对于不区分大小写的匹配,请使用in~
not in (case-sensitive)
如果值不在提供的列表中,则返回true。对于不区分大小写的匹配,请使用not in~
: (case-insensitive)
如果字符串包含在提供的列表中,则返回true。只能用于比较字符串。
like (case-sensitive)
如果字符串与提供的列表中的通配符模式匹配,则返回true。只能用于比较字符串。对于不区分大小写的匹配,请使用like~
regex (case-sensitive)
如果字符串与提供的列表中的正则表达式模式匹配,则返回true。有关支持的正则表达式语法,请参阅正则表达式语法。 只能用于比较字符串。对于不区分大小写的匹配,请使用regex~

数学运算符

edit
+  -  *  /  %
+ (add)
将运算符左右两边的值相加。
- (subtract)
从左边的值中减去右边的值。
* (multiply)
将运算符左右两边的值相乘。
/ (divide)

将运算符左侧的值除以右侧的值。

如果被除数和除数都是整数,除法(\)操作会将返回的任何浮点数向下取整到最接近的整数。为了避免取整,将被除数或除数转换为浮点数。

示例
process.args_count 字段是一个 long 整数字段,包含进程参数的计数。

用户可能期望以下EQL查询仅匹配具有process.args_count值为4的事件。

process where ( 4 / process.args_count ) == 1

然而,EQL 查询匹配事件的 process.args_count 值为 34

对于具有 process.args_count 值为 3 的事件,除法操作返回一个浮点数 1.333...,该值被向下舍入为 1

要匹配仅具有 process.args_count 值为 4 的事件,请将除数或被除数转换为浮点数。

以下EQL查询将整数4转换为等效的浮点数4.0

process where ( 4.0 / process.args_count ) == 1
% (modulo)
将运算符左侧的值除以右侧的值。仅返回余数。

匹配任意条件

edit

要仅根据事件类别匹配事件,请使用 where true 条件。

例如,以下 EQL 查询匹配任何 文件 事件:

file where true

要匹配任何事件,您可以将 any 关键字与 where true 条件结合使用:

any where true

可选字段

edit

默认情况下,EQL查询只能包含存在于您正在搜索的数据集中的字段。如果一个字段具有显式动态运行时映射,则该字段存在于数据集中。如果EQL查询包含一个不存在的字段,它将返回一个错误。

如果你不确定一个字段是否存在于数据集中,使用?操作符将该字段标记为可选。如果一个可选字段不存在,查询会将其替换为null而不是返回错误。

示例
在以下查询中,user.id 字段是可选的。

network where ?user.id != null

如果在您搜索的数据集中存在user.id字段,则查询匹配包含user.id值的任何network事件。如果数据集中不存在user.id字段,EQL会将查询解释为:

network where null != null

在这种情况下,查询没有匹配到任何事件。

检查字段是否存在

edit

要匹配包含某个字段任何值的事件,请使用 != 运算符将该字段与 null 进行比较:

?my_field != null

要匹配不包含字段值的事件,请使用 == 运算符将字段与 null 进行比较:

?my_field == null

字符串

edit

字符串用双引号(")括起来。

"hello world"

不支持用单引号 (') 括起来的字符串。

字符串中的转义字符

edit

当在字符串中使用时,特殊字符,如回车或双引号("),必须用前置的反斜杠(\)进行转义。

"example \r of \" escaped \n characters"
Escape sequence Literal character

\n

换行符(换行)

\r

回车

\t

标签

\\

反斜杠 (\)

\"

双引号 (")

您可以使用十六进制 \u{XXXXXXXX} 转义序列来转义 Unicode 字符。十六进制值可以是 2-8 个字符,并且不区分大小写。 短于 8 个字符的值会用零填充。您可以使用这些转义序列在字符串中包含不可打印或从右到左 (RTL) 的字符。例如,您可以将 从右到左标记 (RLM) 转义为 \u{200f}\u{200F}\u{0000200f}

单引号 (') 字符保留供将来使用。您不能在字面字符串中使用转义的单引号 (\')。请改用转义的双引号 (\")。

原始字符串

edit

原始字符串将特殊字符(如反斜杠 \)视为字面字符。原始字符串用三个双引号(""")括起来。

"""Raw string with a literal double quote " and blackslash \ included"""

原始字符串不能包含三个连续的双引号(""")。相反,请使用带有 \" 转义序列的常规字符串。

"String containing \"\"\" three double quotes"

通配符

edit

对于使用 : 运算符或 like 关键字进行字符串比较时,您可以使用 *? 通配符来匹配特定模式。* 通配符匹配零个或多个字符:

my_field : "doc*"     // Matches "doc", "docs", or "document" but not "DOS"
my_field : "*doc"     // Matches "adoc" or "asciidoc"
my_field : "d*c"      // Matches "doc" or "disc"

my_field like "DOC*"  // Matches "DOC", "DOCS", "DOCs", or "DOCUMENT" but not "DOS"
my_field like "D*C"   // Matches "DOC", "DISC", or "DisC"

通配符 ? 匹配恰好一个字符:

my_field : "doc?"     // Matches "docs" but not "doc", "document", or "DOS"
my_field : "?doc"     // Matches "adoc" but not "asciidoc"
my_field : "d?c"      // Matches "doc" but not "disc"

my_field like "DOC?"  // Matches "DOCS" or "DOCs" but not "DOC", "DOCUMENT", or "DOS"
my_field like "D?c"   // Matches "DOC" but not "DISC"

运算符 : 和关键字 like 也支持在 列表查找中使用通配符:

my_field : ("doc*", "f*o", "ba?", "qux")
my_field like ("Doc*", "F*O", "BA?", "QUX")

序列

edit

您可以使用EQL序列来描述和匹配一系列有序的事件。 序列中的每个项目都是一个事件类别和事件条件, 用方括号([ ])括起来。事件按时间顺序升序列出,最近的事件列在最后。

sequence
  [ event_category_1 where condition_1 ]
  [ event_category_2 where condition_2 ]
  ...

示例
以下 EQL 序列查询匹配这一系列有序事件:

  1. 从以下事件开始:

    • 事件类别为 file
    • file.extensionexe
  2. 随后是一个事件类别为 process 的事件
sequence
  [ file where file.extension == "exe" ]
  [ process where true ]

with maxspan 语句

edit

您可以使用 with maxspan 将序列限制在指定的时间跨度内。匹配序列中的所有事件必须在此持续时间内发生,从第一个事件的时间戳开始。

maxspan 接受 时间值 参数。

sequence with maxspan=30s
  [ event_category_1 where condition_1 ] by field_baz
  [ event_category_2 where condition_2 ] by field_bar
  ...

示例
以下序列查询使用了一个maxspan值为15m(15分钟)。 匹配序列中的事件必须在第一个事件的时间戳后的15分钟内发生。

sequence with maxspan=15m
  [ file where file.extension == "exe" ]
  [ process where true ]

缺失事件

edit

使用 ! 来匹配缺失的事件:在时间跨度约束的序列中,不符合给定条件的事件。

sequence with maxspan=1h
  [ event_category_1 where condition_1 ]
  ![ event_category_2 where condition_2 ]
  [ event_category_3 where condition_3 ]
  ...

缺失事件子句可以用于序列的开头、结尾和/或中间,可以与正事件子句以任何组合方式使用。一个序列可以有多个缺失事件子句,但至少需要有一个正子句。with maxspan 在存在缺失事件子句时是强制性的。

示例
以下序列查询查找在5秒内没有紧接着注销事件的登录事件。

sequence by host.name, user.name with maxspan=5s
  [ authentication where event.code : "4624" ]
  ![ authentication where event.code : "4647" ]

by 关键字

edit

在序列查询中使用 by 关键字来仅匹配共享相同值的事件,即使这些值位于不同的字段中。这些共享的值称为连接键。如果连接键应在所有事件的同一字段中,请使用 sequence by

sequence by field_foo
  [ event_category_1 where condition_1 ] by field_baz
  [ event_category_2 where condition_2 ] by field_bar
  ...

示例
以下序列查询使用 by 关键字来约束匹配事件为:

  • 具有相同user.name值的事件
  • file事件,其file.path值等于以下process事件的process.executable值。
sequence
  [ file where file.extension == "exe" ] by user.name, file.path
  [ process where true ] by user.name, process.executable

因为 user.name 字段在整个序列的所有事件中是共享的,所以可以使用 sequence by 来包含它。以下序列与之前的序列是等价的。

sequence by user.name
  [ file where file.extension == "exe" ] by file.path
  [ process where true ] by process.executable

您可以将 sequence bywith maxspan 结合起来,通过字段值和时间跨度来约束一个序列。

sequence by field_foo with maxspan=30s
  [ event_category_1 where condition_1 ]
  [ event_category_2 where condition_2 ]
  ...

示例
以下序列查询使用 sequence bywith maxspan 来仅匹配满足以下条件的序列事件:

  • 共享相同的user.name字段值
  • 在第一个匹配事件的15m(15分钟)内发生
sequence by user.name with maxspan=15m
  [ file where file.extension == "exe" ]
  [ process where true ]

可选的 by 字段

edit

默认情况下,连接键必须是一个非null的字段值。要允许null连接键,请使用?操作符将by字段标记为可选。如果你不确定正在搜索的数据集是否包含by字段,这也是有帮助的。

示例
以下序列查询使用 sequence by 来约束匹配事件为:

  • 具有相同process.pid值的事件,不包括null值。如果在您搜索的数据集中不存在process.pid字段,查询将返回错误。
  • 具有相同process.entity_id值的事件,包括null值。如果事件不包含process.entity_id字段,则其process.entity_id值被视为null。即使您搜索的数据集中不存在process.pid字段,这也适用。
sequence by process.pid, ?process.entity_id
  [process where process.name == "regsvr32.exe"]
  [network where true]

直到 关键字

edit

您可以使用 until 关键字来指定序列的过期事件。 如果此过期事件在序列中的匹配事件之间发生,则序列过期且不被视为匹配。如果过期事件在序列中的匹配事件之后发生,则序列仍被视为匹配。过期事件不包含在结果中。

sequence
  [ event_category_1 where condition_1 ]
  [ event_category_2 where condition_2 ]
  ...
until [ event_category_3 where condition_3 ]

示例
一个数据集包含以下按共享ID分组的事件序列:

A, B
A, B, C
A, C, B

以下EQL查询在数据集中搜索包含事件A后跟事件B的序列。事件C用作过期事件。

sequence by ID
  A
  B
until C

查询匹配序列 A, BA, B, C 但不匹配 A, C, B

在搜索Windows事件日志中的进程序列时,until 关键字非常有用。

在Windows中,进程ID(PID)仅在进程运行时是唯一的。进程终止后,其PID可以被重新使用。

您可以使用 bysequence by 关键字搜索具有相同PID值的事件序列。

示例
以下EQL查询使用sequence by关键字来匹配共享相同process.pid值的一系列事件。

sequence by process.pid
  [ process where event.type == "start" and process.name == "cmd.exe" ]
  [ process where file.extension == "exe" ]

然而,由于PID重用,这可能导致匹配序列包含来自不相关进程的事件。为了防止误报,您可以使用until关键字在进程终止事件之前结束匹配序列。

以下EQL查询使用until关键字在具有event.typestopprocess事件之前结束序列。这些事件表示进程已被终止。

sequence by process.pid
  [ process where event.type == "start" and process.name == "cmd.exe" ]
  [ process where file.extension == "exe" ]
until [ process where event.type == "stop" ]

with runs 语句

edit

使用 with runs 语句在序列查询中连续运行相同的事件标准。例如:

sequence
  [ process where event.type == "creation" ]
  [ library where process.name == "regsvr32.exe" ] with runs=3
  [ registry where true ]

等同于:

sequence
  [ process where event.type == "creation" ]
  [ library where process.name == "regsvr32.exe" ]
  [ library where process.name == "regsvr32.exe" ]
  [ library where process.name == "regsvr32.exe" ]
  [ registry where true ]

The runs value must be between 1 and 100 (inclusive).

你可以使用带有by关键字with runs语句。 例如:

sequence
  [ process where event.type == "creation" ] by process.executable
  [ library where process.name == "regsvr32.exe" ] by dll.path with runs=3

示例

edit

您可以使用EQL样本来描述和匹配一系列时间上无序的事件。样本中的所有事件在指定的一个或多个字段上共享相同的值,这些字段使用by关键字(连接键)指定。样本中的每个项目都是一个事件类别和事件条件,用方括号([ ])包围。事件按它们匹配的过滤器的顺序列出。

sample by join_key
  [ event_category_1 where condition_1 ]
  [ event_category_2 where condition_2 ]
  ...

示例
以下 EQL 示例查询返回最多 10 个具有唯一 host 值的样本。每个样本由两个事件组成:

  1. 从以下事件开始:

    • 事件类别为 file
    • file.extensionexe
  2. 随后是一个事件类别为 process 的事件
sample by host
  [ file where file.extension == "exe" ]
  [ process where true ]

示例查询不考虑事件的时间顺序。 with maxspanwith runs 语句以及 until 关键字 不受支持。

函数

edit

您可以使用EQL函数来转换数据类型、执行数学运算、操作字符串等。有关支持的函数列表,请参阅函数参考

不区分大小写的函数

edit

大多数EQL函数默认情况下是区分大小写的。要使函数不区分大小写,请在函数名后使用~运算符:

stringContains(process.name,".exe")  // Matches ".exe" but not ".EXE" or ".Exe"
stringContains~(process.name,".exe") // Matches ".exe", ".EXE", or ".Exe"

函数如何影响搜索性能

edit

在EQL查询中使用函数可能会导致搜索速度变慢。如果你经常使用函数来转换索引数据,可以通过在索引期间进行这些更改来加快搜索速度。然而,这通常意味着索引速度会变慢。

示例
一个索引包含 file.path 字段。file.path 包含文件的完整路径,包括文件扩展名。

在运行EQL搜索时,用户通常会使用endsWith函数与file.path字段来匹配文件扩展名:

file where endsWith(file.path,".exe") or endsWith(file.path,".dll")

虽然这可以工作,但编写起来可能会很重复,并且可能会减慢搜索速度。为了加快搜索速度,你可以改为执行以下操作:

  1. 添加一个新字段, file.extension, 到索引中。该 file.extension 字段将仅包含来自 file.path 字段的文件扩展名。
  2. 使用包含 grok 处理器或其他预处理工具的 ingest pipeline 在索引之前从 file.path 字段中提取文件扩展名。
  3. 将提取的文件扩展名索引到 file.extension 字段中。

这些更改可能会减慢索引速度,但可以加快搜索速度。用户可以使用file.extension字段,而不是多次调用endsWith函数:

file where file.extension in ("exe", "dll")

我们建议在生产环境中部署任何索引更改之前进行测试和基准测试。请参阅调整索引速度调整搜索速度

管道

edit

EQL管道过滤、聚合和后处理由EQL查询返回的事件。您可以使用管道来缩小EQL查询结果的范围或使其更加具体。

管道使用竖线(|)字符进行分隔。

event_category where condition | pipe

示例
以下EQL查询使用tail管道仅返回与查询匹配的10个最新事件。

authentication where agent.id == 4624
| tail 10

您可以将一个管道的输出传递给另一个管道。这使您能够使用单个查询进行多个管道操作。

有关支持的管道的列表,请参阅 管道参考

限制

edit

EQL 有以下限制。

EQL 使用 fields 参数

edit

EQL 使用搜索 API 的 fields 参数 来检索字段值。fields 参数的任何限制也适用于 EQL 查询。例如,如果 _source 在任何返回字段或索引级别被禁用,则无法检索这些值。

比较字段

edit

您不能使用EQL比较运算符将一个字段与另一个字段进行比较。即使字段通过函数进行了更改,这也适用。

不支持文本字段

edit

EQL 搜索不支持 text 字段。要搜索 text 字段, 请使用 EQL 搜索 API 的 查询 DSL filter 参数。

在嵌套字段上进行EQL搜索

edit

您不能使用EQL来搜索nested字段的值或nested字段的子字段。然而,包含nested字段映射的数据流和索引在其他方面是受支持的。

与Endgame EQL语法的差异

edit

Elasticsearch EQL 与 Elastic Endgame EQL 语法 的区别如下:

  • 在 Elasticsearch EQL 中,大多数操作符是区分大小写的。例如, process_name == "cmd.exe" 不等同于 process_name == "Cmd.exe"
  • 在 Elasticsearch EQL 中,函数是区分大小写的。要使函数不区分大小写,请使用 ~,例如 endsWith~(process_name, ".exe")
  • 对于不区分大小写的相等比较,请使用 : 操作符。*? 都被识别为通配符字符。
  • ==!= 操作符不会扩展通配符字符。例如, process_name == "cmd*.exe"* 解释为字面星号,而不是通配符。
  • 对于通配符匹配,在区分大小写时使用 like 关键字,在不分区大小写时使用 like~: 操作符等同于 like~
  • 对于正则表达式匹配,请使用 regexregex~
  • = 不能替代 == 操作符。
  • 不支持用单引号 (') 括起来的字符串。请改用双引号 (")。
  • ?"?' 不表示原始字符串。请改用三个双引号 (""") 括起来的原始字符串。
  • Elasticsearch EQL 不支持:

序列查询如何处理匹配

edit

序列查询不会找到序列的所有潜在匹配项。对于大型事件数据集,这种方法会太慢且成本太高。相反,序列查询将待处理的序列匹配作为状态机来处理:

  • 序列查询中的每个事件项都是机器中的一个状态。
  • 每个状态下只能有一个待处理的序列。
  • 如果两个待处理的序列同时处于同一状态,则最新的序列会覆盖较旧的序列。
  • 如果查询包含by字段,查询将为每个唯一的by字段值使用一个单独的状态机。
示例

数据集包含以下按时间顺序升序排列的过程事件:

{ "index" : { "_id": "1" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
{ "index" : { "_id": "2" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
{ "index" : { "_id": "3" } }
{ "user": { "name": "elkbee" }, "process": { "name": "bash" }, ...}
{ "index" : { "_id": "4" } }
{ "user": { "name": "root" }, "process": { "name": "bash" }, ...}
{ "index" : { "_id": "5" } }
{ "user": { "name": "root" }, "process": { "name": "bash" }, ...}
{ "index" : { "_id": "6" } }
{ "user": { "name": "elkbee" }, "process": { "name": "attrib" }, ...}
{ "index" : { "_id": "7" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
{ "index" : { "_id": "8" } }
{ "user": { "name": "elkbee" }, "process": { "name": "bash" }, ...}
{ "index" : { "_id": "9" } }
{ "user": { "name": "root" }, "process": { "name": "cat" }, ...}
{ "index" : { "_id": "10" } }
{ "user": { "name": "elkbee" }, "process": { "name": "cat" }, ...}
{ "index" : { "_id": "11" } }
{ "user": { "name": "root" }, "process": { "name": "cat" }, ...}

EQL序列查询搜索数据集:

sequence by user.name
  [process where process.name == "attrib"]
  [process where process.name == "bash"]
  [process where process.name == "cat"]

查询的事件项对应以下状态:

  • 状态A: [process where process.name == "attrib"]
  • 状态B: [process where process.name == "bash"]
  • 完成: [process where process.name == "cat"]
sequence state machine

要查找匹配的序列,查询为每个唯一的user.name值使用单独的状态机。根据数据集,您可以预期两个状态机:一个用于root用户,另一个用于elkbee

separate state machines

待处理的序列匹配会按照以下方式通过每台机器的状态:

{ "index" : { "_id": "1" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
// Creates sequence [1] in state A for the "root" user.
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [1]    |     |           |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "2" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
// Creates sequence [2] in state A for "root", overwriting sequence [1].
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [2]    |     |           |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "3" } }
{ "user": { "name": "elkbee" }, "process": { "name": "bash" }, ...}
// Nothing happens. The "elkbee" user has no pending sequence to move
// from state A to state B.
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |           |     |           |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "4" } }
{ "user": { "name": "root" }, "process": { "name": "bash" }, ...}
// Sequence [2] moves out of state A for "root".
// State B for "root" now contains [2, 4].
// State A for "root" is empty.
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+ --> +-----------+     +------------+  |
// |  |           |     |   [2, 4]  |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "5" } }
{ "user": { "name": "root" }, "process": { "name": "bash" }, ...}
// Nothing happens. State A is empty for "root".
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |           |     |   [2, 4]  |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "6" } }
{ "user": { "name": "elkbee" }, "process": { "name": "attrib" }, ...}
// Creates sequence [6] in state A for "elkbee".
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [6]    |     |           |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "7" } }
{ "user": { "name": "root" }, "process": { "name": "attrib" }, ...}
// Creates sequence [7] in state A for "root".
// Sequence [2, 4] remains in state B for "root".
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [7]    |     |   [2, 4]  |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "8" } }
{ "user": { "name": "elkbee" }, "process": { "name": "bash" }, ...}
// Sequence [6, 8] moves to state B for "elkbee".
// State A for "elkbee" is now empty.
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+ --> +-----------+     +------------+  |
// |  |           |     |   [6, 8]  |     |            |  |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "9" } }
{ "user": { "name": "root" }, "process": { "name": "cat" }, ...}
// Sequence [2, 4, 9] is complete for "root".
// State B for "root" is now empty.
// Sequence [7] remains in state A.
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+ --> +------------+  |
// |  |    [7]    |     |           |     |  [2, 4, 9] |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "10" } }
{ "user": { "name": "elkbee" }, "process": { "name": "cat" }, ...}
// Sequence [6, 8, 10] is complete for "elkbee".
// State A and B for "elkbee" are now empty.
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+ --> +------------+  |
// |  |           |     |           |     | [6, 8, 10] |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+

{ "index" : { "_id": "11" } }
{ "user": { "name": "root" }, "process": { "name": "cat" }, ...}
// Nothing happens.
// The machines for "root" and "elkbee" remain the same.
//
// +------------------------"root"------------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |    [7]    |     |           |     |  [2, 4, 9] |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+
//
// +-----------------------"elkbee"-----------------------+
// |  +-----------+     +-----------+     +------------+  |
// |  |  State A  |     |  State B  |     |  Complete  |  |
// |  +-----------+     +-----------+     +------------+  |
// |  |           |     |           |     | [6, 8, 10] |
// |  +-----------+     +-----------+     +------------+  |
// +------------------------------------------------------+