Merge eureka-body/leaf-into-huginn: the pipeline leaf and its schema catalogue live in Huginn; the git edge to Epiphany is gone
Soul s1-s3 hold (eureka-body:verdict:cut-leaf-into-huginn.s3); wire parity defended by committed golden bytes and the live-store read-back. Relicensed AGPL-3.0 (ruling leaf-relicensed-agpl). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
afc35f92d1
56 changed files with 17959 additions and 107 deletions
25
AGENTS.md
25
AGENTS.md
|
|
@ -11,22 +11,24 @@ Studio's job, not Huginn's.
|
|||
|
||||
- Project root: `F:\Projects\Huginn`
|
||||
- Upstream: `https://github.com/GameCult/Huginn.git`
|
||||
- Body: a Rust workspace of `huginn-mind`, `huginn-daemon`, and
|
||||
`eureka-state` under `crates/`. Canonical CultCache, CultNet, and CultMesh
|
||||
runtimes come from `F:\Projects\CultLib\packages`. `huginn-mind` is live:
|
||||
it persists, admits and reads back an instance's mind, queries and derived
|
||||
status included, over `cultcache-rs` and `epiphany-pipeline` pinned by git
|
||||
rev. `huginn-daemon` serves one mind over CultNet RUDP through `cultnet-rs`
|
||||
at the same rev, and reaches the leaf and the store type through
|
||||
`huginn-mind` so one crate pins each revision. `eureka-state` is the client
|
||||
core: one call to a daemon over CultNet, holding no state.
|
||||
- Body: a Rust workspace of `eureka-pipeline`, `huginn-mind`, `huginn-daemon`,
|
||||
and `eureka-state` under `crates/`. Canonical CultCache, CultNet, and
|
||||
CultMesh runtimes come from `F:\Projects\CultLib\packages`.
|
||||
`eureka-pipeline` is the pipeline leaf: document shapes, bounds, formats,
|
||||
keys and the schemas published under `schemas/cultnet`. `huginn-mind` is
|
||||
live: it persists, admits and reads back an instance's mind, queries and
|
||||
derived status included, over `cultcache-rs` and `eureka-pipeline`.
|
||||
`huginn-daemon` serves one mind over CultNet RUDP through `cultnet-rs`, and reaches the leaf and the store type through `huginn-mind`
|
||||
so the workspace's one CultLib revision (root `[workspace.dependencies]`) decides both. `eureka-state` is the client core: one call
|
||||
to a daemon over CultNet, holding no state.
|
||||
- Owned: an instance's memory documents, their admission and the read side
|
||||
that derives their status, in `huginn-mind`,
|
||||
over a redb CultCache store at `<state_root>/minds/<instance>/mind.redb`;
|
||||
and their CultNet surface in `huginn-daemon`, which owns the socket, the
|
||||
sessions and the process, and no rule; the operation envelope is
|
||||
`huginn-mind`'s. The two wire
|
||||
schemas are published from `schemas/cultnet/`. There is no index yet.
|
||||
schemas are published from `schemas/cultnet/` and listed, with the
|
||||
pipeline schemas, in its `index.json`.
|
||||
- To depend on: Qdrant, directly, once retrieval exists. Unreachable Qdrant is
|
||||
to be a loud refusal, never a fallback store. No crate connects to it yet.
|
||||
- Forbidden: a second writer of mind state; mind state in version control;
|
||||
|
|
@ -36,7 +38,8 @@ Studio's job, not Huginn's.
|
|||
|
||||
- Prefer CultLib's typed Rust APIs over ad hoc decoding. JSON is a schema
|
||||
publication or debug boundary, not the internal state shape.
|
||||
- The Eureka cut map in `Epiphany/notes/eureka-pipeline-state-cut.md` owns
|
||||
- The campaign's cut specs in the mind (`eureka-body`, read through
|
||||
`eureka-state`) and `docs/eureka-body-map.md` own
|
||||
what each crate does next. Read the cut before the crate.
|
||||
- Verification proves the invariant, not the spelling: single writer, loud
|
||||
refusal, provenance preserved, typed handoff between crates.
|
||||
|
|
|
|||
28
Cargo.lock
generated
28
Cargo.lock
generated
|
|
@ -372,19 +372,6 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epiphany-pipeline"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/GameCult/Epiphany.git?rev=ef9568655e75bb6cb362a038e2739a5ac5e777ee#ef9568655e75bb6cb362a038e2739a5ac5e777ee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"cultcache-rs",
|
||||
"rmp-serde",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
|
|
@ -395,6 +382,19 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eureka-pipeline"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"cultcache-rs",
|
||||
"rmp-serde",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eureka-state"
|
||||
version = "0.1.0"
|
||||
|
|
@ -644,7 +644,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"cultcache-rs",
|
||||
"cultnet-rs",
|
||||
"epiphany-pipeline",
|
||||
"eureka-pipeline",
|
||||
"rmp-serde",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/eureka-pipeline",
|
||||
"crates/huginn-mind",
|
||||
"crates/huginn-daemon",
|
||||
"crates/eureka-state",
|
||||
|
|
@ -8,5 +9,11 @@ resolver = "3"
|
|||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
license = "AGPL-3.0-only"
|
||||
publish = false
|
||||
|
||||
# The one CultLib revision the workspace speaks. Every crate inherits these
|
||||
# entries; a bump is a change to this table and nowhere else.
|
||||
[workspace.dependencies]
|
||||
cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultcache-rs" }
|
||||
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultnet-rs" }
|
||||
|
|
|
|||
661
LICENSE
Normal file
661
LICENSE
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
11
README.md
11
README.md
|
|
@ -61,7 +61,7 @@ It reads them back the same way: a document with the facts of its admission
|
|||
joined from that receipt and its status derived at read time, and typed
|
||||
queries over one mind. Status is never stored, and the views derive it
|
||||
through the same rules admission does. Document shape and keys come from
|
||||
`epiphany-pipeline`; the store is CultLib's Rust CultCache.
|
||||
`eureka-pipeline` (`crates/eureka-pipeline`); the store is CultLib's Rust CultCache.
|
||||
|
||||
`huginn-daemon` opens one mind, binds one UDP socket in that order, and
|
||||
answers every frame on the session it arrived on: one operation per `Mind`
|
||||
|
|
@ -69,12 +69,11 @@ method plus `whoami`, whose payload is the mind's own types as named
|
|||
MessagePack. A refusal is an answer with a `rejected` status, never a
|
||||
transport error; an envelope that does not decode is answered with a typed
|
||||
failure and reaches no mind. The two schemas it publishes live in
|
||||
`schemas/cultnet/` and are pinned to their derivation by a test. There is no
|
||||
index yet.
|
||||
`schemas/cultnet/`, listed in its `index.json`, and are pinned to their
|
||||
derivation by a test.
|
||||
|
||||
`eureka-state` has no MCP surface yet; the campaign's cut map in
|
||||
`Epiphany/notes/eureka-pipeline-state-cut.md` owns what each crate must do
|
||||
next.
|
||||
The campaign's cut specs in the mind (`eureka-body`) and
|
||||
`docs/eureka-body-map.md` own what each crate does next.
|
||||
|
||||
```powershell
|
||||
cargo check --workspace
|
||||
|
|
|
|||
32
crates/eureka-pipeline/Cargo.toml
Normal file
32
crates/eureka-pipeline/Cargo.toml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "eureka-pipeline"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
autobins = false
|
||||
|
||||
# Provenance: this crate was copied once from GameCult/Epiphany at ef956865,
|
||||
# where it was licensed PolyForm Noncommercial. Its sole copyright holder
|
||||
# relicensed it AGPL-3.0-only on 2026-10-09, as the rest of the workspace is
|
||||
# (ruling eureka-body:ruling:leaf-relicensed-agpl).
|
||||
|
||||
[lib]
|
||||
name = "eureka_pipeline"
|
||||
path = "src/lib.rs"
|
||||
|
||||
# A leaf type library. Every dependency here is one the document types or the
|
||||
# write path need in a non-test build: the organ prepares, decodes and
|
||||
# validates envelopes through this crate, so `anyhow` (the cache's error type)
|
||||
# and `rmp-serde` (the payload decode) are normal dependencies. Only the
|
||||
# schema-publication check needs JSON.
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
chrono = "0.4.44"
|
||||
cultcache-rs.workspace = true
|
||||
rmp-serde = "1"
|
||||
schemars = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
29
crates/eureka-pipeline/README.md
Normal file
29
crates/eureka-pipeline/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# eureka-pipeline
|
||||
|
||||
The pipeline leaf: typed documents, bounds, keys and the cultcache wrappers
|
||||
that define the mind's wire. Library only.
|
||||
|
||||
## Provenance and license
|
||||
|
||||
Copied once from GameCult/Epiphany at `ef956865`, where it was licensed
|
||||
PolyForm Noncommercial. Its sole copyright holder relicensed it AGPL-3.0-only
|
||||
on 2026-10-09; the root `LICENSE` is the AGPL-3.0 text and covers this crate
|
||||
like the rest of the workspace.
|
||||
|
||||
## Wire parity checks
|
||||
|
||||
Committed (runs in `cargo test -p eureka-pipeline`): `encoded_envelopes_match_the_committed_golden`
|
||||
asserts key, cultcache type id, schema name and payload bytes of every kind
|
||||
and every encoding-relevant variant against `golden/envelopes.txt`. A diff
|
||||
there is a wire change; do not regenerate it to make the test pass.
|
||||
|
||||
Opt-in, against a real store: `stored_documents_read_back` decodes, validates
|
||||
and keys every stored pipeline document and asserts that it re-encodes to the
|
||||
stored key, type and payload bytes. Take a copy of the store (the test copies
|
||||
the file again before opening it, so the copy may be read-only), never open the
|
||||
live one:
|
||||
|
||||
```sh
|
||||
sudo cp /var/lib/gamecult/huginn/minds/eureka/mind.redb "$SNAP/minds/eureka/mind.redb"
|
||||
HUGINN_MIND_SNAPSHOT="$SNAP" cargo test -p eureka-pipeline -- --ignored --exact tests::stored_documents_read_back --nocapture
|
||||
```
|
||||
40
crates/eureka-pipeline/golden/envelopes.txt
Normal file
40
crates/eureka-pipeline/golden/envelopes.txt
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Golden encoded envelopes of the pipeline leaf. One line per document:
|
||||
# label key type-id schema-name payload-hex (payload is the cultcache MessagePack payload)
|
||||
# Provenance: the payload lines were dumped once by `golden_dump` from this
|
||||
# crate at huginn 44be4cd. 87b6455 differs from 44be4cd only in test code
|
||||
# (cfg(test) items; no encoding path). They match the encoder of Epiphany
|
||||
# ef956865 byte for byte after one substitution: the sample source-path string
|
||||
# was renamed in four kinds (cut_spec, cut_report, finding, follow_up), where it
|
||||
# sits in a CodeLocation path. Soul proved the encoding byte-identical to the
|
||||
# live mind store (eureka-body:verdict:cut-leaf-into-huginn.s1) and to
|
||||
# Epiphany ef956865 modulo that rename (verdict ...s2). Never regenerate to make
|
||||
# a failing test pass: a diff here is a wire change and needs its own ruling.
|
||||
sample.campaign eureka-state:campaign:self epiphany.pipeline.campaign.v2 EpiphanyPipelineCampaignDocument 9185a4736c7567ac657572656b612d7374617465a57469746c65b5457572656b6120706970656c696e65207374617465a57265706f7391b147616d6543756c742f4570697068616e79ae776f726b696e675f6272616e6368bb636f6465782f657572656b612d706970656c696e652d7374617465aa7461726765745f646f6384a470617468d9256e6f7465732f657572656b612d706970656c696e652d73746174652d7461726765742e6d64aa73746172745f6c696e6501a8656e645f6c696e6509a6636f6d6d6974a83566393832323864
|
||||
sample.target eureka-state:target:r2 epiphany.pipeline.target.v2 EpiphanyPipelineTargetDocument 9186a863616d706169676eac657572656b612d7374617465a87265766973696f6e02aa696e76617269616e74739182a56c6162656cab6d696e642d61646d697473a973746174656d656e74b64f6e6c792061646d697373696f6e207772697465732eac6e6f745f696e5f73636f706591ac4576652062726f7773696e67b963616e6f6e6963616c5f696d706c656d656e746174696f6e7391a743756c744c6962a3646f6384a470617468d9256e6f7465732f657572656b612d706970656c696e652d73746174652d7461726765742e6d64aa73746172745f6c696e6501a8656e645f6c696e6509a6636f6d6d6974a83566393832323864
|
||||
sample.question eureka-state:question:Q1 epiphany.pipeline.question.v2 EpiphanyPipelineQuestionDocument 9189a863616d706169676eac657572656b612d7374617465a56c6162656ca25131a57469746c65b357686f206f776e73207468652073746174653fa87175657374696f6eb357686f206f776e73207468652073746174653fa76f7074696f6e739282a56c6162656ca141a474657874ab616e20696e7374616e636582a56c6162656ca142a474657874a661207265706fab7265636f6d6d656e646564a141a7646570656e647391a6437574203361a97261697365645f696e82a46b696e64a743757453706563a26964bf657572656b612d73746174653a6375745f737065633a6375742d33612e7231a861736b65645f6f6eaa323032362d30392d3135
|
||||
sample.ruling eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273b8657572656b612d73746174653a7175657374696f6e3a5131a663686f696365a141a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465bd616c6c207265636f6d6d656e646174696f6e732c20676f206168656164a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e74739185a47265706fb147616d6543756c742f4165746865726961a6636f6d6d6974d92861616161616161616161616161616161616161616161616161616161616161616161616161616161a46b696e64a652756c696e67a26964b363756c7463616368653a72756c696e673a5231ae7061796c6f61645f736861323536d94062626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262a9617574686f72697479a84f70657261746f72
|
||||
sample.cut_spec eureka-state:cut_spec:cut-3a.r1 epiphany.pipeline.cut_spec.v2 EpiphanyPipelineCutSpecDocument 91de0012a863616d706169676eac657572656b612d7374617465a3637574a23361a87265766973696f6e01a57469746c65b2506970656c696e6520646f63756d656e7473a47265706fb147616d6543756c742f4570697068616e79a66272616e6368bb636f6465782f657572656b612d706970656c696e652d7374617465a462617365a83566393832323864aa646570656e64735f6f6e91a132a5666972737491ae526561642074686520737065632ea764656c657465739183a470617468a66f6c642e7273a56c696e657303a46e6f7465a464656164ab6b656570735f6d6f76657391ac636f6d6d6974206f776e6572a46164647391b5706970656c696e655f646f63756d656e74732e7273ac66696c655f6368616e6765739182a86c6f636174696f6e83a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509a66368616e6765a3616464ad617574686f726974795f6d617087a56f776e6572a4636f7265a6696e7075747391af747970656420646f63756d656e7473a76f75747075747391a9656e76656c6f706573ad646572697665645f737461746591ac64657269766564206b657973b1666f7262696464656e5f7772697465727391a34d4350ac7368617265645f706174687391a961646d697373696f6ead64656c6574696f6e5f6c696e65a36e2f61ac766572696669636174696f6e84a66275696c647391ab636172676f20636865636ba574657374739182a46e616d65a46b657973a470696e73af6f6e652064657269766564206b6579a86e656761746976659182a77061747465726ea75665633c75383ea573636f7065a9646f63756d656e7473a86f70657261746f7291a46e6f6e65a8657374696d61746588ab6c696e65735f6164646564cd0384ad6c696e65735f72656d6f76656400b2646570656e64656e636965735f616464656491a8736368656d617273b4646570656e64656e636965735f72656d6f76656490ad666f726d6174735f616464656491b66570697068616e792e706970656c696e652e2a2e7631af666f726d6174735f72656d6f76656490ad746172676574735f616464656490af746172676574735f72656d6f76656490a772756c696e677391b6657572656b612d73746174653a72756c696e673a5238a97175657374696f6e7391b8657572656b612d73746174653a7175657374696f6e3a5131
|
||||
sample.cut_report eureka-state:cut_report:cut-3a.h1 epiphany.pipeline.cut_report.v2 EpiphanyPipelineCutReportDocument 918fa863616d706169676eac657572656b612d7374617465a86375745f73706563bf657572656b612d73746174653a6375745f737065633a6375742d33612e7231a7617474656d707401a47265706fb147616d6543756c742f4570697068616e79a66272616e6368bb636f6465782f657572656b612d706970656c696e652d7374617465a7636f6d6d6974739183a3736861a83566393832323864a77375626a656374ba4164642074686520706970656c696e6520646f63756d656e7473a66275696c6473c3a572616e676582a462617365a83566393832323864a468656164a83566393832323864ac766572696669636174696f6e9183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6ba96d75746174696f6e739187a56c6162656ca24d31a472756c65ae6b65792064657269766174696f6ea86c6f636174696f6e83a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509a66265666f7265bd706172656e745f637574286669656c642c20737065632c20277227293fa56166746572a473706563a6636f6d6d6974a83566393832323864b26661696c65645f61735f6578706563746564c3aa646576696174696f6e739182a477686174a56e616d6573a3776879ac676c6f62206578706f727473a5666f726b7391b8657572656b612d73746174653a7175657374696f6e3a5131b07374727563747572616c5f64656c746188ab6c696e65735f6164646564cd0384ad6c696e65735f72656d6f76656400b2646570656e64656e636965735f616464656491a8736368656d617273b4646570656e64656e636965735f72656d6f76656490ad666f726d6174735f616464656491b66570697068616e792e706970656c696e652e2a2e7631af666f726d6174735f72656d6f76656490ad746172676574735f616464656490af746172676574735f72656d6f76656490ac6c616e6465645f6e616d65739182a46e616d65b0506970656c696e65446f63756d656e74a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a6756e646f6e6591a961646d697373696f6ea870726f6d697365739182a56c6162656ca25031a474657874bd4f6e652064657269766564206b65792070657220646f63756d656e742e
|
||||
sample.verdict eureka-state:verdict:cut-3a.s2 epiphany.pipeline.verdict.v2 EpiphanyPipelineVerdictDocument 9185a863616d706169676eac657572656b612d7374617465aa6375745f7265706f7274d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a47061737302a572616e676582a462617365a83566393832323864a468656164a83566393832323864a6636c61696d739186a5636c61696dbe4120636f6d706f736564206b657920686173206f6e6520736f757263652ea76f7574636f6d65a946616c736966696564a865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6ba866696e64696e677391d921657572656b612d73746174653a66696e64696e673a6375742d33612e73322e4634a770726f6d697365a25031a96d75746174696f6e7391a24d31
|
||||
sample.finding eureka-state:finding:cut-3a.s2.F4 epiphany.pipeline.finding.v2 EpiphanyPipelineFindingDocument 918da863616d706169676eac657572656b612d7374617465a776657264696374be657572656b612d73746174653a766572646963743a6375742d33612e7332a56c6162656ca24634a572616e676582a462617365a83566393832323864a468656164a83566393832323864aa636f6e666964656e6365a9436f6e6669726d6564a87365766572697479a448696768a5636c61696dd9214120646f74746564206c6162656c20636f6d706f7365732074776f206b6579732eaa696e76617269616e747391ab6d696e642d61646d697473a96c6f636174696f6e739183a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509b06661696c7572655f7363656e6172696fbc54776f20646f63756d656e747320636c61696d206f6e65206b65792ea865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6baa707265636564656e747390a66f726967696eaa496e74726f6475636564
|
||||
sample.follow_up eureka-state:follow_up:FU-4 epiphany.pipeline.follow_up.v2 EpiphanyPipelineFollowUpDocument 9188a863616d706169676eac657572656b612d7374617465a56c6162656ca446552d34a6736f7572636582a46b696e64a746696e64696e67a26964d921657572656b612d73746174653a66696e64696e673a6375742d33612e73322e4634a47265706fb147616d6543756c742f4570697068616e79a96c6f636174696f6e739183a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509a46974656db95065722d6b696e642061646d697373696f6e2072756c65732eaf7768795f69745f63616e5f77616974b9546865206f7267616e206f776e732061646d697373696f6e2ea56f776e6572a548616e6473
|
||||
sample.resolution eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a8416e73776572656481a2627982a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a5238a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
sample.instance yggdrasil:instance:self epiphany.pipeline.instance.v2 EpiphanyPipelineInstanceDocument 9184a8696e7374616e6365a979676764726173696cac646973706c61795f6e616d65ae59676764726173696c206d696e64aa637265617465645f6174aa323032362d30392d3135a4686f7374a979676764726173696c
|
||||
sample.stewardship yggdrasil:stewardship:gamecult_-epiphany.n1 epiphany.pipeline.stewardship.v2 EpiphanyPipelineStewardshipDocument 9185a8696e7374616e6365a979676764726173696ca47265706fb147616d6543756c742f4570697068616e79a873657175656e636501ab61737369676e65645f6f6eaa323032362d30392d3135a46e6f7465b954686520457572656b612063616d706169676e207265706f2e
|
||||
sample.hand_off yggdrasil:hand_off:thought-cage.gamecult_-epiphany.2026-09-15 epiphany.pipeline.hand_off.v2 EpiphanyPipelineHandOffDocument 9186ad66726f6d5f696e7374616e6365a979676764726173696cab746f5f696e7374616e6365ac74686f756768742d63616765a47265706fb147616d6543756c742f4570697068616e79a9646f63756d656e747392ac657572656b612d7374617465b6657572656b612d73746174653a72756c696e673a5238a6726561736f6ed92854686520776f726b73746174696f6e206d696e642074616b6573207468652063616d706169676e2ea968616e6465645f6f6eaa323032362d30392d3135
|
||||
resolution.superseded eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581aa5375706572736564656481a262799282a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a523882a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.fixed-by-none eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a26279c0a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.fixed-by-some eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a2627982a46b696e64a94375745265706f7274a26964d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.deferred eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a8446566657272656481a2746f82a46b696e64a8466f6c6c6f775570a26964bb657572656b612d73746174653a666f6c6c6f775f75703a46552d34a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.recorded eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a85265636f7264656481a6726561736f6ea64e6f7465642ea9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
resolution.withdrawn eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a957697468647261776e81a6726561736f6ea94d697374616b656e2ea9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135
|
||||
ruling.authority-standing eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273b8657572656b612d73746174653a7175657374696f6e3a5131a663686f696365a141a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465bd616c6c207265636f6d6d656e646174696f6e732c20676f206168656164a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e74739185a47265706fb147616d6543756c742f4165746865726961a6636f6d6d6974d92861616161616161616161616161616161616161616161616161616161616161616161616161616161a46b696e64a652756c696e67a26964b363756c7463616368653a72756c696e673a5231ae7061796c6f61645f736861323536d94062626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262a9617574686f72697479a85374616e64696e67
|
||||
ruling.authority-defaulted eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273b8657572656b612d73746174653a7175657374696f6e3a5131a663686f696365a141a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465bd616c6c207265636f6d6d656e646174696f6e732c20676f206168656164a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e74739185a47265706fb147616d6543756c742f4165746865726961a6636f6d6d6974d92861616161616161616161616161616161616161616161616161616161616161616161616161616161a46b696e64a652756c696e67a26964b363756c7463616368653a72756c696e673a5231ae7061796c6f61645f736861323536d94062626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262a9617574686f72697479a944656661756c746564
|
||||
ruling.options-none eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273c0a663686f696365c0a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465c0a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e747390a9617574686f72697479a84f70657261746f72
|
||||
question.raised-in-none eureka-state:question:Q1 epiphany.pipeline.question.v2 EpiphanyPipelineQuestionDocument 9189a863616d706169676eac657572656b612d7374617465a56c6162656ca25131a57469746c65b357686f206f776e73207468652073746174653fa87175657374696f6eb357686f206f776e73207468652073746174653fa76f7074696f6e739282a56c6162656ca141a474657874ab616e20696e7374616e636582a56c6162656ca142a474657874a661207265706fab7265636f6d6d656e646564a141a7646570656e647391a6437574203361a97261697365645f696ec0a861736b65645f6f6eaa323032362d30392d3135
|
||||
cut_spec.authority-map-none eureka-state:cut_spec:cut-3a.r1 epiphany.pipeline.cut_spec.v2 EpiphanyPipelineCutSpecDocument 91de0012a863616d706169676eac657572656b612d7374617465a3637574a23361a87265766973696f6e01a57469746c65b2506970656c696e6520646f63756d656e7473a47265706fb147616d6543756c742f4570697068616e79a66272616e6368bb636f6465782f657572656b612d706970656c696e652d7374617465a462617365a83566393832323864aa646570656e64735f6f6e91a132a5666972737491ae526561642074686520737065632ea764656c657465739183a470617468a66f6c642e7273a56c696e657303a46e6f7465a464656164ab6b656570735f6d6f76657391ac636f6d6d6974206f776e6572a46164647391b5706970656c696e655f646f63756d656e74732e7273ac66696c655f6368616e6765739182a86c6f636174696f6e83a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509a66368616e6765a3616464ad617574686f726974795f6d6170c0ac766572696669636174696f6e84a66275696c647391ab636172676f20636865636ba574657374739182a46e616d65a46b657973a470696e73af6f6e652064657269766564206b6579a86e656761746976659182a77061747465726ea75665633c75383ea573636f7065a9646f63756d656e7473a86f70657261746f7291a46e6f6e65a8657374696d61746588ab6c696e65735f6164646564cd0384ad6c696e65735f72656d6f76656400b2646570656e64656e636965735f616464656491a8736368656d617273b4646570656e64656e636965735f72656d6f76656490ad666f726d6174735f616464656491b66570697068616e792e706970656c696e652e2a2e7631af666f726d6174735f72656d6f76656490ad746172676574735f616464656490af746172676574735f72656d6f76656490a772756c696e677391b6657572656b612d73746174653a72756c696e673a5238a97175657374696f6e7391b8657572656b612d73746174653a7175657374696f6e3a5131
|
||||
verdict.claim-holds-promise-none eureka-state:verdict:cut-3a.s2 epiphany.pipeline.verdict.v2 EpiphanyPipelineVerdictDocument 9185a863616d706169676eac657572656b612d7374617465aa6375745f7265706f7274d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a47061737302a572616e676582a462617365a83566393832323864a468656164a83566393832323864a6636c61696d739186a5636c61696dbe4120636f6d706f736564206b657920686173206f6e6520736f757263652ea76f7574636f6d65a5486f6c6473a865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6ba866696e64696e677391d921657572656b612d73746174653a66696e64696e673a6375742d33612e73322e4634a770726f6d697365c0a96d75746174696f6e7391a24d31
|
||||
verdict.claim-unproven-promise-none eureka-state:verdict:cut-3a.s2 epiphany.pipeline.verdict.v2 EpiphanyPipelineVerdictDocument 9185a863616d706169676eac657572656b612d7374617465aa6375745f7265706f7274d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a47061737302a572616e676582a462617365a83566393832323864a468656164a83566393832323864a6636c61696d739186a5636c61696dbe4120636f6d706f736564206b657920686173206f6e6520736f757263652ea76f7574636f6d65a8556e70726f76656ea865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6ba866696e64696e677391d921657572656b612d73746174653a66696e64696e673a6375742d33612e73322e4634a770726f6d697365c0a96d75746174696f6e7391a24d31
|
||||
finding.plausible-blocker-preexisting-kinds eureka-state:finding:cut-3a.s2.F4 epiphany.pipeline.finding.v2 EpiphanyPipelineFindingDocument 918da863616d706169676eac657572656b612d7374617465a776657264696374be657572656b612d73746174653a766572646963743a6375742d33612e7332a56c6162656ca24634a572616e676582a462617365a83566393832323864a468656164a83566393832323864aa636f6e666964656e6365a9506c61757369626c65a87365766572697479a7426c6f636b6572a5636c61696dd9214120646f74746564206c6162656c20636f6d706f7365732074776f206b6579732eaa696e76617269616e747391ab6d696e642d61646d697473a96c6f636174696f6e739183a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e65c0b06661696c7572655f7363656e6172696fbc54776f20646f63756d656e747320636c61696d206f6e65206b65792ea865766964656e63659583a46b696e64a7436f6d6d616e64a76c6f6361746f72a178a6726573756c74a17983a46b696e64a84d75746174696f6ea76c6f6361746f72a178a6726573756c74a17983a46b696e64a550726f6265a76c6f6361746f72a178a6726573756c74a17983a46b696e64aa536f7572636552656164a76c6f6361746f72a178a6726573756c74a17983a46b696e64a743617074757265a76c6f6361746f72a178a6726573756c74a179aa707265636564656e747390a66f726967696eab5072654578697374696e67
|
||||
finding.severity-medium eureka-state:finding:cut-3a.s2.F4 epiphany.pipeline.finding.v2 EpiphanyPipelineFindingDocument 918da863616d706169676eac657572656b612d7374617465a776657264696374be657572656b612d73746174653a766572646963743a6375742d33612e7332a56c6162656ca24634a572616e676582a462617365a83566393832323864a468656164a83566393832323864aa636f6e666964656e6365a9436f6e6669726d6564a87365766572697479a64d656469756da5636c61696dd9214120646f74746564206c6162656c20636f6d706f7365732074776f206b6579732eaa696e76617269616e747391ab6d696e642d61646d697473a96c6f636174696f6e739183a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509b06661696c7572655f7363656e6172696fbc54776f20646f63756d656e747320636c61696d206f6e65206b65792ea865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6baa707265636564656e747390a66f726967696eaa496e74726f6475636564
|
||||
finding.severity-low eureka-state:finding:cut-3a.s2.F4 epiphany.pipeline.finding.v2 EpiphanyPipelineFindingDocument 918da863616d706169676eac657572656b612d7374617465a776657264696374be657572656b612d73746174653a766572646963743a6375742d33612e7332a56c6162656ca24634a572616e676582a462617365a83566393832323864a468656164a83566393832323864aa636f6e666964656e6365a9436f6e6669726d6564a87365766572697479a34c6f77a5636c61696dd9214120646f74746564206c6162656c20636f6d706f7365732074776f206b6579732eaa696e76617269616e747391ab6d696e642d61646d697473a96c6f636174696f6e739183a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509b06661696c7572655f7363656e6172696fbc54776f20646f63756d656e747320636c61696d206f6e65206b65792ea865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6baa707265636564656e747390a66f726967696eaa496e74726f6475636564
|
||||
3351
crates/eureka-pipeline/src/lib.rs
Normal file
3351
crates/eureka-pipeline/src/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -15,7 +15,7 @@ publish.workspace = true
|
|||
# consumer: four tools over stdio, one per wire operation, holding no state.
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultnet-rs" }
|
||||
cultnet-rs.workspace = true
|
||||
huginn-mind = { path = "../huginn-mind" }
|
||||
rmcp = { version = "2.2.0", default-features = false, features = ["server", "macros", "transport-io"] }
|
||||
rmp-serde = "1"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use cultnet_rs::{
|
|||
CultNetRudpSocketTransportConnection, CultNetWireContract, encode_cultnet_message_to_vec, fetch_content,
|
||||
};
|
||||
use huginn_mind::envelope::{OperationFailure, decode_response, encode_request};
|
||||
use huginn_mind::epiphany_pipeline::Slug;
|
||||
use huginn_mind::eureka_pipeline::Slug;
|
||||
use huginn_mind::{HuginnMindRequest, HuginnMindResponse, MAX_DEFERRED_BODY_BYTES};
|
||||
|
||||
/// The correlation key of the one request a session carries.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use std::time::Duration;
|
|||
|
||||
use cultnet_rs::Selection;
|
||||
use eureka_state::{ClientError, HuginnClient};
|
||||
use huginn_mind::epiphany_pipeline::{PipelineDocument, PipelineRef, Short, Slug};
|
||||
use huginn_mind::eureka_pipeline::{PipelineDocument, PipelineRef, Short, Slug};
|
||||
use huginn_mind::{
|
||||
Faculty, HuginnMindRequest, HuginnMindResponse, PipelineAdmissionBatch, PipelineProvenance, SemanticQuery,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use cultnet_rs::{
|
|||
use eureka_state::{ClientError, HuginnClient, MAX_REQUEST_BYTES};
|
||||
use huginn_daemon::serve::MAX_RESPONSE_BYTES;
|
||||
use huginn_mind::envelope::{OperationFailure, encode_failure, encode_request, encode_response};
|
||||
use huginn_mind::epiphany_pipeline::{
|
||||
use huginn_mind::eureka_pipeline::{
|
||||
AuthorityMap, CodeLocation, CutDelete, CutVerification, FileChange, Line, NegativeCheck, OrgRepo,
|
||||
PipelineCutSpec, PipelineDocument, PipelineKind, PipelineRef, Short, StructuralDelta, Title,
|
||||
VerificationTest,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use chrono::Utc;
|
|||
use cultnet_rs::{CultNetMessage, CultNetRudpServerEvent, CultNetWireContract, decode_cultnet_message_from_slice};
|
||||
use huginn_daemon::serve::{bind, run, schema_registry};
|
||||
use huginn_daemon::{Daemon, Handled, Hits, IndexSink, SearchTicket, ServeOptions};
|
||||
use huginn_mind::epiphany_pipeline::{
|
||||
use huginn_mind::eureka_pipeline::{
|
||||
Date, DocRef, OrgRepo, PipelineCampaign, PipelineDocument, PipelineInstance, PipelineRef, PipelineStewardship, Sha, Short, Slug,
|
||||
Title,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use std::time::Duration;
|
|||
|
||||
use huginn_mind::envelope::{OperationFailure, encode_failure, encode_response};
|
||||
use huginn_mind::{HuginnMindRequest, HuginnMindResponse};
|
||||
use huginn_mind::epiphany_pipeline::{Date, PipelineDocument, PipelineKind, PipelineRef, Short};
|
||||
use huginn_mind::eureka_pipeline::{Date, PipelineDocument, PipelineKind, PipelineRef, Short};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
mod common;
|
||||
|
|
@ -536,7 +536,7 @@ fn initialize_advertises_tools() {
|
|||
/// value `pipeline_key` computes for that document, never a re-built one.
|
||||
#[test]
|
||||
fn admit_carries_the_leaf_key_refusal_with_its_field_and_value() {
|
||||
use huginn_mind::epiphany_pipeline::{PipelineResolution, ResolutionOutcome, pipeline_key};
|
||||
use huginn_mind::eureka_pipeline::{PipelineResolution, ResolutionOutcome, pipeline_key};
|
||||
|
||||
let (root, daemon) = mind(vec![vec![stewardship()]]);
|
||||
let server = serve(root, daemon);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ path = "src/main.rs"
|
|||
anyhow = "1"
|
||||
base64 = "0.22"
|
||||
chrono = "0.4.44"
|
||||
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultnet-rs" }
|
||||
cultnet-rs.workspace = true
|
||||
huginn-mind = { path = "../huginn-mind" }
|
||||
rmp-serde = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use chrono::{DateTime, Utc};
|
||||
use cultnet_rs::Selection;
|
||||
use huginn_mind::epiphany_pipeline::{PipelineRef, Slug};
|
||||
use huginn_mind::eureka_pipeline::{PipelineRef, Slug};
|
||||
use huginn_mind::wire::{HuginnMindRequest, HuginnMindResponse, IndexStatus};
|
||||
use huginn_mind::{Mind, MindRefusal, MindStore, PipelineAdmissionOutcome, SemanticQuery};
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ impl<S: MindStore, I: IndexSink<S>> Daemon<S, I> {
|
|||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use chrono::TimeZone;
|
||||
use huginn_mind::epiphany_pipeline::{
|
||||
use huginn_mind::eureka_pipeline::{
|
||||
AuthorityMap, CodeLocation, CutDelete, CutVerification, Date, DocRef, FileChange, Line, NegativeCheck,
|
||||
OrgRepo, PipelineCampaign, PipelineCutSpec, PipelineDocument, PipelineInstance, PipelineKind,
|
||||
PipelineQuestion, PipelineRuling, PipelineStewardship, QuestionOption, RulingAuthority, Sha, Short,
|
||||
|
|
@ -685,7 +685,7 @@ pub(crate) mod tests {
|
|||
assert_ne!(fullwidth, INSTANCE, "not the mind's own bytes");
|
||||
assert!(!fullwidth.is_ascii(), "outside the grammar: a Slug is dot-joined ASCII labels");
|
||||
|
||||
let expected = MindRefusal::Document(huginn_mind::epiphany_pipeline::PipelineRefusal::InvalidFormat {
|
||||
let expected = MindRefusal::Document(huginn_mind::eureka_pipeline::PipelineRefusal::InvalidFormat {
|
||||
field: "declared".into(),
|
||||
value: fullwidth.into(),
|
||||
});
|
||||
|
|
@ -715,7 +715,7 @@ pub(crate) mod tests {
|
|||
let (_root, mut daemon) = seeded();
|
||||
let fullwidth = "\u{FF59}ggdrasil";
|
||||
|
||||
let expected = MindRefusal::Document(huginn_mind::epiphany_pipeline::PipelineRefusal::InvalidFormat {
|
||||
let expected = MindRefusal::Document(huginn_mind::eureka_pipeline::PipelineRefusal::InvalidFormat {
|
||||
field: "declared".into(),
|
||||
value: fullwidth.into(),
|
||||
});
|
||||
|
|
@ -744,7 +744,7 @@ pub(crate) mod tests {
|
|||
// A reference whose kind and id disagree names no document any writer
|
||||
// could have composed; the leaf's own refusal says so.
|
||||
let invalid = PipelineRef { kind: PipelineKind::Question, id: "not a reference".into() };
|
||||
let malformed = MindRefusal::Document(huginn_mind::epiphany_pipeline::PipelineRefusal::InvalidFormat {
|
||||
let malformed = MindRefusal::Document(huginn_mind::eureka_pipeline::PipelineRefusal::InvalidFormat {
|
||||
field: "ref.id".into(),
|
||||
value: "not a reference".into(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ use std::sync::{Arc, Condvar, Mutex, MutexGuard};
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{Context, Result, bail, ensure};
|
||||
use huginn_mind::epiphany_pipeline::{PipelineKind, PipelineRef, Short, Slug};
|
||||
use huginn_mind::eureka_pipeline::{PipelineKind, PipelineRef, Short, Slug};
|
||||
use huginn_mind::wire::IndexStatus;
|
||||
use huginn_mind::{INDEX_TEXT_VERSION, IndexEntry, Mind, MindStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use std::time::Duration;
|
|||
|
||||
use anyhow::Result;
|
||||
use cultnet_rs::{CULTNET_OPERATION_CONNECTION_ID, CultMesh, CultMeshRudpSocketOptions, FieldPredicate, Selection};
|
||||
use huginn_mind::epiphany_pipeline::{
|
||||
use huginn_mind::eureka_pipeline::{
|
||||
Date, PipelineDocument, PipelineKind, PipelineQuestion, PipelineResolution, PipelineRef, PipelineRuling,
|
||||
QuestionOption, ResolutionOutcome, RulingAuthority, Short, Title,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ use cultnet_rs::{
|
|||
CultNetWireContract, answer_content_chunk_request, decode_cultnet_message_from_slice,
|
||||
encode_cultnet_message_to_vec, pack_content,
|
||||
};
|
||||
use huginn_mind::epiphany_pipeline::Slug;
|
||||
use huginn_mind::eureka_pipeline::Slug;
|
||||
use huginn_mind::wire::{
|
||||
DeferredAnswer, HuginnMindResponse, MIND_REQUEST_SCHEMA, MIND_REQUEST_SCHEMA_JSON, MIND_RESPONSE_SCHEMA,
|
||||
MIND_RESPONSE_SCHEMA_JSON, MIND_SERVICE_ID,
|
||||
|
|
@ -548,7 +548,7 @@ mod tests {
|
|||
FITTING_CHANGES, FITTING_CUT, INSTANCE, NoIndex, OTHER, WIDE_CHANGES, WIDE_CUT, batch, identity, now,
|
||||
open_unindexed, seeded_wide, slug,
|
||||
};
|
||||
use huginn_mind::epiphany_pipeline::{PipelineKind, PipelineRef};
|
||||
use huginn_mind::eureka_pipeline::{PipelineKind, PipelineRef};
|
||||
use huginn_mind::envelope::{FAILURE_SCHEMA, decode_response, encode_request};
|
||||
use crate::index::Backoff;
|
||||
use base64::Engine;
|
||||
|
|
|
|||
|
|
@ -6,20 +6,21 @@ license.workspace = true
|
|||
publish.workspace = true
|
||||
|
||||
# The organ's rule engine: storage, identity and admission of one instance's
|
||||
# mind. Document shape, bounds, formats and keys come from `epiphany-pipeline`;
|
||||
# the store comes from CultLib's Rust CultCache at the same rev Epiphany pins,
|
||||
# so the leaf's wrappers and this crate's cache are one `cultcache-rs`. The
|
||||
# mind. Document shape, bounds, formats and keys come from this workspace's
|
||||
# `eureka-pipeline` (crates/eureka-pipeline);
|
||||
# the store comes from CultLib's Rust CultCache at the workspace's one CultLib
|
||||
# revision, so the leaf's wrappers and this crate's cache are one `cultcache-rs`. The
|
||||
# read side selects through CultNet's typed selection (`cultnet-rs`, the same
|
||||
# rev the daemon pins): the selection's vocabulary and evaluator are the
|
||||
# revision): the selection's vocabulary and evaluator are the
|
||||
# substrate's, this crate supplies the rows. No socket, no index: those are the
|
||||
# daemon's (Cut 10, Cut 11).
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
base64 = "0.22"
|
||||
chrono = "0.4.44"
|
||||
cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultcache-rs" }
|
||||
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultnet-rs" }
|
||||
epiphany-pipeline = { git = "https://github.com/GameCult/Epiphany.git", rev = "ef9568655e75bb6cb362a038e2739a5ac5e777ee", package = "epiphany-pipeline" }
|
||||
cultcache-rs.workspace = true
|
||||
cultnet-rs.workspace = true
|
||||
eureka-pipeline = { path = "../eureka-pipeline" }
|
||||
rmp-serde = "1"
|
||||
schemars = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ use std::collections::BTreeSet;
|
|||
|
||||
use chrono::{DateTime, Utc};
|
||||
use cultcache_rs::{CultCache, CultCacheEnvelope};
|
||||
use epiphany_pipeline::{
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, FindingConfidence, Line, PipelineDocument, PipelineKind, PipelineRef, PipelineRefusal,
|
||||
PipelineResolution, PipelineStewardship, ResolutionOutcome, RulingAuthority, Short, Slug, pipeline_key,
|
||||
validate_pipeline_write_envelope,
|
||||
|
|
@ -180,7 +180,7 @@ impl<S: MindStore> Mind<S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The bound `epiphany_pipeline::Short` declares, in UTF-8 bytes. The leaf
|
||||
/// The bound `eureka_pipeline::Short` declares, in UTF-8 bytes. The leaf
|
||||
/// exposes no validator for it, so `short_bound_is_the_leafs_own` pins this to
|
||||
/// the `maxLength` its schema publishes.
|
||||
const SHORT_MAX: usize = 200;
|
||||
|
|
@ -371,7 +371,7 @@ fn check(docs: &Docs, staged: &Staged, mind: &Slug) -> Result<(), MindRefusal> {
|
|||
Ok(())
|
||||
}
|
||||
D::Target(target) => {
|
||||
let predecessor = D::Target(epiphany_pipeline::PipelineTarget { revision: target.revision.wrapping_sub(1), ..target.clone() });
|
||||
let predecessor = D::Target(eureka_pipeline::PipelineTarget { revision: target.revision.wrapping_sub(1), ..target.clone() });
|
||||
revision_rule(docs, K::Target, key, target.revision, &predecessor)?;
|
||||
unique_labels("target.invariants", target.invariants.iter().map(|invariant| invariant.label.0.as_str()))
|
||||
}
|
||||
|
|
@ -429,7 +429,7 @@ fn check(docs: &Docs, staged: &Staged, mind: &Slug) -> Result<(), MindRefusal> {
|
|||
return Err(MindRefusal::UnknownDependency { cut: dependency.0.clone() });
|
||||
}
|
||||
}
|
||||
let predecessor = D::CutSpec(epiphany_pipeline::PipelineCutSpec { revision: spec.revision.wrapping_sub(1), ..spec.clone() });
|
||||
let predecessor = D::CutSpec(eureka_pipeline::PipelineCutSpec { revision: spec.revision.wrapping_sub(1), ..spec.clone() });
|
||||
revision_rule(docs, K::CutSpec, key, spec.revision, &predecessor)
|
||||
}
|
||||
D::CutReport(report) => {
|
||||
|
|
@ -704,7 +704,7 @@ mod tests {
|
|||
use crate::receipt::{DocumentVersion, Faculty, HuginnCommitReceipt};
|
||||
use crate::store::test_stores::{MemoryStore, RefusingStore, SwapCommand};
|
||||
use cultcache_rs::DatabaseEntry;
|
||||
use epiphany_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument as D, PipelineKind as K};
|
||||
use eureka_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument as D, PipelineKind as K};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
fn receipt_count<S: MindStore>(mind: &Mind<S>) -> usize {
|
||||
|
|
@ -833,7 +833,7 @@ mod tests {
|
|||
fn provenance_text_is_the_leafs_title_rule() {
|
||||
let leaf_accepts = |text: &str| {
|
||||
let D::Campaign(mut campaign) = campaign(&[REPO]) else { unreachable!() };
|
||||
campaign.title = epiphany_pipeline::Title(text.into());
|
||||
campaign.title = eureka_pipeline::Title(text.into());
|
||||
D::Campaign(campaign).validate().is_ok()
|
||||
};
|
||||
let mine_accepts = |text: &str| {
|
||||
|
|
@ -1280,7 +1280,7 @@ mod tests {
|
|||
let mut steward = seeded();
|
||||
committed(admit(&mut steward, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[])]));
|
||||
let D::HandOff(mut back) = hand_off(OTHER_INSTANCE, INSTANCE, REPO, &[]) else { panic!() };
|
||||
back.handed_on = epiphany_pipeline::Date("2026-09-17".into());
|
||||
back.handed_on = eureka_pipeline::Date("2026-09-17".into());
|
||||
committed(admit(&mut steward, vec![D::HandOff(back)]));
|
||||
assert_eq!(
|
||||
refusal(admit(&mut steward, vec![stewardship_n(INSTANCE, REPO, 4)])),
|
||||
|
|
@ -1366,7 +1366,7 @@ mod tests {
|
|||
// Handed back, the mind stewards it again, as `n2`. No field names a
|
||||
// return: this is the same hand-off shape with the instances swapped.
|
||||
let D::HandOff(mut back) = hand_off(OTHER_INSTANCE, INSTANCE, REPO, &[]) else { panic!() };
|
||||
back.handed_on = epiphany_pipeline::Date("2026-09-17".into());
|
||||
back.handed_on = eureka_pipeline::Date("2026-09-17".into());
|
||||
let (_, writes) = committed(admit(&mut mind, vec![D::HandOff(back)]));
|
||||
let again = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n2");
|
||||
assert_eq!(writes, vec![
|
||||
|
|
@ -1375,7 +1375,7 @@ mod tests {
|
|||
]);
|
||||
let Some(D::Stewardship(taken)) = mind.get(K::Stewardship, &again).unwrap() else { panic!() };
|
||||
assert_eq!(taken.sequence, 2);
|
||||
assert_eq!(taken.assigned_on, epiphany_pipeline::Date("2026-09-17".into()));
|
||||
assert_eq!(taken.assigned_on, eureka_pipeline::Date("2026-09-17".into()));
|
||||
assert!(mind.envelope(K::Stewardship, &first).is_some(), "the first assignment is still readable by key");
|
||||
}
|
||||
|
||||
|
|
@ -1388,7 +1388,7 @@ mod tests {
|
|||
let mut mind = seeded();
|
||||
committed(admit(&mut mind, vec![hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[])]));
|
||||
let D::HandOff(mut back) = hand_off(OTHER_INSTANCE, INSTANCE, REPO, &[]) else { panic!() };
|
||||
back.handed_on = epiphany_pipeline::Date("2026-09-17".into());
|
||||
back.handed_on = eureka_pipeline::Date("2026-09-17".into());
|
||||
committed(admit(&mut mind, vec![D::HandOff(back)]));
|
||||
let first = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n1");
|
||||
let again = format!("{INSTANCE}:stewardship:gamecult_-epiphany.n2");
|
||||
|
|
@ -1434,7 +1434,7 @@ mod tests {
|
|||
let withdrawal = format!("{INSTANCE}:resolution:stewardship.gamecult_-epiphany.n1.n1");
|
||||
committed(admit(&mut mind, vec![resolution(r(K::Resolution, &withdrawal), withdrawn())]));
|
||||
let D::HandOff(mut again) = hand_off(INSTANCE, OTHER_INSTANCE, REPO, &[]) else { panic!() };
|
||||
again.handed_on = epiphany_pipeline::Date("2026-09-17".into());
|
||||
again.handed_on = eureka_pipeline::Date("2026-09-17".into());
|
||||
let (_, writes) = committed(admit(&mut mind, vec![D::HandOff(again)]));
|
||||
assert_eq!(writes, vec![
|
||||
r(K::HandOff, &format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.gamecult_-epiphany.2026-09-17")),
|
||||
|
|
@ -1444,7 +1444,7 @@ mod tests {
|
|||
|
||||
fn hand_off_on(from: &str, to: &str, day: &str) -> D {
|
||||
let D::HandOff(mut hand_off) = hand_off(from, to, REPO, &[]) else { panic!() };
|
||||
hand_off.handed_on = epiphany_pipeline::Date(day.into());
|
||||
hand_off.handed_on = eureka_pipeline::Date(day.into());
|
||||
D::HandOff(hand_off)
|
||||
}
|
||||
|
||||
|
|
@ -1905,7 +1905,7 @@ mod tests {
|
|||
report.repo = repo(OTHER_REPO);
|
||||
assert_eq!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::SpecMismatch { field: "repo".into() });
|
||||
let mut report = cut_report("1", 1);
|
||||
report.range.head = epiphany_pipeline::Sha("abcdef0".into());
|
||||
report.range.head = eureka_pipeline::Sha("abcdef0".into());
|
||||
assert_eq!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::RangeOutsideCommits { head: "abcdef0".into() });
|
||||
committed(admit(&mut mind, vec![resolution(r(K::CutSpec, &id("cut_spec", "cut-1.r1")), withdrawn())]));
|
||||
assert_eq!(
|
||||
|
|
@ -2008,18 +2008,18 @@ mod tests {
|
|||
committed(admit(&mut mind, vec![D::CutSpec(cut_spec("1", 1))]));
|
||||
// The commit is spelled short and the head full, and the reverse.
|
||||
let mut report = cut_report("1", 1);
|
||||
report.range.head = epiphany_pipeline::Sha(full.into());
|
||||
report.range.head = eureka_pipeline::Sha(full.into());
|
||||
committed(admit(&mut mind, vec![D::CutReport(report)]));
|
||||
let mut report = cut_report("1", 2);
|
||||
report.commits[0].sha = epiphany_pipeline::Sha(full.into());
|
||||
report.commits[0].sha = eureka_pipeline::Sha(full.into());
|
||||
committed(admit(&mut mind, vec![D::CutReport(report)]));
|
||||
// A head that is no commit's prefix, long or short, is outside.
|
||||
let mut report = cut_report("1", 3);
|
||||
report.range.head = epiphany_pipeline::Sha("5f98229d0123456789abcdef0123456789abcdef".into());
|
||||
report.range.head = eureka_pipeline::Sha("5f98229d0123456789abcdef0123456789abcdef".into());
|
||||
assert!(matches!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::RangeOutsideCommits { .. }));
|
||||
let mut report = cut_report("1", 3);
|
||||
report.commits[0].sha = epiphany_pipeline::Sha(full.into());
|
||||
report.range.head = epiphany_pipeline::Sha("5f98229".into());
|
||||
report.commits[0].sha = eureka_pipeline::Sha(full.into());
|
||||
report.range.head = eureka_pipeline::Sha("5f98229".into());
|
||||
assert!(matches!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::RangeOutsideCommits { .. }));
|
||||
}
|
||||
|
||||
|
|
@ -2115,16 +2115,16 @@ mod tests {
|
|||
struct Rangeless {
|
||||
campaign: Slug,
|
||||
verdict: Short,
|
||||
label: epiphany_pipeline::Label,
|
||||
label: eureka_pipeline::Label,
|
||||
confidence: FindingConfidence,
|
||||
severity: epiphany_pipeline::FindingSeverity,
|
||||
severity: eureka_pipeline::FindingSeverity,
|
||||
claim: Line,
|
||||
invariants: Vec<epiphany_pipeline::Label>,
|
||||
locations: Vec<epiphany_pipeline::CodeLocation>,
|
||||
failure_scenario: epiphany_pipeline::Para,
|
||||
evidence: Vec<epiphany_pipeline::Evidence>,
|
||||
precedents: Vec<epiphany_pipeline::ForeignRef>,
|
||||
origin: epiphany_pipeline::FindingOrigin,
|
||||
invariants: Vec<eureka_pipeline::Label>,
|
||||
locations: Vec<eureka_pipeline::CodeLocation>,
|
||||
failure_scenario: eureka_pipeline::Para,
|
||||
evidence: Vec<eureka_pipeline::Evidence>,
|
||||
precedents: Vec<eureka_pipeline::ForeignRef>,
|
||||
origin: eureka_pipeline::FindingOrigin,
|
||||
}
|
||||
let whole = finding("1", 1, "F1", FindingConfidence::Plausible);
|
||||
let rangeless = Rangeless {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
//! `stored_at`.
|
||||
|
||||
use cultcache_rs::CultCacheEnvelope;
|
||||
use epiphany_pipeline::{
|
||||
use eureka_pipeline::{
|
||||
OrgRepo, PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, PipelineStewardship, ResolutionOutcome,
|
||||
Short, Slug,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use cultcache_rs::CultCacheEnvelope;
|
||||
use epiphany_pipeline::{
|
||||
use eureka_pipeline::{
|
||||
AuthorityMap, ClaimOutcome, CodeLocation, CommitRange, CutVerification, Date, DocRef, Evidence, EvidenceKind,
|
||||
FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, MutationRecord, OrgRepo, PipelineCampaign,
|
||||
PipelineCutReport, PipelineCutSpec, PipelineDocument, PipelineFinding, PipelineFollowUp, PipelineHandOff,
|
||||
|
|
@ -32,7 +32,7 @@ pub(crate) const NEAR_INSTANCE: &str = "yggdrasix";
|
|||
pub(crate) const PREFIXED_INSTANCE: &str = "yggdrasil-two";
|
||||
/// A foreign instance differing from `INSTANCE` in case alone. A `Slug` is
|
||||
/// dot-joined labels, each `[A-Za-z0-9_-]{1,64}`, bounded to 64 bytes whole
|
||||
/// (`epiphany_pipeline::dotted_text`), so this is a name a client may declare
|
||||
/// (`eureka_pipeline::dotted_text`), so this is a name a client may declare
|
||||
/// and a mind may hold: two minds whose names differ only in case are two
|
||||
/// minds, and a comparison that folds case answers one of them for the other.
|
||||
pub(crate) const CASED_INSTANCE: &str = "Yggdrasil";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! how a change to it makes every projection rebuild. No network, no JSON, no
|
||||
//! store handle: the daemon owns the vector store and the embedder.
|
||||
|
||||
use epiphany_pipeline::{PipelineDocument, PipelineKind, PipelineRef};
|
||||
use eureka_pipeline::{PipelineDocument, PipelineKind, PipelineRef};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::docs::Docs;
|
||||
|
|
@ -154,7 +154,7 @@ mod tests {
|
|||
use crate::fixtures::*;
|
||||
use crate::receipt::Faculty;
|
||||
use crate::store::test_stores::MemoryStore;
|
||||
use epiphany_pipeline::{
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, Deviation, FindingConfidence, PipelineDocument as D, PipelineKind as K, RulingAuthority,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
//! so the daemon and the client share one set
|
||||
//! of types.
|
||||
//!
|
||||
//! Document shape, bounds, formats and keys are `epiphany-pipeline`'s; the
|
||||
//! Document shape, bounds, formats and keys are `eureka-pipeline`'s; the
|
||||
//! organ registers, prepares, decodes and validates through the leaf's four
|
||||
//! doors and never re-derives a key. The store is CultLib's owned redb
|
||||
//! CultCache, whose lifetime-long exclusive lock is the single-writer
|
||||
|
|
@ -59,4 +59,4 @@ pub use wire::{
|
|||
/// The leaf, whole, through the one crate that pins its rev: the daemon and
|
||||
/// the client name `Slug`, `PipelineRef` and the document types from here, so
|
||||
/// one `[dependencies]` entry decides which revision the workspace speaks.
|
||||
pub use epiphany_pipeline;
|
||||
pub use eureka_pipeline;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use cultcache_rs::{CultCache, CultCacheEnvelope, DatabaseEntry, OwnedRedbMessagePackBackingStore};
|
||||
use cultnet_rs::CursorKey;
|
||||
use epiphany_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument, PipelineKind, Slug, register_pipeline_document_types};
|
||||
use eureka_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineDocument, PipelineKind, Slug, register_pipeline_document_types};
|
||||
|
||||
use crate::receipt::HuginnCommitReceipt;
|
||||
use crate::refusal::MindRefusal;
|
||||
|
|
@ -48,7 +48,7 @@ pub(crate) fn unavailable(error: anyhow::Error) -> MindRefusal {
|
|||
}
|
||||
|
||||
/// A declared name's grammar, checked before its bytes are compared to
|
||||
/// anything or reach the filesystem. `Slug` is `epiphany_pipeline`'s,
|
||||
/// anything or reach the filesystem. `Slug` is `eureka_pipeline`'s,
|
||||
/// dot-joined ASCII labels, and the leaf now opens a public door onto its own
|
||||
/// check, `Slug::validate_slug`, on the pattern of `PipelineRef::validate_ref`
|
||||
/// (Self's ruling on the F6 fork): the grammar is the leaf's, never
|
||||
|
|
@ -64,7 +64,7 @@ pub(crate) fn unavailable(error: anyhow::Error) -> MindRefusal {
|
|||
/// old wrapper's field, a client never sent) and not a label fragment.
|
||||
fn require_grammatical_slug(field: &str, declared: &Slug) -> Result<(), MindRefusal> {
|
||||
declared.validate_slug().map_err(|_| {
|
||||
MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat {
|
||||
MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat {
|
||||
field: field.into(),
|
||||
value: declared.0.clone(),
|
||||
})
|
||||
|
|
@ -712,7 +712,7 @@ mod tests {
|
|||
let escaping = Slug("..\\..\\escaped".into());
|
||||
assert_eq!(
|
||||
Mind::open(&inner, &escaping).err(),
|
||||
Some(MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat {
|
||||
Some(MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat {
|
||||
field: "instance".into(),
|
||||
value: escaping.0.clone(),
|
||||
}))
|
||||
|
|
@ -734,7 +734,7 @@ mod tests {
|
|||
let bad = Slug("not a slug!".into());
|
||||
assert_eq!(
|
||||
Mind::open_with(store.clone(), &bad).err(),
|
||||
Some(MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat {
|
||||
Some(MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat {
|
||||
field: "instance".into(),
|
||||
value: bad.0.clone(),
|
||||
}))
|
||||
|
|
@ -753,7 +753,7 @@ mod tests {
|
|||
let bad = Slug("not a slug!".into());
|
||||
assert_eq!(
|
||||
mind.require_instance(&bad).err(),
|
||||
Some(MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat {
|
||||
Some(MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat {
|
||||
field: "declared".into(),
|
||||
value: bad.0.clone(),
|
||||
}))
|
||||
|
|
@ -773,7 +773,7 @@ mod tests {
|
|||
assert!(!declared.0.is_ascii(), "U+2010 is not ASCII, unlike plain '-'");
|
||||
assert_eq!(
|
||||
Mind::open_with(MemoryStore::new(), &declared).err(),
|
||||
Some(MindRefusal::Document(epiphany_pipeline::PipelineRefusal::InvalidFormat {
|
||||
Some(MindRefusal::Document(eureka_pipeline::PipelineRefusal::InvalidFormat {
|
||||
field: "instance".into(),
|
||||
value: declared.0.clone(),
|
||||
}))
|
||||
|
|
@ -789,7 +789,7 @@ mod tests {
|
|||
#[ignore = "reads and writes the scratch copy of a live state root named by HUGINN_MIND_SNAPSHOT"]
|
||||
fn snapshot_admits_and_reads_back() {
|
||||
use crate::fixtures::{admit, r, resolution, withdrawn};
|
||||
use epiphany_pipeline::{PipelineRef, ResolutionOutcome, Short, pipeline_key};
|
||||
use eureka_pipeline::{PipelineRef, ResolutionOutcome, Short, pipeline_key};
|
||||
|
||||
let root = std::env::var("HUGINN_MIND_SNAPSHOT").expect("HUGINN_MIND_SNAPSHOT names a scratch copy of the state root");
|
||||
let mut mind = Mind::open(Path::new(&root), &slug("eureka")).unwrap();
|
||||
|
|
@ -816,7 +816,7 @@ mod tests {
|
|||
.unwrap_or_else(|| panic!("{} does not view", envelope.key));
|
||||
assert_eq!(view.document, stored, "{}", envelope.key);
|
||||
if let PipelineDocument::Resolution(held) = &stored
|
||||
&& !matches!(held.outcome, epiphany_pipeline::ResolutionOutcome::Withdrawn { .. })
|
||||
&& !matches!(held.outcome, eureka_pipeline::ResolutionOutcome::Withdrawn { .. })
|
||||
&& !envelope.key.contains(":resolution:resolution.")
|
||||
{
|
||||
resolutions.push(envelope.key.clone());
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use std::collections::BTreeMap;
|
|||
use cultnet_rs::{
|
||||
Cursor, EdgeAnchor, EdgeMatch, Evaluation, LIMIT_MAX, LIMIT_MIN, PROJECTION_DOCUMENT, Row, Selection, select, validate,
|
||||
};
|
||||
use epiphany_pipeline::{
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, CommitRange, Date, FindingConfidence, FindingOrigin, FindingSeverity, Label, Line, OrgRepo,
|
||||
PipelineDocument, PipelineKind, PipelineRef, PipelineResolution, ResolutionOutcome, RulingAuthority, Sha, Short,
|
||||
Slug, Title,
|
||||
|
|
@ -621,7 +621,7 @@ mod tests {
|
|||
use crate::mind::schema_cache;
|
||||
use crate::store::test_stores::MemoryStore;
|
||||
use chrono::{TimeZone, Utc};
|
||||
use epiphany_pipeline::{
|
||||
use eureka_pipeline::{
|
||||
ClaimOutcome, FindingConfidence, PipelineDocument as D, PipelineKind as K, PipelineRefusal, ResolutionOutcome,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
use chrono::{DateTime, SecondsFormat, Utc};
|
||||
use cultcache_rs::{CultCacheEnvelope, DatabaseEntry};
|
||||
use epiphany_pipeline::{PipelineKind, PipelineRef, Short, Slug};
|
||||
use eureka_pipeline::{PipelineKind, PipelineRef, Short, Slug};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
|
@ -47,9 +47,9 @@ pub enum Faculty {
|
|||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct PipelineProvenance {
|
||||
pub faculty: Faculty,
|
||||
pub agent: epiphany_pipeline::Short,
|
||||
pub session: epiphany_pipeline::Short,
|
||||
pub tool: epiphany_pipeline::Short,
|
||||
pub agent: eureka_pipeline::Short,
|
||||
pub session: eureka_pipeline::Short,
|
||||
pub tool: eureka_pipeline::Short,
|
||||
}
|
||||
|
||||
/// The exact bytes of one stored document, as read or as written.
|
||||
|
|
@ -311,7 +311,7 @@ mod tests {
|
|||
use crate::mind::schema_cache;
|
||||
use crate::store::test_stores::MemoryStore;
|
||||
use cultcache_rs::{CacheBackingStore, PushAllOptions};
|
||||
use epiphany_pipeline::{PipelineDocument, PipelineKind, PipelineRefusal};
|
||||
use eureka_pipeline::{PipelineDocument, PipelineKind, PipelineRefusal};
|
||||
|
||||
/// A memory store that keeps the replacement set of every swap it was
|
||||
/// asked for, so a test can count the commit's calls as well as read
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
//! serialises it, the tools (Cut 13) show it.
|
||||
|
||||
use cultnet_rs::SelectionRefusal;
|
||||
use epiphany_pipeline::{PipelineKind, PipelineRefusal};
|
||||
use eureka_pipeline::{PipelineKind, PipelineRefusal};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use cultnet_rs::{Row, RowSet, Selection};
|
||||
use epiphany_pipeline::{OrgRepo, Label, PipelineDocument, PipelineKind, PipelineRef, ResolutionOutcome, Slug};
|
||||
use eureka_pipeline::{OrgRepo, Label, PipelineDocument, PipelineKind, PipelineRef, ResolutionOutcome, Slug};
|
||||
|
||||
use crate::docs::{CitationRole, Held, kind_of_id, kind_of_type};
|
||||
use crate::query::PipelineDocumentView;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
use cultcache_rs::DatabaseEntry;
|
||||
use cultnet_rs::{CultMeshCdnArtifactManifest, Selection};
|
||||
use epiphany_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineKind, PipelineRef, Slug};
|
||||
use eureka_pipeline::{PIPELINE_SCHEMA_EPOCH, PipelineKind, PipelineRef, Slug};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
|||
902
docs/history/eureka-body-plane-cut.md
Normal file
902
docs/history/eureka-body-plane-cut.md
Normal file
|
|
@ -0,0 +1,902 @@
|
|||
<!-- Provenance: copied verbatim from GameCult/Epiphany ef956865 notes/eureka-body-plane-cut.md; text below this line is unchanged. -->
|
||||
# The body plane for oversize answers: cut map
|
||||
|
||||
Date: 2026-09-22. Imagination output (Opus). Nothing here is committed and no
|
||||
repository was edited. Self places and commits this file; the natural homes
|
||||
are a section of `F:\Projects\Epiphany\notes\eureka-pipeline-state-cut.md`
|
||||
(the campaign that owns the ruling) with the CultLib half copied or linked
|
||||
into `F:\Projects\CultLib\docs\`, because the cut spans the organ and
|
||||
CultMesh.
|
||||
|
||||
Status: cut map for three cuts, **BP-1** (CultLib: the content plane in the
|
||||
Rust runtime), **BP-2** (the pin move that lets Huginn see it) and **BP-3**
|
||||
(Huginn: an oversize answer is deferred to the body plane).
|
||||
|
||||
**2026-09-29 (Self):** both questions in section 8 are ruled (Q-BP1 A,
|
||||
Q-BP2 A, 2026-09-22). Selection Cut 1 merged to CultLib `main` at `dbee044`,
|
||||
so section 6's collision is gone. **BP-1 is in Hands** (Sonnet) on CultLib
|
||||
`cultmesh/rust-content-plane`, based on `268e0ef`, with two overrides to
|
||||
section 3: no committed mutation entries (the operator retired hand-written
|
||||
harnesses on 2026-09-22; each rule's test is shown red once by a scratch
|
||||
mutant instead), and verification on Yggdrasil through the stopgap rather
|
||||
than on the workstation. The Pins table below is as of 2026-09-22.
|
||||
|
||||
**BP-1 landed** (Sonnet, 2026-09-29) on `cultmesh/rust-content-plane` at
|
||||
`29c3e4c`, `11485fb` and `8a66b1d`. `cultnet-rs` went from 264 to 279 tests,
|
||||
plus 3 C# vector tests, and all 22 listed mutants went red. Size: +486 source
|
||||
lines against ~330 estimated, and +778 test lines against ~350.
|
||||
**Soul (Opus) will merge it after a fix batch, now in Hands:**
|
||||
- F1: a flood of zero-size chunks makes the client send unbounded requests;
|
||||
- F2: malformed requests are refused at decode, so a Rust server answers
|
||||
nothing where the reference answers `found:false`. **This was the map's
|
||||
defect**: section 3 ordered decode-time request validation, which
|
||||
contradicts "a failure is an answer, never silence". Refusals belong to the
|
||||
answer function;
|
||||
- F3: `BTreeMap` metadata breaks byte identity for keys that are not sorted;
|
||||
- F4: hashes are normalised only after requests have gone out;
|
||||
- F5: one error spelling differs from the reference;
|
||||
- F6: 8 surviving mutants.
|
||||
Held: byte identity in both directions beyond the fixture, verification,
|
||||
serving, validation order, and one path. The empty-body deviation (one
|
||||
zero-length chunk) is the reference's behaviour, so the spec was wrong there too.
|
||||
|
||||
**The ruling this map is written under** (operator, 2026-09-17, recorded in
|
||||
the Epiphany map at about line 368): an answer too large for the control
|
||||
plane travels over CultMesh's content/body transfer. The control plane
|
||||
carries a summary and a reference to the body. Raising the window
|
||||
(1,228,800 bytes) and bounding documents smaller were both rejected. **The
|
||||
typed oversize refusal stays as the backstop.** The ruling says the cut "spans
|
||||
the organ and CultMesh", and nothing about it was in Cut 10 except the refusal.
|
||||
|
||||
## Pins
|
||||
|
||||
| Repo | Branch | HEAD | State |
|
||||
|---|---|---|---|
|
||||
| CultLib | `main` (checked out; the operator's shell is on `main`, and the gitStatus snapshot's `cultnet/selection-cut1` is the locked worktree `.claude/worktrees/agent-af580008561975a4c`, also at `b3d9cf7`) | `b3d9cf7` | Dirty in `native/GameCult.Mesh.Quic.Native/*` and `scripts/mutate-cultmesh.mjs` from the QUIC campaign. **Not touched.** `git diff --stat a0813c6 b3d9cf7 -- packages/cultcache-rs packages/cultnet-rs packages/cultmesh-rs` is **empty**, so every Rust anchor below holds at Huginn's pin as well. |
|
||||
| CultLib selection Cut 1 | `cultnet/selection-cut1` | `b3d9cf7` + uncommitted work in the locked worktree | In Hands (another agent). The collision is in section 6. |
|
||||
| Huginn | `eureka/memory-organ` | `202e5e3` | Clean. Cut 10 and its two fix batches have landed. |
|
||||
| Epiphany | `codex/eureka-pipeline-state` | `2876a827` | Map read only. The leaf `epiphany-pipeline` is pinned by Huginn at `5cda0886` and pins `cultcache-rs` at `a0813c6`. |
|
||||
|
||||
## 0b. Model page: identity, lifecycle and authority
|
||||
|
||||
This table was written before any cut was mapped. Every persistent or
|
||||
long-lived kind the cut touches has a row, and no cell is empty.
|
||||
|
||||
| Kind | Identity (what names it) | Lifecycle (what happens to it over time) | Authority (who decides) |
|
||||
|---|---|---|---|
|
||||
| **Deferred body**: the encoded bytes of one `HuginnMindResponse` that did not fit one send | The SHA-256 of those bytes, lowercase hex: the manifest's `contentHash`. Two identical answers are one body. | Created when an answer's encoded envelope exceeds the window and the body is within the deferral bound. A second deferral of the same bytes refreshes it; it is never duplicated. Every chunk served also refreshes it (sliding expiry). It expires when it has been untouched for the TTL, and it is evicted least-recently-touched first when the retained total exceeds the budget. It is lost when the process exits, and the client asks again. It is never written to the mind, a file or any `.cc` store. | The daemon's serve module (`huginn-daemon`) decides to defer and holds the retention store. The mind decides what the answer *is* and knows nothing about deferral. |
|
||||
| **Chunk** | The SHA-256 of the chunk's bytes. Its record key is `mesh:cdn:chunk:<hash>`, which is the C# reference's spelling (`CultMeshCdnArtifactChunk.CreateRecordKey`, `CultMeshCdn.cs:120`). | It lives exactly as long as some retained body references it. A chunk shared by two bodies is stored once and stays until both are gone. | Chunking (the boundaries and the hash) belongs to `cultnet-rs` `pack_content`. Retention belongs to the daemon's store. |
|
||||
| **Reference (manifest)** | `gamecult.mesh.cdn_artifact_manifest.v1`, the reference's type, reused and not reinvented. `contentHash` identifies the body; `artifactId` and the other fields describe it and never identify it. | Transient: minted with the body, carried once inside the deferred response, and never stored by the daemon or the client. It stays valid while the body is retained. After that a chunk request answers `found: false` and the client re-asks the operation. | `cultnet-rs` owns the shape and the validation rules, with parity to `CultMeshCdn.ValidateManifestShape` (`CultMeshCdn.cs:499-526`). The daemon fills it in. |
|
||||
| **Deferred response**: the control-plane answer that stands in for an oversize one | Correlated by the operation envelope's `messageId`, exactly like every other answer. | One per request, sent once, and never deferred itself. It always fits: at the deferral bound it is about 41 KB, far under the window. | Only the daemon's serve module produces it. `Mind` never does. |
|
||||
| **Content chunk wire messages** `cultmesh.content_chunk_request.v1` / `.response.v1` | Their `schemaVersion` strings, both already defined by the reference (`CultNetSchemaMessages.cs:153,157`, classes `:1414-1446`). | Stateless: one request, one response. | The C# reference owns the spelling. `cultnet-rs` gains it with byte identity, pinned by vectors in both directions. |
|
||||
| **Vector fixtures** `contracts/cultmesh/content-vectors.cs-written.json` and `.rs-written.json` | File path plus the `label` of each vector. | Committed. Each writer regenerates its own file under `CULTNET_WRITE_VECTORS=1`. The file is expected to grow and is not pinned as final. | Each runtime owns its written file. Both runtimes' tests read both files. |
|
||||
| **Mind documents and receipts** | Unchanged: leaf keys and receipt ids. | Unchanged. **No body-plane state enters the mind.** | Unchanged: `huginn-mind` admission. |
|
||||
|
||||
Forks this page surfaced, which are sections 8's Q-BP1 and Q-BP2:
|
||||
|
||||
- **Authority over the physical path.** CultMesh's own doctrine forbids
|
||||
schema messages carrying bulk bodies and names the RUDP content path as
|
||||
legacy. That is Q-BP1.
|
||||
- **What the summary is.** The ruling says "a summary". Whether that means the
|
||||
reference's own facts or a per-operation typed summary decides whether
|
||||
`huginn-mind` grows new types. That is Q-BP2.
|
||||
|
||||
**Identity cannot be the request.** Re-deriving a body per chunk request from
|
||||
the mind would need no retention at all. But a chunk request names only a
|
||||
hash, so the daemon could not know which answer to recompute without a
|
||||
Huginn-specific request shape, and that would give up wire reuse with the
|
||||
reference. Rejected for that reason, not asked.
|
||||
|
||||
## 1. Body facts
|
||||
|
||||
Each fact is backed by a source read at the pins above or by a probe that
|
||||
ran code. The probes are in the scratchpad and nothing was committed:
|
||||
|
||||
- `bodyplane-cs/`: a net10.0 console against the built reference DLLs
|
||||
`bin/GameCult.{Networking,Mesh}/Debug/netstandard2.1/*.dll`, dated
|
||||
2026-09-16. The sources it depends on last moved at `f622ee1` (2026-08-18),
|
||||
so the DLLs match `b3d9cf7`. It emitted real reference bytes to
|
||||
`bodyplane-cs/out/`.
|
||||
- `bodyplane-rs/`: a crate against `packages/cultnet-rs` by path, release
|
||||
build. `src/main.rs` is the transport probe and `src/bin/parity.rs` the
|
||||
byte-parity probe.
|
||||
- `pinprobe/`: `cargo tree -d` over mixed CultLib git revisions.
|
||||
|
||||
### F1. Only the C# reference implements body or content transfer. The Rust client Huginn uses has no path today.
|
||||
|
||||
- **Source.** Across `packages/` there is no occurrence of
|
||||
`content_chunk_request`, `body_read_request`, `cdn_artifact_manifest`,
|
||||
`ContentChunk*` or `BodyRead*`, and no `cdn`, `chunkHash` or
|
||||
`artifact_manifest` in any `.rs`, `.ts`, `.py` or `.kt` source.
|
||||
`cultnet-rs` does not contain the word "body". What the other runtimes have
|
||||
is the *local* stream-body negotiation enum (`SharedMemory`,
|
||||
`CultCachePage`, `InlineBytes`, and so on): `cultmesh-rs/src/lib.rs:191-200`,
|
||||
`cultnet-py/.../cultmesh_contracts.py:434`, and in `cultmesh-ts` and
|
||||
`cultmesh-kotlin`. That is same-machine body mapping, not network transfer.
|
||||
- **Probe A (decode).** `cultnet_rs::decode_cultnet_message_from_slice` over
|
||||
bytes the C# reference emitted:
|
||||
- `content_chunk_request` gives *unknown variant
|
||||
`cultmesh.content_chunk_request.v1`*, because the closed enum
|
||||
(`contracts.rs:252-392`) lists 20 `cultnet.*` variants.
|
||||
- `content_chunk_response`, `body_read_response` and a real 256 KiB chunk
|
||||
response give *invalid type: byte array, expected any valid JSON value*.
|
||||
The non-raw decode path converts through `serde_json::Value`
|
||||
(`contracts.rs:404-411`), which cannot hold MessagePack `bin`. So adding
|
||||
the variants alone is not enough: both must ride the raw path that
|
||||
`document_put_raw` uses (`contracts.rs:771-854`).
|
||||
- **Probe E (on the wire).** A Rust hub sent the reference's own 262,357-byte
|
||||
chunk response raw on the `schema` channel. The transport delivered it, and
|
||||
the Rust client refused it in `receive_schema_message_once` with the same
|
||||
byte-array error.
|
||||
|
||||
**Conclusion:** no Rust runtime can request, answer, decode or verify a body
|
||||
or a content chunk today. Rust gets its first content-plane code in BP-1.
|
||||
|
||||
### F2. The reference has two planes, and only one of them fits this class of content.
|
||||
|
||||
- **The content plane.** It carries large immutable artifacts
|
||||
(`src/GameCult.Mesh/docs/content-sessions.md:1-3`). The reference is a
|
||||
`CultMeshCdnArtifactManifest` (`CultMeshCdn.cs:127-190`) listing
|
||||
content-addressed chunks (default 4 MiB, `CultMeshCdnPackOptions`
|
||||
`:44`). The chunk protocol is one bounded response per chunk
|
||||
(`content-sessions.md:44-46`). The transfer owner
|
||||
(`CultMeshContentTransferService`, `CultMeshContentTransfer.cs:115`) is the
|
||||
only owner of chunk verification, whole-body SHA-256 verification, resume
|
||||
and promotion. The preferred connector is TCP: a typed header, then raw
|
||||
bytes outside the envelope (`transport-planes.md:22-26`). It is plaintext
|
||||
and **restricted to explicit loopback development**
|
||||
(`transport-planes.md:103-106`). The RUDP content server
|
||||
(`CultMeshLegacyRudpContentServer`, `CultMeshContentSessions.cs:14-87`) is
|
||||
"basement priority and exists for compatibility and parity measurement"
|
||||
(`transport-planes.md:93-96`), and its connector is never installed by
|
||||
default (`CultMeshContentSessions.cs:97-116`, priority 10,000).
|
||||
- **The body plane proper.** It carries live, lease-bound generations of
|
||||
mutable bodies: `CultMeshNetworkBodyStore` (`CultMeshBodySessions.cs:19-219`,
|
||||
a 16 MiB default bound, 8 retained generations, HMAC capability tokens,
|
||||
expiry by lease). It is served **whole in one response**
|
||||
(`CultMeshBodyServer.HandleAsync`, `:241-272`, payload inline). There is no
|
||||
chunking.
|
||||
- **Why that decides it.** A pipeline answer is an immutable artifact, which
|
||||
is the content plane's stated class. The body plane's single response meets
|
||||
exactly the wall the ruling is routing around: probe B's whole-body send
|
||||
failed with `RUDP reliable send queue is full`. The operator's phrase
|
||||
"content/body transfer" covers both planes, and the source says which one
|
||||
this is. **This map uses the content plane's chunk protocol and manifest.**
|
||||
It is recorded as a reading of the source, not asked, because the body
|
||||
plane cannot carry this answer on any transport Huginn has.
|
||||
|
||||
### F3. What the wire carries. Decoded from real bytes and pinned at byte level.
|
||||
|
||||
| Message | Reference encoding (probe, hex prefix) | Fields in order |
|
||||
|---|---|---|
|
||||
| `cultmesh.content_chunk_request.v1` | named map `0x85`, 169 bytes for the sample | `schemaVersion`, `messageId`, `chunkHash` (64 hex), `recordKey` (may be empty), `expectedSizeBytes` (int32) |
|
||||
| `cultmesh.content_chunk_response.v1` | named map `0x87`, 179 bytes for a 4-byte payload | `schemaVersion`, `messageId`, `found`, `chunkHash`, `sizeBytes` (int32), `payload` (**MessagePack `bin`**), `error` (string, empty on success) |
|
||||
| `gamecult.mesh.cdn_artifact_manifest.v1` | **array** `0x9A` (10 integer-keyed fields), 1,097 bytes for 6 chunks | `[artifactId, kind, version, contentHash, sizeBytes(int64), mimeType, createdAtUtc, chunks[[chunkHash, offset(int64), sizeBytes(int32), recordKey]], tags[], metadata{}]` |
|
||||
| `cultmesh.body_read_response.v1` (not used) | named map `0x8B`, 11 fields | whole generation in `payload` |
|
||||
|
||||
Measured:
|
||||
|
||||
- A 256 KiB chunk response encodes to **262,357 bytes**, 213 bytes of
|
||||
overhead, so four fit in Huginn's 1,228,800-byte window.
|
||||
- The manifest for the real 1,315,551-byte case at 256 KiB chunks is 6 refs
|
||||
and 1,097 bytes.
|
||||
- **Probe parity:**
|
||||
- A Rust `rmpv` map written in the reference's key order is **byte-identical**
|
||||
to the reference's request (169 = 169) and response (179 = 179).
|
||||
- A Rust tuple-shaped manifest decodes the reference's bytes and re-encodes
|
||||
them **byte-identically**.
|
||||
- A Rust re-pack of the same body with SHA-256 at 256 KiB reproduces every
|
||||
chunk reference (hash, offset, size, record key) and the content hash.
|
||||
- `rmp_serde::to_vec_named`, the encoder Huginn uses for responses, still
|
||||
writes a tuple-shaped type as an array (`0x9A`). So the reference's array
|
||||
form survives inside Huginn's named-map payload **only if** the Rust type
|
||||
serialises as a tuple.
|
||||
|
||||
### F4. Chunked delivery works on Huginn's exact transport. Pipelining past the window does not.
|
||||
|
||||
Probes B, C and D used a hub configured exactly as Huginn's `serve::bind`
|
||||
(`serve.rs:135-143`: fragment 1200, pending 1024, one settled session) and the
|
||||
real 1,315,551-byte body, with chunks carried as base64 inside operation
|
||||
responses, the only bytes-bearing message Rust has today.
|
||||
|
||||
- **One reply carrying the whole body** (a 1,754,254-byte envelope):
|
||||
`RUDP reliable send queue is full`, returned to the sender only.
|
||||
- **Sequential request, then reply, one chunk at a time** at 256, 512 and 768
|
||||
KiB: delivered whole and SHA-equal, in 1.56 s, 1.54 s and 1.45 s on loopback
|
||||
(about 0.85 MB/s, paced by the 32-packet send window,
|
||||
`rudp.rs:34`). **Chunk size barely moves throughput.**
|
||||
- **900 KiB** as base64 is a 1,228,983-byte envelope, which exceeds the window,
|
||||
so the send fails.
|
||||
- **Six 256 KiB chunks sent without waiting:** four accepted, two refused
|
||||
immediately, and all four accepted ones were delivered. This is Cut 10's F2
|
||||
(pipelined reads) reproduced at chunk scale. The window is per session and
|
||||
counts unacknowledged packets.
|
||||
|
||||
**Consequence for the design:** the fetch is sequential, one chunk in
|
||||
flight, at 256 KiB. With `bin` payloads, as opposed to this probe's base64,
|
||||
one chunk reply is 262,357 bytes, which leaves room for a control-plane
|
||||
answer on the same session.
|
||||
|
||||
### F5. Moving one CultLib pin duplicates `cultcache-rs` even when its content is identical.
|
||||
|
||||
`pinprobe` (`cultcache-rs` at `a0813c6` beside `cultnet-rs` at `b3d9cf7`,
|
||||
whose `cultcache-rs` content is byte-identical) reports **two**
|
||||
`cultcache-rs v0.2.0` and two `cultcache-rs-derive` in `cargo tree -d`,
|
||||
because Cargo keys a git source by revision.
|
||||
|
||||
Huginn has three pins at `a0813c6`: `huginn-mind`'s `cultcache-rs`,
|
||||
`huginn-daemon`'s `cultnet-rs`, and the Epiphany leaf `epiphany-pipeline`'s
|
||||
`cultcache-rs` (at leaf `5cda0886`). All three must move together, or "one
|
||||
crate pins one revision" breaks (see `huginn-daemon/Cargo.toml` and the Cut 8
|
||||
build check `cargo tree -p huginn-mind -e normal -d` with zero duplicates).
|
||||
That is BP-2.
|
||||
|
||||
### F6. Huginn today.
|
||||
|
||||
The anchors in `huginn-daemon/src/serve.rs` at `202e5e3`:
|
||||
|
||||
- The module doc's stated limit is at `:10-17`, and the pipelined-read
|
||||
paragraphs are at `:32-44`.
|
||||
- `MAX_FRAGMENT_BYTES`, `MAX_PENDING_RELIABLE_PACKETS` and `MAX_RESPONSE_BYTES`
|
||||
are at `:124-131`, and `bind` is at `:135-143`.
|
||||
- `answer` (`:165-194`) routes `OperationRequest` and `SchemaCatalogRequest`,
|
||||
and answers every other family with `Error` (`:188-192`).
|
||||
- **`within_window` (`:217-251`) is the single owner of the size decision
|
||||
today.** It encodes the envelope, admits it at `<= MAX_RESPONSE_BYTES`, and
|
||||
otherwise answers `Refused(ResponseTooLarge { bytes, limit })`.
|
||||
- `run` (`:257-305`) reads the clock once per frame, at `:294`.
|
||||
|
||||
Elsewhere in the organ:
|
||||
|
||||
- `MindRefusal::ResponseTooLarge` is at `huginn-mind/src/refusal.rs:57-65`, and
|
||||
its doc names the daemon as owner.
|
||||
- `HuginnMindResponse` is at `huginn-mind/src/wire.rs:81-90`, and `status` is
|
||||
at `:97-102`.
|
||||
- The published schema is `schemas/cultnet/huginn.mind_response.v1.schema.json`,
|
||||
pinned byte for byte by `published_wire_schemas_match_derivation`
|
||||
(`wire.rs:210-216`).
|
||||
- The tests that pin the gate are
|
||||
`an_answer_too_large_for_one_send_is_a_typed_refusal_that_reaches_the_client`
|
||||
(`serve.rs:548-656`) and
|
||||
`the_gates_boundary_is_the_transports_boundary_byte_for_byte`
|
||||
(`serve.rs:749-777`).
|
||||
- The mutation entries D20, D20L through D20L5, D21 and D21L are in
|
||||
`tools/eureka-cut10-mutations.psd1:250-330`.
|
||||
- `huginn-mind` has **no** `cultnet-rs` dependency today. The selection
|
||||
consumer cut adds one, because `Query { instance, selection }` carries the
|
||||
substrate's type (`docs/cultnet-selection-cut.md` §12).
|
||||
- `eureka-state/src/lib.rs` is empty. Cut 13 has not been built.
|
||||
|
||||
## 2. The cut order
|
||||
|
||||
```
|
||||
[selection Cut 1, Rust commit 3 lands on CultLib main] (in Hands now, not this map)
|
||||
│
|
||||
▼
|
||||
BP-1 CultLib the content plane in cultnet-rs (wire, manifest, pack, answer, verified fetch) + vectors both ways
|
||||
│
|
||||
▼
|
||||
BP-2 Epiphany leaf + Huginn one pin move to a CultLib rev carrying selection Cut 1 and BP-1
|
||||
│
|
||||
├──► [Huginn selection consumer cut] (Epiphany map, unnumbered, not this map)
|
||||
▼
|
||||
BP-3 Huginn an oversize answer is deferred to the body plane; the refusal becomes the backstop
|
||||
│
|
||||
▼
|
||||
Cut 11 / Cut 13 (Cut 13's spec gains: resolve a Deferred answer)
|
||||
```
|
||||
|
||||
BP-3 comes **after** the Huginn selection consumer cut by default, for two
|
||||
reasons. That cut already adds `cultnet-rs` to `huginn-mind`, which BP-3's
|
||||
response variant needs. It also deletes `open_items` and `history`, so BP-3
|
||||
tests fewer read operations. If the operator orders BP-3 first, BP-3 adds
|
||||
that dependency itself (section 7, BP-3 "Adds") and nothing else changes.
|
||||
|
||||
## 3. Cut BP-1. The content plane in `cultnet-rs`
|
||||
|
||||
- **Repo and branch:** CultLib, `cultmesh/rust-content-plane`, from `main`
|
||||
**after selection Cut 1's Rust commit (its commit 3) has landed on `main`**.
|
||||
It depends on nothing else. See section 6 for why it cannot branch now.
|
||||
- **First:**
|
||||
- Record `packages/cultnet-rs` baseline tests (51 in `tests/cultnet.rs`
|
||||
and 7 in `tests/rudp_server_hub.rs` at `b3d9cf7`; recount at the actual
|
||||
base).
|
||||
- Record the target-dir path count and size.
|
||||
- Capture the reference's bytes for every vector with the C# writer
|
||||
before writing any Rust.
|
||||
- **Deletes first:** none. This cut fills an absence. No Rust code carries a
|
||||
body today, so nothing is stood beside, and there is no compensator to cut.
|
||||
This is stated rather than padded.
|
||||
- **Keeps (untouched):**
|
||||
- Every C# source file. The reference does not change: its chunk classes,
|
||||
serializer arms (`CultNetSchemaMessageSerialization.cs:79-80`), CDN pack
|
||||
and validation, and servers stay as they are.
|
||||
- `CultNetDatabaseSubscribeMessage` and its `bodyIds`/`supportedBodyTransports`.
|
||||
- `CultMeshBodyDemand.cs` and `CultMesh.cs`.
|
||||
- `contracts/cultnet/*.schema.json` and `CultNetSchemaRegistry.cs`.
|
||||
- `cultmesh-rs` (Huginn does not depend on it, and this cut does not make it).
|
||||
- `rudp.rs` (the transport carries these as ordinary schema frames).
|
||||
- **Adds:**
|
||||
|
||||
| Add | Owner | Live consumer | Protected invariant | Why an existing owner cannot serve |
|
||||
|---|---|---|---|---|
|
||||
| `contracts.rs`: `CultNetMessage::ContentChunkRequest { message_id, chunk_hash, record_key, expected_size_bytes: i32 }` and `ContentChunkResponse { message_id, found, chunk_hash, size_bytes: i32, payload: Vec<u8>, error }`, renamed to the reference's `schemaVersion`s, **both on the raw path** | `cultnet-rs` | Huginn's daemon (BP-3), `fetch_content` | **byte identity with the reference in both directions** | The closed enum cannot carry them (F1). The JSON-value path cannot carry `bin` (F1). |
|
||||
| `src/content.rs`: `CultMeshCdnArtifactManifest` and `CultMeshCdnChunkRef` (the reference's names; named Rust fields; serde `into`/`from` a private tuple mirror, so every encoder writes the reference's array, F3) | `cultnet-rs` | BP-3's deferred response, the fetch | one reference vocabulary across runtimes; the array form survives `to_vec_named` | A Huginn-local reference type would be a second vocabulary for one thing |
|
||||
| `normalize_hash` (mirrors `CultMeshCdn.NormalizeHash`, `CultMeshCdn.cs:549-560`: an optional case-insensitive `sha256:` prefix is stripped, then trimmed, then lowercased; empty is refused) | `cultnet-rs` | answer, fetch | one hash spelling | — |
|
||||
| `pack_content(artifact_id, kind, version, mime_type, created_at_utc, bytes, chunk_size) -> (CultMeshCdnArtifactManifest, Vec<CultMeshCdnChunk>)` (mirrors `CultMeshCdn.PackArtifact`, `:232-310`) | `cultnet-rs` | BP-3's store | **the same bytes and options produce the reference's manifest byte for byte** | — |
|
||||
| `validate_manifest` (mirrors `ValidateManifestShape`, `:499-526`, **including its sort by offset before the contiguity check**) and a caller-supplied `max_bytes` refused **before any fetch** | `cultnet-rs` | `fetch_content` | a hostile or corrupt manifest never makes a client allocate or ask | — |
|
||||
| `answer_content_chunk_request(request, lookup: impl Fn(&str) -> Option<&[u8]>) -> CultNetMessage` (mirrors `CultMeshLegacyRudpContentServer.HandleAsync`, `CultMeshContentSessions.cs:43-86`, **in its validation order**: message id, hash normalisation, record-key agreement with `mesh:cdn:chunk:<hash>`, lookup, size and hash of what is served; a failure is `found: false`, an empty payload, and `error` = `<ExceptionKind>: <message>` in the reference's spelling) | `cultnet-rs` | BP-3's `serve::answer` | a server never serves bytes whose hash is not the requested one, and never answers a failure as silence | Rust has no `ICultNetSchemaServer`, so a pure function is the whole server |
|
||||
| `fetch_content(manifest, max_bytes, ask: impl FnMut(CultNetMessage) -> Result<CultNetMessage>) -> Result<Vec<u8>>`: sequential, one chunk in flight, in manifest-offset order; verifies each response's correlation id, `found`, size and SHA-256 against the reference, then the whole body's size and SHA-256 against `contentHash` | `cultnet-rs` | Huginn's tests now, `eureka-state` (Cut 13) later | **one owner of verification on the receiving side**, as `CultMeshContentTransferService` is in the reference; no caller re-derives a check | The reference's transfer service is durable and file-promoting, and a C# owner does not serve Rust. This is its in-memory subset, with no resume, no failover and no files |
|
||||
| `lib.rs:21-39`: `mod content; pub use content::*;` | `cultnet-rs` | — | — | — |
|
||||
| `tests/content.rs` (new) | `cultnet-rs` | CI | the rules below | — |
|
||||
| `contracts/cultmesh/content-vectors.cs-written.json` and `content-vectors.rs-written.json`: request, response found, response not found, and manifest vectors. A **deterministic** body (`byte[i] = i % 251`, 1,315,551 bytes, the real oversize case) at 256 KiB. **The fixture must carry:** a hash given with an uppercase `SHA256:` prefix; a manifest whose chunk list is **out of offset order**, which the reference accepts; a final chunk shorter than the rest; a zero-length body (0 chunks); and a chunk response whose `chunkHash` differs from its payload's hash in the **last** hex digit only. Otherwise a loosening that compares prefixes cannot fail. | each runtime writes its own | `tests/content.rs`, the C# test below | **vectors the reference writes decode and re-encode identically in Rust, and the other way round** | — |
|
||||
| `tests/GameCult.Mesh.Tests/CultMeshContentVectorTests.cs` (**a new file**, not `NetworkingTests.cs`, which the selection cut is rewriting): writes the cs vectors under `CULTNET_WRITE_VECTORS=1`; asserts the committed cs vectors equal a fresh encode; decodes the rs-written vectors, asserts field equality and byte-identical re-encode; asserts `CultMeshCdn.PackArtifact` of the fixture body equals the fixture manifest | C# tests | CI | parity from the reference's side | — |
|
||||
| Docs: `src/GameCult.Mesh/docs/transport-planes.md` cut line (`:93-101`) and `content-sessions.md` gain one paragraph each naming the Rust runtime's content path and its transport (the wording depends on **Q-BP1**); `docs/runtime-parity-scope.md` gets one row | docs | — | describe the live system | — |
|
||||
|
||||
No new dependency: `sha2`, `serde_bytes` and `rmpv` are already in
|
||||
`cultnet-rs/Cargo.toml:14-31`. No new package, binary, transport, store
|
||||
format or JSON schema file (see section 5).
|
||||
|
||||
- **Per-file changes** in `packages/cultnet-rs/src/contracts.rs` at `b3d9cf7`.
|
||||
Re-anchor at the base, because selection Cut 1 moves this file.
|
||||
- **`:252-392`**, the enum: two variants after `OperationResponse`.
|
||||
`payload` is `#[serde(with = "serde_bytes")]`, although the raw path
|
||||
never serialises it through serde.
|
||||
- **`:398-418`**, `parse_cultnet_message`: both `schemaVersion`s join the
|
||||
raw arm at `:405`.
|
||||
- **`:420-436`**, `encode_cultnet_message_for_wire`: both join the raw arm
|
||||
at `:427`.
|
||||
- **`:458-643`**, `validate_message`: two arms.
|
||||
- Request: `messageId` non-empty; `chunkHash` non-empty after
|
||||
normalisation; `expectedSizeBytes >= 0`.
|
||||
- Response: `messageId` non-empty. `found` implies
|
||||
`payload.len() == sizeBytes` and an empty `error`. Not `found` implies
|
||||
an empty payload.
|
||||
- These are the reference's own invariants, as its constructors and
|
||||
`HandleAsync` produce them. They are not new policy.
|
||||
- **`:771-810`**, `parse_raw_cultnet_schema_message`: two arms, reading
|
||||
`bin` via `as_slice` and ints via `as_i64` range-checked to `i32`.
|
||||
- **`:812-854`**, `encode_raw_cultnet_schema_message`: two arms writing keys
|
||||
**in the reference's declaration order** (F3), with the payload as
|
||||
`rmpv::Value::Binary`.
|
||||
- **`:964-1138`**, the `gamecult.networking.v0` path: unchanged. Its `_`
|
||||
arms already refuse. A test pins that refusal.
|
||||
- **Authority map:**
|
||||
- **Owner:** `cultnet-rs::content` owns chunking, hashing, manifest shape,
|
||||
manifest validation, answering a chunk request and verifying a fetched
|
||||
body, for every Rust runtime. `contracts.rs` owns the two messages'
|
||||
spelling.
|
||||
- **Inputs:** bytes plus pack options (packing); a chunk request plus a
|
||||
lookup (answering); a manifest, a size cap and a transport closure
|
||||
(fetching).
|
||||
- **Outputs:** a manifest and chunks; a chunk response message; verified
|
||||
bytes or a typed error.
|
||||
- **Derived state:** `recordKey` is derived from the hash and is never an
|
||||
independent identity. `sizeBytes` and `contentHash` are derived from the
|
||||
bytes.
|
||||
- **Forbidden writers:** no consumer (Huginn, `eureka-state`) hashes,
|
||||
chunks, validates a manifest or checks a chunk itself. No consumer
|
||||
re-orders or retries chunks behind `fetch_content`.
|
||||
- **Shared paths:** the daemon's answer and the client's fetch both call
|
||||
`normalize_hash`. `pack_content` and `validate_manifest` share the one
|
||||
contiguity rule.
|
||||
- **Deletion line:** none. This is the first owner.
|
||||
- **Verification:**
|
||||
- **Builds** (PowerShell, `$env:CARGO_TARGET_DIR='C:\Users\Meta\.cargo-target-codex'`):
|
||||
`cargo test -p cultnet-rs --tests` from `packages/cultnet-rs`; then
|
||||
`dotnet test tests/GameCult.Mesh.Tests --filter CultMeshContentVectorTests`.
|
||||
- **Tests, each with the rule it pins and the mutants that must kill it**
|
||||
(committed in `scripts/mutate-cultmesh.mjs`, CultLib's own runner, which
|
||||
owes the Eureka harness contract by name, **once the QUIC campaign has
|
||||
released that file**; see section 6):
|
||||
- `reference_vectors_encode_byte_identically` pins byte identity of the
|
||||
two messages and the manifest.
|
||||
Mutants:
|
||||
- swap two keys in `encode_raw`'s request arm (revert-shaped);
|
||||
- write `payload` as a `str` or an array of ints (loosening);
|
||||
- encode the manifest through a named-field struct, i.e. drop the tuple
|
||||
mirror (loosening, killed because `to_vec_named` then writes a map).
|
||||
- `rust_vectors_decode_in_the_reference` (C#) pins the direction the
|
||||
reference reads.
|
||||
Mutant: Rust writes `sizeBytes` as a `u64`, which MessagePack-CSharp
|
||||
rejects into `int`.
|
||||
- `a_chunk_response_carries_bin_through_the_raw_path` pins F1's failure
|
||||
mode.
|
||||
Mutant: remove the response from the raw arm at `:405`. It must fail
|
||||
with the byte-array error.
|
||||
- `normalize_hash_matches_the_reference` pins the reference's
|
||||
normalisation.
|
||||
Mutants:
|
||||
- case-sensitive prefix strip;
|
||||
- no lowercasing;
|
||||
- no trim (each killed by the fixture's `SHA256:` uppercase vector).
|
||||
- `a_manifest_out_of_offset_order_is_accepted_as_the_reference_accepts_it`
|
||||
pins parity of the sort.
|
||||
Mutant: drop the sort. It must fail, because the fixture's chunk list
|
||||
is shuffled.
|
||||
- `a_manifest_over_the_cap_is_refused_before_any_request` pins that the
|
||||
cap precedes fetching.
|
||||
Mutants:
|
||||
- check the cap after the first chunk (the test counts closure calls,
|
||||
expecting 0);
|
||||
- compare `>` instead of `>=` at the boundary.
|
||||
The cap's boundary vector is exactly `max_bytes` accepted and one byte
|
||||
over refused. **At least one mutant must be a function of the input**:
|
||||
`max_bytes * 2`. The probe sizes sit where doubling would admit them.
|
||||
- `a_chunk_whose_hash_differs_in_the_last_digit_is_refused` pins whole-hash
|
||||
comparison.
|
||||
Mutants:
|
||||
- compare the first 63 characters;
|
||||
- compare lengths;
|
||||
- compare prefixes.
|
||||
All are killed only because the fixture pair differs in the last digit.
|
||||
- `a_body_whose_chunks_verify_but_whole_hash_does_not_is_refused` pins the
|
||||
whole-body check.
|
||||
Mutant: skip the final `contentHash` comparison. The fixture substitutes
|
||||
a manifest whose chunk refs are valid but whose `contentHash` is another
|
||||
body's.
|
||||
- `a_response_for_another_message_id_is_refused` pins correlation.
|
||||
Mutant: skip the id check.
|
||||
- `answer_refuses_a_record_key_that_disagrees_with_its_hash` pins parity
|
||||
with `HandleAsync`'s third check.
|
||||
Mutant: delete the record-key check.
|
||||
- `answer_serves_found_false_with_the_reference_error_spelling` pins that a
|
||||
failure is an answer.
|
||||
Mutant: return `Error` instead.
|
||||
- `gamecult_networking_contract_refuses_content_messages` pins that the
|
||||
legacy contract does not grow them.
|
||||
Mutant: add them to its encoder.
|
||||
- A **no-op control** rewrites each target through the runner's I/O path
|
||||
and must leave everything green.
|
||||
- **Negative checks:**
|
||||
- `git diff --stat main -- src/GameCult.Networking src/GameCult.Mesh/*.cs contracts/cultnet packages/cultmesh-rs packages/cultnet-rs/src/rudp.rs`
|
||||
is empty.
|
||||
- `rg -n "untagged" packages/cultnet-rs/src/content.rs` is empty.
|
||||
- **Operator:** nothing to click. This cut's evidence is the two vector files.
|
||||
- **Subtraction estimate:** removes 0 lines. Adds about 330: `content.rs`
|
||||
about 190, `contracts.rs` about 90, `lib.rs` 2, and docs about 20. Tests add
|
||||
about 260 Rust and 90 C#, plus two vector files. There are no dependencies,
|
||||
targets or formats to remove or add. **The delta is net positive because it
|
||||
buys an explicitly ruled capability with the smallest surface that carries
|
||||
it: the reference's own wire and reference type, not a new one.**
|
||||
- **Build budget:**
|
||||
- Host and target are both the Windows workstation, debug.
|
||||
- Rust: `cultnet-rs` library and tests only, in the warm codex target dir.
|
||||
The probe's release build of the same crate by path added 945 files and
|
||||
0.32 GiB (12.31 to 12.63 GiB) cold. The expected debug delta is up to
|
||||
+1,000 paths and about +0.4 GiB. Nothing is cleaned.
|
||||
- C#: `GameCult.Mesh.Tests`, which rebuilds Networking, Mesh and Caching as
|
||||
dependencies with no source change in them.
|
||||
- No native build, no TypeScript, no Python, no workspace-wide build.
|
||||
- C: had 258 GB free.
|
||||
- The Linux target (Yggdrasil) is not exercised here. Nothing in this cut
|
||||
is platform-specific, and Cut 14 builds on Yggdrasil.
|
||||
|
||||
## 4. Cut BP-2. One pin move
|
||||
|
||||
- **Repo and branch:** Epiphany `codex/eureka-pipeline-state` and Huginn
|
||||
`eureka/memory-organ`. It depends on BP-1 **and** selection Cut 1 being on
|
||||
CultLib `main`, pushed.
|
||||
- **Why it is its own cut:** F5. Moving `cultnet-rs` alone duplicates
|
||||
`cultcache-rs`. The selection consumer cut needs the same move, so **one**
|
||||
move to one revision carrying both CultLib changes serves both Huginn cuts.
|
||||
That saves a second leaf release and a second Huginn bump. Self schedules
|
||||
it once, whichever Huginn cut runs first.
|
||||
- **Changes:**
|
||||
- Epiphany `epiphany-pipeline/Cargo.toml` `cultcache-rs` rev: from
|
||||
`a0813c6…` to the new CultLib SHA. This is one commit. The leaf's own
|
||||
tests must stay green. `cultcache-rs` content is unchanged across
|
||||
`a0813c6..b3d9cf7`, so any change after that is itself a finding.
|
||||
- Huginn: `crates/huginn-mind/Cargo.toml` `cultcache-rs` rev and
|
||||
`epiphany-pipeline` rev to the new leaf SHA, and
|
||||
`crates/huginn-daemon/Cargo.toml` `cultnet-rs` rev. This is one commit.
|
||||
- **Verification:**
|
||||
- `cargo tree -p huginn-daemon -e normal -d` shows zero `cultcache-rs` or
|
||||
`cultnet-rs` duplicates.
|
||||
- The full Huginn suite is green: 74 tests at `202e5e3`.
|
||||
- The cut8, cut9 and cut10 mutation suites rerun clean.
|
||||
- `rg "a0813c6" F:\Projects\Huginn\crates F:\Projects\Epiphany\epiphany-pipeline`
|
||||
is empty.
|
||||
- **Subtraction:** 0 or 0. Only four revision strings change.
|
||||
- **Build budget:** Huginn workspace check and tests, and `epiphany-pipeline`
|
||||
library tests. Each is a cold compile of three CultLib crates at a new
|
||||
revision, about +300 to +600 paths. The same host as before.
|
||||
|
||||
## 5. Cut BP-3. Huginn defers an oversize answer to the body plane
|
||||
|
||||
- **Repo and branch:** Huginn `eureka/memory-organ`. It depends on BP-2 and,
|
||||
by default, on the Huginn selection consumer cut (section 2). It needs
|
||||
**Q-BP2** answered, and **Q-BP1** decides one paragraph of its module
|
||||
documentation.
|
||||
- **First:** rerun
|
||||
`an_answer_too_large_for_one_send_is_a_typed_refusal_that_reaches_the_client`
|
||||
and record the two sizes it prints. At `202e5e3` they are 1,315,551 bytes
|
||||
for the wide view and 1,189,495 for the fitting one. They are the
|
||||
before-and-after witness.
|
||||
- **Deletes first:**
|
||||
- `serve.rs:217-251` `within_window` is **deleted whole**. It is the one
|
||||
owner of the size decision, and it is replaced by `deliver` (below), not
|
||||
wrapped. A gate that refuses and a second gate that defers would be two
|
||||
owners of one decision.
|
||||
- `serve.rs:10-17`, the stated limit ("This is the current bound and not a
|
||||
design target… expect the number to move"), is deleted. The number no
|
||||
longer moves, because the window stops being the organ's delivery limit.
|
||||
- In `tools/eureka-cut10-mutations.psd1:250-330`, entries D20, D20L, D20L2,
|
||||
D20L3, D20L4, D20L5, D21 and D21L are **deleted from the cut10 suite and
|
||||
re-authored in BP-3's suite** against the new spelling. Their `Old`
|
||||
anchors name `within_window`'s lines, which no longer exist, so leaving
|
||||
them would make the cut10 runner fail on anchor-not-found rather than on
|
||||
a rule.
|
||||
- **Keeps:**
|
||||
- `MAX_FRAGMENT_BYTES`, `MAX_PENDING_RELIABLE_PACKETS` and
|
||||
`MAX_RESPONSE_BYTES` (`:124-131`): the window is still what one send
|
||||
carries.
|
||||
- `MindRefusal::ResponseTooLarge { bytes, limit }` keeps its name and
|
||||
shape; **its doc is re-scoped** (below).
|
||||
- The pipelined-read paragraphs `:32-44`, unchanged and still true:
|
||||
deferral does not fix F2.
|
||||
- `encode_or_fail` (`:197-215`).
|
||||
- The `Daemon` dispatch. `daemon.rs` is untouched: **the mind and the pure
|
||||
dispatch know nothing about deferral.**
|
||||
- **Adds:**
|
||||
|
||||
| Add | Owner | Live consumer | Protected invariant | Why an existing owner cannot serve |
|
||||
|---|---|---|---|---|
|
||||
| `huginn-mind/src/wire.rs:81-90`: `HuginnMindResponse::Deferred(DeferredAnswer)` with `DeferredAnswer { manifest: CultMeshCdnArtifactManifest }` under Q-BP2 A, or plus the typed summary under B; `status()` (`:97-102`) answers `accepted` for it. The manifest's schema is embedded through a `#[schemars(schema_with)]` field attribute returning a `$ref` to its published schema, as the selection consumer cut now does for `Selection` (corrected 2026-09-22: a hand-written `impl JsonSchema` for a foreign type is `E0117`, the orphan rule). `schemas/cultnet/huginn.mind_response.v1.schema.json` is regenerated. **The schema id stays `v1`**, because no client of it exists (Cut 13 is unbuilt) | `huginn-mind` (types only) | the daemon, Cut 13 | the reference rides the response schema, so there is no second vocabulary for a client | `HuginnMindResponse` is the only response a client decodes |
|
||||
| `huginn-daemon/src/bodies.rs` (new), `DeferredBodies`: a map from chunk hash to bytes with reference counts, and a map from content hash to `(chunk hashes, bytes, last_touched)`. `retain(manifest, chunks, now)`, which is idempotent on the content hash and refreshes it; `chunk(hash, now) -> Option<&[u8]>`, which refreshes the owning bodies; `expire(now)`; eviction least-recently-touched first when the total is over budget. Constants: `DEFERRED_CHUNK_BYTES = 256 * 1024`; `MAX_DEFERRED_BODY_BYTES = 64 MiB`; `DEFERRED_BUDGET_BYTES = 256 MiB`; `DEFERRED_TTL = 60 s`, which is twice `session_timeout`, and is a field of `ServeOptions` (`:70-79`) with that default | `huginn-daemon::serve` | `deliver`, `answer` | retention is bounded in bytes and time, and a body is never evicted while its chunks are being served within the TTL | The mind must not hold transport artifacts. `cultnet-rs` owns no retention policy, and should not, because the policy is this service's memory budget |
|
||||
| `serve.rs`: `deliver(reply, message_id, operation, runtime_id, bodies, now)` replaces `within_window`. It encodes the envelope. If the envelope is `<= MAX_RESPONSE_BYTES`, the reply is sent unchanged. Otherwise it takes the **payload bytes**, the named MessagePack of the response. If they are `<= MAX_DEFERRED_BODY_BYTES`, it calls `pack_content("huginn.mind_response", "package", "", "application/vnd.gamecult.huginn.mind-response+msgpack", now, payload, DEFERRED_CHUNK_BYTES)`, then `bodies.retain`, and answers `Deferred`. Otherwise it answers `Refused(ResponseTooLarge { bytes: payload_len, limit: MAX_DEFERRED_BODY_BYTES })` | `huginn-daemon::serve` | `answer` | **the one owner of how an answer is delivered.** Three outcomes, in that order, and nothing else decides | — |
|
||||
| `serve.rs:165-194`, `answer`: a new `CultNetMessage::ContentChunkRequest` arm calls `cultnet_rs::answer_content_chunk_request(request, \|h\| bodies.chunk(h, now))`. The error text at `:188-192` names the third family. The signature gains `bodies: &mut DeferredBodies`, and `run` (`:257-305`) owns one and passes it | `huginn-daemon::serve` | the client's fetch | a chunk is answered on the requester's own session, like every other frame | — |
|
||||
| `refusal.rs:57-65`, doc only: `ResponseTooLarge` is now "the answer exceeds the largest body the organ will deliver by any plane", `limit` is `MAX_DEFERRED_BODY_BYTES`, and `bytes` is the encoded answer (the payload, not the envelope). It is still the daemon's, and still never raised by the mind | `huginn-mind` | clients | **the backstop stays, per the ruling** | — |
|
||||
|
||||
- **Authority map:**
|
||||
- **Owner:** `huginn-daemon::serve::deliver` decides, per answer, between
|
||||
delivering it whole, deferring it, or refusing it. `DeferredBodies` owns
|
||||
retention. `cultnet-rs::content` owns chunking, hashing, the manifest and
|
||||
chunk answering.
|
||||
- **Inputs:** the encoded reply, the window constants, the deferral bound,
|
||||
the store's state and the per-frame clock read (`serve.rs:294`, still the
|
||||
crate's only one).
|
||||
- **Outputs:**
|
||||
- The reply, unchanged.
|
||||
- `Deferred { manifest }` plus retained chunks.
|
||||
- `Refused(ResponseTooLarge)`.
|
||||
- `ContentChunkResponse` for each chunk request.
|
||||
- **Derived state:**
|
||||
- The manifest and the chunk hashes are derived from the payload bytes.
|
||||
- The deferred body is **cache-only** (ephemeral transport state, like
|
||||
the reference's `CultMeshNetworkBodyStore`, `CultMeshBodySessions.cs:15-18`,
|
||||
"not a CultCache document store and not world truth").
|
||||
- The mind is not a writer or a reader of any of it.
|
||||
- **Forbidden writers:**
|
||||
- `Daemon::handle` and `Mind` never produce `Deferred` and never read the
|
||||
window.
|
||||
- No second gate: `within_window` is gone, and nothing else compares
|
||||
against `MAX_RESPONSE_BYTES`.
|
||||
- The client never re-derives a check: it calls `fetch_content`.
|
||||
- No pagination or truncation behind the caller's back (the Cut 10 F1
|
||||
ruling still holds).
|
||||
- **Shared paths:** every operation's reply passes through `deliver`,
|
||||
including `Whoami` and `Admit`. There is no per-operation path to
|
||||
deferral. Direct replies and chunk replies leave through the same
|
||||
`hub.send_schema_message` at `:295`.
|
||||
- **Deletion line:** `within_window` and the stated-limit paragraph are
|
||||
deleted before `deliver` is added, and the cut10 entries that anchored
|
||||
on them are removed in the same commit.
|
||||
- **Verification:**
|
||||
- **Builds** (PowerShell, the codex target dir):
|
||||
- `cargo test -p huginn-mind --lib`;
|
||||
- `cargo test -p huginn-daemon --lib`;
|
||||
- `cargo check --workspace`;
|
||||
- `cargo tree -p huginn-daemon -e normal -d`, which must show no
|
||||
`cultcache-rs` or `cultnet-rs` duplicate.
|
||||
- **Tests, each with its rule and mutants.** They are committed in a new
|
||||
`tools/eureka-bodyplane-mutations.psd1` and run by the Eureka harness
|
||||
`C:\Users\Meta\.claude\skills\eureka\tools\eureka-mutations.ps1 -Repo F:\Projects\Huginn`,
|
||||
with a no-op control.
|
||||
- `an_oversize_answer_arrives_deferred_and_fetches_to_the_same_answer`
|
||||
rewrites `serve.rs:548-656` over a real loopback hub and a real mind.
|
||||
The wide view, at 1,315,551 bytes, comes back `Deferred`. The client
|
||||
runs `cultnet_rs::fetch_content` over the **same session**, one chunk
|
||||
in flight, and decodes the bytes as `HuginnMindResponse`. The result
|
||||
**equals** `daemon.handle` of the same request, and is `View(Some)` and
|
||||
not `Deferred`. The fitting view and `Whoami` still arrive directly.
|
||||
This pins the ruling.
|
||||
Mutants:
|
||||
- `deliver` refuses instead of deferring (revert to Cut 10's
|
||||
behaviour);
|
||||
- `deliver` defers at `MAX_RESPONSE_BYTES * 2` (a function of the input:
|
||||
the wide answer sits between 1x and 2x, so doubling lets it through
|
||||
whole and the send fails silently, and the test times out with no
|
||||
reply).
|
||||
- `the_delivery_boundary_is_the_transports_boundary_byte_for_byte`
|
||||
rewrites `serve.rs:749-777`. An envelope of exactly `MAX_RESPONSE_BYTES`
|
||||
goes direct; one byte more is `Deferred`, **not** refused. This pins
|
||||
that the threshold is the window.
|
||||
Mutants:
|
||||
- `<` for `<=` (off by one);
|
||||
- measure the payload and not the envelope (the old D20L5, re-authored);
|
||||
- `MAX_RESPONSE_BYTES - MAX_FRAGMENT_BYTES` (a function of the constant
|
||||
that a "safety margin" edit would produce).
|
||||
- `an_answer_over_the_deferral_bound_is_refused_by_name` uses a synthetic
|
||||
response sized exactly `MAX_DEFERRED_BODY_BYTES` (deferred) and one
|
||||
byte more (refused, with `bytes` = payload length and `limit` =
|
||||
`MAX_DEFERRED_BODY_BYTES`). This pins that the backstop stays and is
|
||||
measured against the deferral bound.
|
||||
Mutants:
|
||||
- `if true` (never refuse);
|
||||
- `limit: MAX_RESPONSE_BYTES` (the old limit leaking through);
|
||||
- `bytes: envelope_len` (the wrong measure);
|
||||
- `MAX_DEFERRED_BODY_BYTES * 4` (a function of the input).
|
||||
- `a_deferred_answer_is_never_itself_deferred` pins a rule written
|
||||
*inside* `deliver`: the deferred reply is always under the window, and
|
||||
a body never decodes to `Deferred`. Encode the `Deferred` reply at the
|
||||
bound (about 256 manifest refs) and assert it is `<= MAX_RESPONSE_BYTES`.
|
||||
Mutant: recurse `deliver` on the deferred reply.
|
||||
- `retention_is_idempotent_and_sliding` exercises `DeferredBodies` alone
|
||||
with an injected `now`. The same body deferred twice is stored once, and
|
||||
its budget use is counted once. A chunk served at `t = TTL - 1`
|
||||
refreshes the body, so it survives to `2 * TTL - 2`. An untouched body
|
||||
is gone at `TTL`.
|
||||
Mutants:
|
||||
- expiry from creation rather than last touch;
|
||||
- `>` rather than `>=` at the TTL boundary;
|
||||
- a TTL that is a function of the input (`ttl / 2`);
|
||||
- count bytes per retain instead of per body.
|
||||
- `eviction_is_least_recently_touched_and_bounded_in_bytes` uses three
|
||||
bodies over the budget. The one touched longest ago goes first, and a
|
||||
shared chunk survives while any owner lives.
|
||||
Mutants:
|
||||
- evict the newest;
|
||||
- evict by insertion order (killed because the fixture touches the
|
||||
oldest-inserted body last);
|
||||
- free a shared chunk with its first owner.
|
||||
- `an_evicted_body_answers_found_false_and_the_client_can_ask_again`
|
||||
pins lifecycle end to end.
|
||||
Mutant: `chunk()` returns stale bytes after eviction. That is
|
||||
impossible by construction, so the mutant used is "do not refresh on
|
||||
serve" at a TTL shorter than a fetch.
|
||||
- `the_mind_never_sees_a_deferral` is a negative test:
|
||||
`rg -n "Deferred|DeferredBodies|MAX_RESPONSE_BYTES" F:\Projects\Huginn\crates\huginn-mind\src`
|
||||
matches only the `wire.rs` variant and its schema test.
|
||||
- `published_wire_schemas_match_derivation` (`wire.rs:210-216`, kept)
|
||||
pins that the regenerated schema equals the derivation.
|
||||
- **Scenario sited at the decision:** the loopback test holds its client
|
||||
on the `Deferred` reply **before** fetching and asserts the manifest's
|
||||
`sizeBytes` equals the direct `daemon.handle` payload's length. That
|
||||
observation is at the decision, not downstream of the fetch (the
|
||||
skill's "put the observation where the rule is decided").
|
||||
- **Negative checks:**
|
||||
- `rg -n "fn within_window" F:\Projects\Huginn\crates` is empty.
|
||||
- `rg -n "sha2|Sha256" F:\Projects\Huginn\crates\huginn-daemon\src` is
|
||||
empty, because hashing belongs to `cultnet-rs`.
|
||||
- Verified to not collide: `huginn-mind`'s `sha2` is for receipts, in
|
||||
another crate.
|
||||
- **Operator:** none until Cut 14. The real-network check (WireGuard to
|
||||
Yggdrasil, an oversize read end to end) is Cut 14's runbook step and is
|
||||
recorded there.
|
||||
- **Subtraction estimate:**
|
||||
- Removes about 35 lines (`within_window` 35, the stated-limit doc 8) and
|
||||
8 mutation entries.
|
||||
- Adds about 190 lines: `bodies.rs` about 110, `deliver` about 35, the
|
||||
`answer` arm 8, the wire variant with its `schema_with` `$ref` about
|
||||
35.
|
||||
- Tests are about +220 net over the rewritten two.
|
||||
- Entries: 8 removed, about 20 added.
|
||||
- No dependency is added: `cultnet-rs` is already a daemon dependency, and
|
||||
`huginn-mind` already has it after the selection consumer cut. If BP-3
|
||||
runs first, it adds that dependency to `huginn-mind`, and that is the one
|
||||
listed cost.
|
||||
- **Build budget:**
|
||||
- `huginn-mind` and `huginn-daemon` library and tests, warm after BP-2.
|
||||
- Expected +50 to +200 paths.
|
||||
- Host and target are both the workstation. The unix arm is not exercised,
|
||||
and Cut 14 builds on Yggdrasil.
|
||||
|
||||
## 6. Interaction with CultNet selection Cut 1, and sequencing
|
||||
|
||||
Selection Cut 1 is in Hands on `cultnet/selection-cut1` in the locked worktree
|
||||
`.claude/worktrees/agent-af580008561975a4c`, based at `b3d9cf7`.
|
||||
|
||||
**What it touches, from its own map:**
|
||||
|
||||
- `CultNetDatabaseSubscribeMessage`: v1 classes beside v0. It **keeps** the
|
||||
body-plane demand fields `bodyIds` and `supportedBodyTransports`, which its
|
||||
§5 lists as not selection.
|
||||
- `CultMeshBodyDemand.cs:177-221` and `CultMesh.cs` body and snapshot
|
||||
selector construction.
|
||||
- `CultNetSchemaRegistry.cs:360-385`.
|
||||
- `CultNetSchemaMessageSerialization.cs:9-85`.
|
||||
- `NetworkingTests.cs`.
|
||||
- In Rust: `packages/cultnet-rs/src/contracts.rs:314-331`, which gains
|
||||
`SnapshotRequestV1`, `DatabaseSubscribeV1` and `SnapshotResponseRawV1`. The
|
||||
last is a **raw** message, so it edits the same `parse_raw` and
|
||||
`encode_raw` dispatch this map edits.
|
||||
- `lib.rs` re-exports, the new `selection.rs`, and the vector writer pattern
|
||||
under `CULTNET_WRITE_VECTORS=1`.
|
||||
|
||||
**Semantic interaction: none.** `bodyIds` and `supportedBodyTransports` on the
|
||||
subscribe message are demand for *live* bodies, the reference's body plane
|
||||
proper (F2). This cut uses the content plane and never touches that message,
|
||||
its schema, the body-demand code or the database subscription server and
|
||||
client. A Huginn read is an operation request and not a subscription, so the
|
||||
two cuts share no rule.
|
||||
|
||||
**Textual collision: certain, in five places:**
|
||||
|
||||
- the `CultNetMessage` enum;
|
||||
- `parse_cultnet_message`'s raw arm;
|
||||
- `encode_cultnet_message_for_wire`'s raw arm;
|
||||
- `parse_raw` and `encode_raw`;
|
||||
- `validate_message`;
|
||||
- and `lib.rs`.
|
||||
|
||||
Two Hands in one working tree is forbidden, and two branches editing one
|
||||
dispatch list is a merge that falls to whoever lands second.
|
||||
|
||||
**Sequence:**
|
||||
|
||||
1. Selection Cut 1 lands its Rust commit (its commit 3) on `main`.
|
||||
2. BP-1 branches from that `main`. The anchors in section 3 are re-taken at
|
||||
that base, and the discrepancy is reported.
|
||||
3. BP-1 lands.
|
||||
4. BP-2 moves the pins once to a revision carrying both.
|
||||
5. Then the Huginn selection consumer cut and BP-3, in that order by
|
||||
default.
|
||||
|
||||
BP-1 does **not** add JSON schema files or registry entries, so it does not
|
||||
touch `CultNetSchemaRegistry.cs` or `contracts/cultnet/`, which selection
|
||||
Cut 1 is editing. The absence of published schemas for the content messages
|
||||
predates both cuts and is a follow-up (section 7).
|
||||
|
||||
**The mutation runner:** `scripts/mutate-cultmesh.mjs` is modified in the
|
||||
working tree by the live QUIC campaign. BP-1 adds its entries only after that
|
||||
file is committed or released, and never onto someone else's uncommitted
|
||||
edit. If the QUIC campaign still holds it when BP-1 is ready, BP-1 carries
|
||||
its entries in a new `scripts/mutate-cultnet-content.mjs` under the same
|
||||
contract, and Self collapses the two later. That is a default, not a
|
||||
question.
|
||||
|
||||
## 7. Deliberately not in scope
|
||||
|
||||
- **Pipelined chunk fetch, and Cut 10's F2 (pipelined reads).** The fetch is
|
||||
sequential. F4 shows the throughput barely depends on chunk size, and
|
||||
pipelining is the known way to lose a reply silently. A window-aware sender
|
||||
is a transport change in `rudp.rs`, not this cut.
|
||||
- **Throughput.** About 0.85 MB/s on loopback (F4), set by the 32-packet
|
||||
pacing window. A 64 MiB body takes about 75 s. Recorded, not addressed.
|
||||
- **A TCP or QUIC content connector in Rust, and Rust session identity with
|
||||
Odin-signed route certificates.** These are the reference's
|
||||
`CultMeshSessionIdentityClient` and `transport-planes.md:73-86`. Huginn's
|
||||
control plane has none of that today, and the body rides the same session
|
||||
with the same (absent) trust. **Trust for both planes is Cut 14's** (D8, the
|
||||
trust boundary).
|
||||
- **Durable, resumable or file-promoted fetch** (the reference transfer
|
||||
service's resume state and `<content-hash>.body` promotion). An answer is
|
||||
re-asked, not resumed.
|
||||
- **Publishing JSON schemas for `cultmesh.content_chunk_*`.** The reference
|
||||
never published them: there is no file in `contracts/cultnet/` and no entry
|
||||
at `CultNetSchemaRegistry.cs:360-385`. Follow-up: a CultLib parity fill for
|
||||
every runtime, after selection Cut 1 has released the registry.
|
||||
- **The TypeScript, Python and Kotlin content plane.** It follows when one of
|
||||
them has a caller, the same rule the selection cut ruled.
|
||||
- **The live body plane** (`body_read_*`, `CultMeshNetworkBodyStore`, body
|
||||
demand) in Rust.
|
||||
- **Persisting deferred bodies across a daemon restart.**
|
||||
- **Per-operation summaries,** unless Q-BP2 B is ruled. Row-level summaries
|
||||
are the selection vocabulary's projections.
|
||||
- **Cut 13's client handling of `Deferred`.** Consequence for Cut 13's spec,
|
||||
to be carried into its refresh: every read tool resolves `Deferred` through
|
||||
`cultnet_rs::fetch_content` with a cap no larger than
|
||||
`MAX_DEFERRED_BODY_BYTES`, and treats `ResponseTooLarge` as "narrow the
|
||||
selection".
|
||||
- **Consequence for Cut 14's spec:** the daemon's memory budget on Yggdrasil
|
||||
grows by up to `DEFERRED_BUDGET_BYTES` (256 MiB).
|
||||
|
||||
## 8. Operator questions
|
||||
|
||||
Two real forks. Every other choice above (the content plane over the body
|
||||
plane per F2, the reference's manifest, the ephemeral store, the constants,
|
||||
the order) is a default with its reason stated. It is recorded, not asked.
|
||||
|
||||
**Q-BP1: RULED A by the operator, 2026-09-22 ("A is fine here").**
|
||||
- Chunk requests and responses travel on the session the client already
|
||||
holds, byte-identical on the wire to the reference's
|
||||
`CultMeshLegacyRudpContentServer`.
|
||||
- BP-1 amends `transport-planes.md` to name this as Rust's *explicit* content
|
||||
path, never an implicit default.
|
||||
- It lasts until Rust has an authenticated content connector, TCP+TLS or
|
||||
QUIC. That later move sits behind the same `fetch_content` owner and needs
|
||||
no change to the reference type.
|
||||
|
||||
*History, the question as asked:* **Q-BP1. Which physical path carries the body?**
|
||||
|
||||
CultMesh's own transport doctrine says "Schema messages must not carry bulk
|
||||
bodies" and that the RUDP content path is legacy, "compatibility and parity
|
||||
measurement" only (`transport-planes.md:90-96`). But the only session
|
||||
Huginn and its client share is CultNet RUDP, and the doctrine's preferred
|
||||
content plane (TCP byte stream) is plaintext and loopback-only until TLS
|
||||
(`:103-106`), while Huginn serves remote clients over WireGuard.
|
||||
|
||||
- **A. Chunk request and response on the session the client already holds.**
|
||||
This is wire-identical to the reference's `CultMeshLegacyRudpContentServer`.
|
||||
`transport-planes.md` is amended to name it as the Rust runtime's
|
||||
*explicit* content path, never an implicit default, until Rust has an
|
||||
authenticated content connector. Cost: it re-legitimises, for Rust, a path
|
||||
the C# doctrine retired, and it inherits RUDP's pacing (F4).
|
||||
- **B. Port the TCP content plane to Rust** (`cultmesh-content+tcp://`, a
|
||||
typed header and raw bytes). Faster, and outside the envelope. It is
|
||||
blocked for Huginn by the doctrine's own loopback-only gate until TLS
|
||||
exists, so it needs a TLS content connector as well: a much larger cut, and
|
||||
Huginn waits on it.
|
||||
- **C. Wait for authenticated QUIC in Rust**, which does not exist. Huginn's
|
||||
oversize answers stay refused until then.
|
||||
|
||||
**Recommended: A.** It is the only option that delivers what the ruling
|
||||
asked for on the substrate Huginn has. It reuses the reference's wire byte
|
||||
for byte, and the amendment keeps the doctrine honest rather than quietly
|
||||
contradicted. B and C are where the body moves later, behind the same
|
||||
`fetch_content` owner and without changing the wire's reference type.
|
||||
|
||||
**What depends on it:** BP-1's documentation paragraph, whether BP-1 grows a
|
||||
TCP connector (B), and whether BP-3 can land before a TLS campaign (B and C
|
||||
block it).
|
||||
|
||||
**Q-BP2: RULED A by the operator, 2026-09-22 ("I accept your
|
||||
recommendations").** The deferred answer carries the reference's own facts:
|
||||
the manifest (`sizeBytes`, `contentHash`, the chunk list) and the operation.
|
||||
It carries no per-operation summary. Row-level facts come from asking again
|
||||
with a header projection. `huginn-mind` gains no summary types. B can be added
|
||||
later on the selection projection if Cut 13's tools show the need.
|
||||
|
||||
*History, the question as asked:* **Q-BP2. What is "the summary" the control plane carries?**
|
||||
|
||||
- **A. The reference's own facts.** The manifest carries `sizeBytes`,
|
||||
`contentHash` and the chunk list, and the envelope carries the operation.
|
||||
A caller decides whether to fetch on size alone. Anything row-level (ids,
|
||||
statuses, `matched`) comes from asking again with a selection projection,
|
||||
which the selection cut already provides.
|
||||
- **B. A typed per-operation summary produced by the mind:** for a view, the
|
||||
id, kind, status and admission facts without the document; for a page,
|
||||
`matched` and row references. The caller can act without fetching at all.
|
||||
Cost: new summary types and derivations in `huginn-mind`, one per read
|
||||
operation, re-specified whenever the selection consumer cut reshapes the
|
||||
reads, plus schema growth.
|
||||
|
||||
**Recommended: A.** It keeps deferral a pure transport decision owned by one
|
||||
function that knows nothing about operations. A per-operation summary is a
|
||||
second projection vocabulary beside the one the operator just ruled into
|
||||
CultNet. If Cut 13's tools show that agents need to decide without fetching,
|
||||
B can be added then, on the selection projection rather than beside it.
|
||||
|
||||
**What depends on it:** `DeferredAnswer`'s shape, whether `huginn-mind`
|
||||
gains types, BP-3's size (B adds about 120 lines and a test per operation),
|
||||
and Cut 13's tool output.
|
||||
|
||||
## 9. What was probed, what was only read, and what is not settled
|
||||
|
||||
- **Probed by running code:** F1 (decode refusals, plus the on-wire refusal),
|
||||
F3 (real reference bytes, byte identity of both messages and the manifest
|
||||
in Rust, the Rust re-pack equal to the reference's), F4 (whole-body send
|
||||
failure; sequential delivery at three chunk sizes with SHA equality and
|
||||
timings; the 900 KiB failure; the pipelining loss), and F5 (duplicate
|
||||
`cultcache-rs` under mixed revisions).
|
||||
- **Read only, not run:**
|
||||
- C# `ValidateManifestShape` sorting and contiguity, `NormalizeHash`, and
|
||||
the legacy server's validation order. These are the rules the vectors
|
||||
will pin in BP-1.
|
||||
- The reference's TCP connector behaviour and the loopback-only gate, from
|
||||
`transport-planes.md`, not from running `CultMeshTcpContentTransport`.
|
||||
- Selection Cut 1's exact edit sites in the worktree. I read its map, not
|
||||
its uncommitted diff, and did not open the locked worktree's changes, to
|
||||
avoid reading another agent's working tree mid-edit.
|
||||
- **Not probed:**
|
||||
- Throughput over WireGuard or to Yggdrasil (loopback only).
|
||||
- The 60 s TTL against a real slow link.
|
||||
- C# decoding **Rust-written** bytes. Byte identity makes it follow, but
|
||||
BP-1's C# test is the first thing that will actually run it.
|
||||
- Whether a hostile chunk flood can starve the control plane on one
|
||||
session (the window is per session, so the likely answer is that it can
|
||||
starve only the flooding session). Recorded for Cut 14's trust pass.
|
||||
- **Substrate missing, recorded per the skill:** typed pipeline state. This
|
||||
map, its model page and its questions live in markdown because Huginn,
|
||||
which would hold them, is the thing being built.
|
||||
6465
docs/history/eureka-pipeline-state-cut.md
Normal file
6465
docs/history/eureka-pipeline-state-cut.md
Normal file
File diff suppressed because it is too large
Load diff
171
docs/history/eureka-pipeline-state-target.md
Normal file
171
docs/history/eureka-pipeline-state-target.md
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
<!-- Provenance: copied verbatim from GameCult/Epiphany ef956865 notes/eureka-pipeline-state-target.md; text below this line is unchanged. -->
|
||||
# Eureka pipeline state: target
|
||||
|
||||
Status: target (the ends). The means live in `notes/eureka-pipeline-state-cut.md`,
|
||||
written by Imagination. Operator rulings are dated 2026-09-15 and 2026-09-16.
|
||||
|
||||
**This target was rewritten on 2026-09-16** after the operator rejected the
|
||||
ownership model it had been built on. Rulings 5, 6, 10 and 13's store clauses,
|
||||
which put the store in the task's repo under a per-clone lease, are superseded
|
||||
by rulings 14-17 below. The cut map's Cut 3 sections describe the old model and
|
||||
are history, not live design.
|
||||
|
||||
## Why
|
||||
|
||||
Eureka is the Claude Code counterpart of this organism. It is a skill,
|
||||
published as `GameCult/Eureka` and installed at `~/.claude/skills/eureka`, that
|
||||
runs the same faculties (Self, Imagination, Hands, Soul, Mind Steward) over
|
||||
foundation changes. In its first campaign, the Aetheria CultCache migration, the
|
||||
pipeline worked, but its findings lived in prose: the cut map, subagent reports
|
||||
and memory files. Self re-read and re-summarised that prose at every step. Stale
|
||||
claims survived until a steward caught them. The postmortem needed a transcript
|
||||
crawl, and one operator quote survived only in a compaction summary.
|
||||
|
||||
Epiphany already has the other half: typed documents, admission and receipts.
|
||||
Her own `notes/faculty-workflow-lessons-2026-09-04.md:146-176` proposes typed
|
||||
forms of Eureka's habits (`Map`, `Spec`, `CutReport`, `Verdict`, rulings, a
|
||||
landed-names digest), but none of them are built.
|
||||
|
||||
This campaign builds them once and gives Eureka agents access to them.
|
||||
|
||||
## What the first model got wrong
|
||||
|
||||
The store was owned by a repo. Nothing owned the state, so the machinery grew to
|
||||
compensate: a per-clone writer lease, git-directory resolution, committed
|
||||
`.gitattributes` checks, and a merge tool for divergent copies. Three Soul passes
|
||||
went into propping that up, and it still resolved a store into a directory the
|
||||
operator never named.
|
||||
|
||||
Mind state also does not want version control. Git offers branches, merges and
|
||||
history rewriting; a mind wants one owner and an append-only record.
|
||||
|
||||
The operator's correction: **someone owns the state**. An instance owns its mind,
|
||||
and stewardship over repos is an assignment, not an identity. That is Epiphany's
|
||||
own design, and Eureka inherits it.
|
||||
|
||||
## Operator rulings
|
||||
|
||||
1. **Epiphany owns the schemas** for pipeline state.
|
||||
2. **Searchable state serves agents first.** Self and new agents rehydrate from
|
||||
it ("what rulings govern stores?", "what is still open?"). Soul and
|
||||
Imagination check precedent in it ("have nil map keys bitten us?"). Operator
|
||||
browsing across projects comes later.
|
||||
3. **Two campaigns.** This one covers the schemas, the memory organ, admission,
|
||||
the Eureka MCP client and a proof on one real Eureka task. The second,
|
||||
Epiphany adopting Eureka's habits, comes after the schema stops moving.
|
||||
4. **Re-pin first.** Landed: Epiphany moved from CultLib `e171eca3` to
|
||||
`a0813c6`.
|
||||
5. **Q1-Q5 (2026-09-15), of which only Q2 and Q5 survive the rewrite:** additive
|
||||
schema changes keep the epoch and a breaking bump refuses the old store
|
||||
(Q2); `schemars` is an unconditional `epiphany-core` dependency and the
|
||||
published JSON schemas are derived from the Rust types (Q5).
|
||||
6. **The operator channel is the Claude Code session.** Eureka has no Persona.
|
||||
Self is the operator surface, because spec iteration works best where the
|
||||
question and the tree share a context. A blocking question may be pushed
|
||||
through whatever notification MCP the user configures; Eureka owns no
|
||||
transport and names no provider. Answers come back in the session. Answering
|
||||
over a chat channel is a later campaign and needs identity binding first.
|
||||
7. **Eureka is published** as `GameCult/Eureka`, MIT.
|
||||
|
||||
### Ownership (2026-09-16)
|
||||
|
||||
14. **An instance owns its mind.** A store is canonical to exactly one instance.
|
||||
Identity lives in the state, not in a path. Admission refuses a write
|
||||
carrying another instance's identity, whatever the transport. Stewardship
|
||||
over repos is an assignment recorded in that mind, and one instance may
|
||||
steward several repos. Reassignment is an explicit typed hand-off recorded
|
||||
in both minds, so history stays attributable.
|
||||
15. **A service owns the state, on Yggdrasil.** The memory organ is a daemon: it
|
||||
outlives every session, owns a real resource dependency, and isolates a dead
|
||||
session from a corrupted mind. It earns its process under the GameCult
|
||||
daemon test. Minds are no longer committed to git, so there is no per-clone
|
||||
lease, no git-attribute precondition, and no divergence between clones.
|
||||
16. **The organ depends on Qdrant directly, not on voidbot.** It owns its own
|
||||
collections and indexes at admission time. voidbot keeps its read-only
|
||||
public-repo retrieval; it is not in this path. Embeddings come from the
|
||||
Ollama endpoint the map names. Epiphany's own service embeds against a
|
||||
Yggdrasil-local endpoint with the same model, and only voidbot uses
|
||||
Nightwing; follow the local precedent and name the endpoint, not the host.
|
||||
17. **Huginn is the memory organ.** The dormant `.cc`-to-Eve CLI is retired and
|
||||
generic `.cc` inspection belongs to CultCache Studio in CultLib. Huginn
|
||||
becomes the Rust service that owns instance minds, admission and the index.
|
||||
That gives Huginn a real runtime for the first time, but it closes the
|
||||
authority vacancy only for pipeline state: doctrine still names Huginn the
|
||||
Persona-state steward, and this campaign builds none of that. Recorded as a
|
||||
follow-up, not solved here.
|
||||
|
||||
### Shape decided with those rulings
|
||||
|
||||
- **Transport:** the organ speaks CultNet. `eureka-state` stays a local stdio MCP
|
||||
server that is a thin CultNet client, so the MCP surface stays swappable and
|
||||
the service protocol stays typed.
|
||||
- **Unreachable organ:** refuse loudly and let the campaign stall on that step.
|
||||
No local spool, because that reintroduces two writers.
|
||||
- **Cut 3a code:** keep the document kinds, keys, validation and the refusals
|
||||
that survive the layer. Six of the thirteen typed refusals exist only for the
|
||||
repo store and die with it. Delete the store, lease and git layers, and
|
||||
re-point admission at the organ.
|
||||
|
||||
## End state
|
||||
|
||||
- **Documents.** Epiphany defines the typed pipeline documents: campaign, target,
|
||||
question, ruling, cut spec, cut report, verdict, finding, follow-up and
|
||||
resolution, plus the three ruling 14 requires: instance, stewardship and
|
||||
hand-off. Each has a stable schema id and version, a derived key, and a live
|
||||
Eureka consumer. Schemas are derived from the Rust types and published in
|
||||
`schemas/cultnet/index.json`.
|
||||
- **Instance identity.** A mind carries the instance that owns it and the repos
|
||||
it stewards. A campaign names its repo; the mind holds campaigns across every
|
||||
repo that instance stewards.
|
||||
- **The organ (Huginn).** One service owns every mind it hosts:
|
||||
- admission, with per-document rules and typed refusals;
|
||||
- exact and filtered queries (by campaign, repo, cut, kind, status,
|
||||
supersession, time), "open items", and "rulings in force";
|
||||
- semantic search over its own Qdrant collections, indexed at admission;
|
||||
- a typed hand-off for reassigning stewardship, and an import path for
|
||||
another instance's mind. Import is not a merge: with one owner per mind,
|
||||
two copies cannot diverge, so nothing needs reconciling;
|
||||
- supervised by Idunn, with a named backup owner for its volume.
|
||||
- **Eureka.** The skill's briefs tell Self, Imagination, Hands and Soul to admit
|
||||
and query typed state instead of relaying prose. `eureka-state` is the thin
|
||||
MCP client, registered at user scope.
|
||||
- **Proof.** One real Eureka task runs end to end on typed state: rulings
|
||||
admitted, a cut spec, Hands' cut report, Soul findings, follow-ups queried
|
||||
back, and a fresh agent rehydrating from queries alone.
|
||||
|
||||
## Invariants
|
||||
|
||||
- **One owner.** Exactly one instance owns a mind, and the organ is its only
|
||||
writer. Eureka agents and the MCP client are clients; the index is a
|
||||
projection.
|
||||
- **Identity travels with the write.** An admission carries the instance, and a
|
||||
foreign instance is refused.
|
||||
- **Typed, not prose.** No JSON store and no blob fields where a typed field
|
||||
exists. JSON appears only in published schemas.
|
||||
- **Mind state is not in version control.** Campaign prose (target, cut map) stays
|
||||
in the repo; the mind does not.
|
||||
- **Supersession, not deletion.** An old ruling or finding stays queryable as
|
||||
superseded.
|
||||
- **The skill defers to the schema.** Eureka describes how to use the state; the
|
||||
schema set and admission rules are owned here.
|
||||
- **Availability is honest.** When the organ is unreachable, Eureka refuses and
|
||||
says so. It never writes a second copy.
|
||||
|
||||
## Not in scope
|
||||
|
||||
- Epiphany's own organs consuming pipeline documents: the second campaign.
|
||||
- Operator browsing surfaces (Eve/CultUI) over pipeline state.
|
||||
- Answering operator questions over a chat channel.
|
||||
- Migrating the Aetheria CultCache campaign's prose into typed documents.
|
||||
- Cleanup of `.epiphany-run/` (105 GiB) and the repo-local `target/` (39 GiB).
|
||||
- Huginn's legacy `.voidbot` Persona state, which needs its own admission
|
||||
decision.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Substrate maps (scratch, 2026-09-15): Epiphany typed state and admission; the
|
||||
MCP and client landscape.
|
||||
- Dated comparison: `~/.claude/skills/eureka/references/epiphany-comparison-2026-09-15.md`.
|
||||
- Campaign precedent: `F:\Projects\Aetheria\docs\cultcache-migration-postmortem.md`.
|
||||
- Huginn's dormancy and authority vacancy:
|
||||
`F:\Projects\gamecult-ops\docs\repo-census-2026-09\repos\Huginn.md`.
|
||||
524
docs/history/eureka-query-prior-art.md
Normal file
524
docs/history/eureka-query-prior-art.md
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
<!-- Provenance: copied verbatim from GameCult/Epiphany ef956865 notes/eureka-query-prior-art.md; text below this line is unchanged. -->
|
||||
# Prior art for a typed, serializable query surface over a memory organ's read side
|
||||
|
||||
Eyes pass, 2026-09-16/17. Facts with evidence pointers. No ranking, no recommendation.
|
||||
|
||||
## The thing being measured against
|
||||
|
||||
The five capabilities, abbreviated throughout as **C1..C5**:
|
||||
|
||||
- **C1** typed predicates over each kind's own fields, expressed as data
|
||||
- **C2** projection (summary or whole document)
|
||||
- **C3** ordering plus an opaque cursor
|
||||
- **C4** one hop of citation in both directions
|
||||
- **C5** count / existence without bodies
|
||||
|
||||
The portability facts, abbreviated **P1..P5**:
|
||||
|
||||
- **P1** does the query itself serialize as typed data across runtimes (MessagePack wire, constructible from TS/C#), or is it Rust-only builder calls?
|
||||
- **P2** does it derive a JSON Schema, or is the schema hand-written?
|
||||
- **P3** transitive dependency cost; anything dragging in a runtime, network client, DB driver or parser generator
|
||||
- **P4** does it assume a backing store engine?
|
||||
- **P5** licence, last release, maintenance signal, single-maintainer?
|
||||
|
||||
## The crate this lands in
|
||||
|
||||
`F:\Projects\Huginn\crates\huginn-mind`, manifest at `F:\Projects\Huginn\crates\huginn-mind\Cargo.toml:9-19`. Dependencies exactly as stated in the brief: `anyhow`, `chrono 0.4.44`, `cultcache-rs` (CultLib, rev `a0813c6e`), `epiphany-pipeline` (Epiphany, rev `5cda0886`), `rmp-serde 1`, `schemars 1`, `serde 1`, `sha2 0.10`. Dev-only: `serde_json`, `tempfile`. The manifest comment states the constraint in its own words: "No network, no socket, no index: those are the daemon's (Cut 10, Cut 11)."
|
||||
|
||||
**The baseline P3 measurement, taken directly.** `F:\Projects\Huginn\Cargo.lock` currently resolves **139 packages** for the whole three-crate workspace. A grep of the lockfile for `tokio`, `hyper`, `reqwest`, `tonic`, `async-std`, `mio`, `prost`, `pest`, `lalrpop` and `nom` returns **NONE** — there is no async runtime, no HTTP client, no gRPC stack and no parser generator anywhere in the tree today. The sibling `huginn-daemon` (`crates/huginn-daemon/Cargo.toml`) gets its socket from `cultnet-rs` and its process lifetime from `signal-hook 0.3`, synchronously. So any candidate pulling a runtime in would be introducing the first one, and the 139 is the number a transitive-dependency count should be read against.
|
||||
|
||||
---
|
||||
|
||||
# Part I — GameCult-internal candidates
|
||||
|
||||
## I-1. The incumbent: `PipelineQuery` in `huginn-mind`
|
||||
|
||||
**What it is.** A flat option-struct filter, already built, already on the wire, already schema-published. This is the surface a general one would replace.
|
||||
|
||||
**Owner.** `huginn-mind`, this crate. Landed in commit `49cc6e3` "Read a mind: views, queries, open items and history".
|
||||
|
||||
**Evidence.**
|
||||
|
||||
- `F:\Projects\Huginn\crates\huginn-mind\src\query.rs:77-97` — `PipelineQuery`: `campaign: Option<Slug>`, `repo: Option<OrgRepo>`, `cut: Option<Label>`, `kinds: Vec<PipelineKind>`, `in_force: Option<bool>`, `faculty: Option<Faculty>`, `admitted_after: Option<Short>`, `admitted_before: Option<Short>`, `limit: Option<u32>`, `semantic: Option<SemanticQuery>`. Derives `Serialize, Deserialize, JsonSchema`.
|
||||
- `query.rs:249-290` — `Reader::matches`, the whole evaluator: a hand-written `if let Some(..)` chain, one branch per field, AND-only.
|
||||
- `query.rs:30-32` — `QUERY_LIMIT_MAX = 200`.
|
||||
- `query.rs:307-311` — `ordered()`, a fixed sort by `(admitted_at, id)`; not selectable.
|
||||
- `query.rs:331-350` — `Mind::query`; `matched` is computed before truncation so a short page is not mistaken for the whole answer.
|
||||
- `query.rs:110-117` — `PipelineOpenItems`, five named buckets.
|
||||
- `query.rs:121-125` — `HistoryScope::{Subject, Repo}`.
|
||||
- `F:\Projects\Huginn\crates\huginn-mind\src\wire.rs:43-50` — `HuginnMindRequest::{Whoami, Admit, View, Query, OpenItems, History}`.
|
||||
- `wire.rs:188, 203` — round-trips through `rmp_serde::to_vec_named` in test; MessagePack is the live encoding.
|
||||
- `F:\Projects\Huginn\crates\huginn-daemon\src\envelope.rs:39, 84, 144-145` — on the live wire the payload is **`rmp_serde::to_vec_named` then base64** inside a CultNet operation envelope, dispatched by schema id (`MIND_RESPONSE_SCHEMA` vs `FAILURE_SCHEMA`). Base64 is a 4/3 size multiplier on whatever the query serializes to, which is a fact about any encoding that nests deeply.
|
||||
- `wire.rs:31-34, 210-221` — the published JSON Schemas are `include_str!`'d from `schemas/cultnet/huginn.mind_request.v1.schema.json` and pinned byte-for-byte against `schemars::schema_for!` by the test `published_wire_schemas_match_derivation`, with `HUGINN_WRITE_SCHEMAS` as the regeneration path. The request schema currently carries 59 `$defs`.
|
||||
|
||||
**Portability facts.** P1: yes, it is already typed data on a MessagePack wire. P2: yes, `schemars`, derived and pinned. P3: zero added. P4: no; it runs over `Docs::from_image(mind.envelopes())`. P5: ours.
|
||||
|
||||
**Coverage.**
|
||||
|
||||
- C1: **partial and cross-cutting only.** Every filter is a field shared across kinds or derived from admission. There is no way to express "severity = High" or "outcome = Withdrawn" — the brief's own example. `severity`, `confidence`, `origin` (`PipelineFinding`), `sequence`/`outcome` (`PipelineResolution`) are unreachable.
|
||||
- C2: **no.** `PipelineQueryPage.items` is always `Vec<PipelineDocumentView>` and a view carries the whole `PipelineDocument` (`query.rs:57-63`).
|
||||
- C3: **no cursor, fixed order.** `limit` truncates; `matched` tells you it truncated. A page is a truncation, exactly what the brief says it must stop being.
|
||||
- C4: **no, but two hops are hardcoded.** `open_items` (`query.rs:366-389`) walks `CutReport.cut_spec` and `Verdict.cut_report` by hand to derive `specs_without_report` and `reports_without_verdict`. `Reader::base` (`query.rs:237-246`) walks resolution→subject so a filter reads through a resolution.
|
||||
The derivation layer beneath it (`F:\Projects\Huginn\crates\huginn-mind\src\docs.rs`) is entirely kind-specific and entirely `pub(crate)`: `find` (`:71`), `of_kind` (`:75`), `resolutions_of` (`:97`), `assignments_of` (`:104`), `stewardships_of` (`:194`), `closing_resolution` (`:186`), `later_in_force` (`:212`), `stewardship_of` (`:225`). There is **no generic "documents citing X" and no generic "documents cited by X"** anywhere in the crate; each traversal that exists was written for one pair of kinds.
|
||||
- C5: **no.** `matched: u32` is the only count, and it is a by-product of a query that already materialized every body.
|
||||
|
||||
**Three places the incumbent reaches into key strings because there is no structured alternative** — worth recording as pressure evidence:
|
||||
|
||||
- `query.rs:169-174` `root_and_local` splits `<root>:<kind>:<local>` by position.
|
||||
- `query.rs:262` matches a cut by `format!("cut-{}.", cut.0)` prefix on the local segment.
|
||||
- `query.rs:178-195` `repo_matches` is a hand-written match over 13 document variants to answer "does this document carry this repo".
|
||||
|
||||
**The semantic hole is already declared.** `query.rs:65-72` and `331-336`: `SemanticQuery { text, top_k }` is accepted by the type and refused typed — "Cut 11 also owns which fields of each kind are text, so no substring filter lives here to become a second answer to that question."
|
||||
|
||||
## I-2. `epiphany-pipeline` — the document leaf, and how its references are shaped
|
||||
|
||||
**What it is.** The crate that owns document shape, bounds, formats and keys. Not a query surface; it is the schema C1 and C4 would have to be expressed over.
|
||||
|
||||
**Owner.** Epiphany, `F:\Projects\Epiphany\epiphany-pipeline\src\lib.rs`, 2444 lines, single file.
|
||||
|
||||
**The load-bearing fact for C4: references are not uniformly typed.**
|
||||
|
||||
- `lib.rs:400-437` — `PipelineRef { kind: PipelineKind, id: Short }` with `validate_ref()`. This is the typed reference.
|
||||
- But most citations are bare `Short`, kind implied by field name:
|
||||
- `lib.rs:349` `PipelineCutReport { cut_spec: Short, forks: Vec<Short>[8], .. }`
|
||||
- `lib.rs:356` `PipelineVerdict { cut_report: Short, .. }`
|
||||
- `lib.rs:357` `PipelineFinding { verdict: Short, .. }`
|
||||
- `lib.rs:342` `PipelineCutSpec { depends_on: Vec<Short>[8], rulings: Vec<Short>[32], questions: Vec<Short>[16] }`
|
||||
- `lib.rs:324` `VerdictClaim { findings: Vec<Short>[16], mutations: Vec<Label>[8], .. }`
|
||||
- Typed `PipelineRef` appears in only three places: `PipelineQuestion.raised_in: Option<PipelineRef>` (`lib.rs:334`), `PipelineFollowUp.source: PipelineRef` (`lib.rs:362`), `PipelineResolution.subject: PipelineRef` (`lib.rs:374`).
|
||||
- `ForeignRef` (`lib.rs:286`) is the cross-repo reference: `{ repo, commit, kind, id, payload_sha256 }` — typed, but names another repo.
|
||||
- `DocRef` (`lib.rs:287`) points at a file range, not a document.
|
||||
|
||||
There is **no generic "references out of this document" enumerator** in the leaf. `grep` finds 137 occurrences of `PipelineRef` and no `fn refs`, `fn references` or `fn cites`. A generic C4 hop must therefore either carry a per-field kind table or the leaf must grow a reference enumerator.
|
||||
|
||||
**Per-kind predicate fields C1 would need to reach** (`lib.rs:357` and around): `PipelineFinding { confidence: FindingConfidence, severity: FindingSeverity, origin: FindingOrigin, invariants: Vec<Label>[8] }`; `PipelineResolution { sequence: u32, outcome: ResolutionOutcome }` (`lib.rs:374`); `PipelineStewardship { instance, repo, sequence, assigned_on }` (`lib.rs:388`). Every bound is declared in the macro (`[8]`, `[16]`, `[64]`), so field cardinality is already known statically.
|
||||
|
||||
**Portability facts.** P1/P2: the documents already serialize and derive `JsonSchema`, same `schemars`. P3: it is already a dependency. P4: no. P5: ours.
|
||||
|
||||
**Coverage.** Owns none of C1..C5; it is the type universe they range over.
|
||||
|
||||
## I-3. CultCache (`cultcache-rs`) — no query concept at all
|
||||
|
||||
**What it is.** The store. Envelope persistence, compare-exchange, snapshot pull.
|
||||
|
||||
**Owner.** CultLib, `F:\Projects\CultLib\packages\cultcache-rs\src\lib.rs`, single file.
|
||||
|
||||
**Evidence.** The entire public surface is CAS/batch/snapshot: `compare_and_swap_entry` (`:412`), `insert_entry_if_absent` (`:437`), `compare_and_swap_batch` (`:456`), `append_if_snapshot_unchanged` (`:510`), `delete_batch_if_unchanged` (`:543`), `replace_and_append_if_snapshot_unchanged` (`:571`), `pull_all_read_only_snapshot` (`:674`), `compare_exchange` (`:728`), `register_document_type` (`:1952`), `pull_all_backing_stores` (`:2022`). There is **no** `fn query`, `fn filter`, `fn find`, `fn select` or `fn where` anywhere in the file.
|
||||
|
||||
**One correction to "no selection at all": the C# reference has two single-value index lookups, and the Rust runtime has neither.**
|
||||
|
||||
- `F:\Projects\CultLib\src\GameCult.Caching\CultCache.cs:1495-1509` — `GetByName<T>(string name)` and `GetByIndex<T>(string alias, string value)`. Both funnel through `Single<T>(...)`, so each returns **at most one document**: these are unique-index lookups, not multi-result selection, and there is no range, no set membership, no composition.
|
||||
- Consumers are all in the C# tree: `src/GameCult.Mesh/CultMesh.cs:2351, 2433, 3330`, `src/GameCult.Networking/CultNetDatabase.cs:794-797, 1105-1106`.
|
||||
- `grep` for `get_by_index` / `by_index` / `index_alias` in `packages/cultcache-rs/src/lib.rs` returns **nothing**. The runtime `huginn-mind` actually depends on has no index concept at all.
|
||||
|
||||
A repo-wide search for declared filter/predicate/selector types across `CultLib`, `Epiphany`, `Eve`, `Odin`, `Norn` and `Mimir` sources (`grep -rliE "(struct|class|interface|enum) +[A-Za-z]*(Filter|Predicate|Selector|Criterion|Criteria)"`) returns **zero hits in CultLib, Odin and Norn**; the Eve hits are all `node_modules`; Mimir's two are a signal-processing matched filter and a transport selector, unrelated.
|
||||
|
||||
**Coverage.** None of C1..C5. Read is "give me every envelope"; selection is the caller's loop. P4 is the notable one: CultCache is exactly the in-memory-image shape the brief describes, so a candidate that needs a store engine is a different kind of thing.
|
||||
|
||||
## I-4. CultNet — one selection shape on the wire, and it is two id allowlists
|
||||
|
||||
**What it is.** `CultNetMessage::SnapshotRequest`, the only CultNet message that narrows what comes back.
|
||||
|
||||
**Owner.** CultLib, `F:\Projects\CultLib\packages\cultnet-rs\src\snapshot_query.rs`.
|
||||
|
||||
**Evidence.**
|
||||
|
||||
- `snapshot_query.rs:160-163` — `CultNetRawSnapshotQuery { message_id: String, expectations: BTreeMap<(String, String), CultNetSnapshotSourceExpectation> }`.
|
||||
- `snapshot_query.rs:197-217` — `request()` lowers those expectations to `CultNetMessage::SnapshotRequest { message_id, schema_ids: Option<Vec<String>>, record_keys: Option<Vec<String>> }`. Note it lowers to the *cross product* of schema ids and record keys, then re-validates the response against the exact expectations in `accept_response` (`:220-289`).
|
||||
- `snapshot_query.rs:30-56` — `CultNetReadOnlySnapshotPolicy` is an explicit `(schema_id, record_key)` allowlist, not a predicate.
|
||||
|
||||
**Portability facts.** P1: `SnapshotRequest` crosses the wire, so yes, but it carries no predicates to serialize. P2: not `schemars`-derived here. P3: in-substrate. P4: no. P5: ours.
|
||||
|
||||
**Coverage.** C1 no (identity only, no fields). C2 no. C3 no. C4 no. C5 no. This is a fetch-by-id list, not a query.
|
||||
|
||||
## I-5. CultMesh — a query *surface*, with the query shape left to the caller
|
||||
|
||||
**What it is.** The generic plumbing for "a parameterised, watchable read". It deliberately does not define what a query is.
|
||||
|
||||
**Owner.** CultLib. C# reference `F:\Projects\CultLib\src\GameCult.Mesh\CultMeshPrimitives.cs`; TS runtime `F:\Projects\CultLib\packages\cultmesh-ts\src\index.ts`.
|
||||
|
||||
**Evidence.**
|
||||
|
||||
- `CultMeshPrimitives.cs:628` — `CultMeshQuerySurface<TParameters, TResult>`; `:706` `CultMeshBoundQuerySurface<TParameters, TResult>`. `TParameters` is entirely caller-defined. The TS mirror is `packages/cultmesh-ts/src/index.ts:~643-730`.
|
||||
- `CultMeshPrimitives.cs:876-881` — `CultMeshDocumentQueryParameters` is an **empty struct** with a single `Empty` singleton. The document-handle case has no parameters at all.
|
||||
- `CultMeshPrimitives.cs:487` `CultMeshQueryContext` and `:593` its builder — this is *routing and locality* context (verse, route hint), not selection.
|
||||
- `F:\Projects\CultLib\src\GameCult.Mesh\CultMeshDiscoveryService.cs:34-57` — `CultMeshDiscoveryQuery { EndpointId, VerseIds, AuthorityRuntimeId }`. A three-field discovery filter over Verses, not over documents.
|
||||
|
||||
**Coverage.** CultMesh supplies the *slot* a query document would sit in (`TParameters`) and nothing that fills it. None of C1..C5 are answered; the type parameter is the evidence that the question was left open.
|
||||
|
||||
## I-6. Eve DSL — binding, and an explicit refusal to own selection
|
||||
|
||||
**What it is.** A composition language for interface projection. It binds components to documents and fields.
|
||||
|
||||
**Owner.** Eve, `F:\Projects\Eve`.
|
||||
|
||||
**Evidence.**
|
||||
|
||||
- `F:\Projects\Eve\docs\surface-contract-v1.md:66-71` — the binding primitives: `var` (one live value), `collection` (event-shaped ordered values), `derive` (computed fields such as counts and latest events), `bind` (component props subscribe to a var/collection/derived field).
|
||||
- `surface-contract-v1.md:156-158` — a component names `documentId`, `schemaId`, `slotId`, `presentationKind`, `routeHint`. Then, verbatim: "Provider adapters or advertised plugins own schema-specific query selection and decoding. Renderer code must not switch on provider schema ids or call provider-specific query methods to resolve a slot."
|
||||
- Selection that does exist in Eve is presentation-only and enumerated, not general. `F:\Projects\Epiphany\epiphany-core\src\atlas\eve_surface.rs:65-93` — `AtlasEveAttentionFilter` is a five-variant enum (`NeedsAttention`, `Degraded`, `Disputed`, `Stale`, `All`) lowered to a `control.select` with a fixed option list (`eve_surface.rs:~548-574`) and an advertised command marked `presentation_only: true, domain_state_effects: "none"` (`eve_surface.rs:~1146-1180`).
|
||||
|
||||
**Coverage.** Eve binds to a subset someone else selected. `derive` covers counts (C5-adjacent) at the presentation layer only. C1..C4 are explicitly out of scope by the surface contract's own sentence.
|
||||
|
||||
## I-7. Odin — two structs named Query, neither a predicate language
|
||||
|
||||
**Owner.** Odin, `F:\Projects\Odin`.
|
||||
|
||||
**Evidence.**
|
||||
|
||||
- `F:\Projects\Odin\crates\odin-daemon\src\lib.rs:324-328` — `OdinStoreQuery { incarnation: IncarnationRef, provider_signer_identity_id: String, dependencies: Vec<IncarnationRef> }`. A by-identity read; `OdinTopologyStore::read` (`:339`) returns one `OdinStoreSnapshot`. No derives — not serializable as written.
|
||||
- `F:\Projects\Odin\crates\odin-core\src\discovery.rs:6-11` — `OdinEndpointQuery<'a> { schema: Option<&'a str>, transport_contains: Option<&'a str>, host_hint: Option<&'a str>, device_filter: Option<&'a str> }`. Four optional string filters, one of them a substring match. **Borrowed lifetimes and no serde derives**: this cannot cross a wire as written. `discover_provider_endpoints` (`:22`) loops `cache().get_all::<EveProviderAdvertisementRecord>()` and filters in Rust.
|
||||
|
||||
**Coverage.** `OdinEndpointQuery` is the same flat-option-struct shape as `PipelineQuery` (I-1), arrived at independently, over a different type. Neither covers C1..C5.
|
||||
|
||||
## I-8. VoidBot — the live Qdrant filter translation, and a second flat option struct
|
||||
|
||||
**What it is.** The only place in the substrate that builds a Qdrant filter document today. Directly relevant because Cut 11 depends on Qdrant.
|
||||
|
||||
**Owner.** VoidBot, `F:\Projects\VoidBot`.
|
||||
|
||||
**Evidence.**
|
||||
|
||||
- `F:\Projects\VoidBot\packages\shared\src\index.ts:114-124` — `RetrievalFilters { corpusKind?: "discord_history" | "repository_source" | "persona_memory"; guildId?; channelId?; authorId?; identityId?; repoName?; pathPrefix?; language?; sourceId? }`. Nine optional strings, AND-only, no ordering, no cursor.
|
||||
- `F:\Projects\VoidBot\packages\rag\src\qdrant-vector-store.ts:21` — `type QdrantFilter = Schemas["Filter"]`, i.e. the types come from the Qdrant client's own generated OpenAPI schema types, not hand-written.
|
||||
- `qdrant-vector-store.ts:543-563` — `toQdrantFilter`: builds `const must: Schemas["Condition"][]`, appends one exact match per set field, returns `must.length > 0 ? { must } : undefined`. **`should` and `must_not` are never used.** The full Qdrant boolean model is available and only the AND arm is exercised.
|
||||
- `qdrant-vector-store.ts:566-584` — `buildMatchAnyFilter(field, values)` produces a `match: { any: [...] }` condition, used only for delete-by-sourceId batches (`:119-125`).
|
||||
- `qdrant-vector-store.ts:129-136` `deleteByFilters`, `:142-162` search with `filter: toQdrantFilter(filters)`.
|
||||
- Consumers: `packages/rag/src/{file-vector-store,in-memory-vector-store,message-archive,retrieval-service,source-vector-store}.ts` all import `RetrievalFilters` — so the same nine-field struct is the filter shape for the in-memory store, the file store and Qdrant alike.
|
||||
- The MCP tool surface exposes a hand-written subset of the same fields: `packages/providers/src/local-llm-tools.ts:79-115` (`search_sources` with `repoName`, `pathPrefix`, `language`, `limit`).
|
||||
|
||||
**This is the strongest in-substrate evidence of the pressure the brief names.** Two independent flat option-structs (`PipelineQuery`, `RetrievalFilters`) plus a third (`OdinEndpointQuery`) exist because no general one does, and `RetrievalFilters` additionally flattens Qdrant's boolean model down to `must`-only on the way through.
|
||||
|
||||
## I-9. Huginn's own README and AGENTS — authority boundary
|
||||
|
||||
Recorded because it bounds where a query surface may live. `F:\Projects\Huginn\README.md:14-24` and `AGENTS.md:26-40`: Huginn owns `.cc` inspection projection and Eve DSL emission for `cultcache.huginn.inspector`; it does **not** own CultCache persistence. Note the AGENTS file still names `E:\Projects\Huginn` as project root while the live tree is `F:\Projects\Huginn` — stale, flagged, not part of this question.
|
||||
|
||||
---
|
||||
|
||||
# Part II — Prior attempts and abandonments in our own history
|
||||
|
||||
## II-1. `ThreadEpiphanyGraphQuery` — a serializable citation-hop query, built and deleted
|
||||
|
||||
**What it was.** The closest thing the substrate has ever had to C4 as data.
|
||||
|
||||
**Evidence.**
|
||||
|
||||
- Added: Epiphany commit `5c62a650` "Add Epiphany graph query surface", 2026-04-29.
|
||||
- Shape, from the generated TS at `vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyGraphQuery.ts` in that commit:
|
||||
`{ kind: ThreadEpiphanyGraphQueryKind, nodeIds?: string[], edgeIds?: string[], paths?: string[], symbols?: string[], edgeKinds?: string[], direction?: ThreadEpiphanyGraphQueryDirection | null, depth?: number | null, includeLinks?: boolean | null }`
|
||||
- `ThreadEpiphanyGraphQueryKind = "node" | "path" | "frontierNeighborhood" | "neighbors"`.
|
||||
- `ThreadEpiphanyGraphQueryDirection = "incoming" | "outgoing" | "both"` — **both directions of the hop, as data**.
|
||||
- Doc comments in the generated file: direction "Defaults to `both`"; depth "Defaults to 1 and is capped by the server"; includeLinks "Defaults to true so neighborhood queries preserve dataflow/architecture joins".
|
||||
- Schema was **derived, not hand-written**: `// This file was generated by [ts-rs]`. The same commit added `codex_app_server_protocol.schemas.json` and `.v2.schemas.json` (138 lines each) and `ClientRequest.json` (85 lines).
|
||||
- Deleted: Epiphany commit `5f6f2441` "Remove Epiphany app-server compatibility surface", 2026-07-12. Nothing named `*GraphQuery*` survives in the tree today (`find` returns nothing outside `node_modules`).
|
||||
|
||||
**What happened to it.** It was not removed for being a bad query shape. It went out with the whole app-server compatibility surface it lived in — it was a Codex protocol type in `vendor/`, carried on ts-rs and JSON Schema generation, and died with its host. Related surviving history: `2ea57376` "Add local Verse context query", `e2a8b479` "Read graph query from memory graph store", `fa222584` "Move retrieval query law to core", `0cf4b081` "Move graph query smoke to Rust", `bf433d63` "Make semantic readiness proof query-owned".
|
||||
|
||||
**Coverage it had.** C1 partial (id/kind sets, not per-kind field predicates), C2 via `includeLinks` only, C3 no cursor but a server-capped depth, **C4 yes — direction and depth as data**, C5 no.
|
||||
|
||||
## II-1b. Selective hydration, deleted from CultCache three days ago
|
||||
|
||||
**What it was.** A filter applied at the store layer so a pull could hydrate a subset rather than the whole image.
|
||||
|
||||
**Evidence.** CultLib commit `900e148d8ad6dceed4b9c14dac23d12a869cb539`, "Read only v4 directory stores; delete probes and selective hydration", 2026-09-13. Its own message: "DirectoryMessagePackBackingStore loses **HydrationFilter**, FlushStageProbe, ReadStageProbe and their stage constants, ReadPersistedGeneration, **PullSelected**, and every legacy path ... **CacheBackingStore.PullSelected** and CultPersistedRecordMetadata go with them."
|
||||
|
||||
**What happened to it.** It went out with the v1/v2/v3 store formats in a format-narrowing cut, not as a judgement on filtering. But the effect stands: as of three days before this pass, CultCache's backing-store surface has **no way to pull a subset**. `pull_all_read_only_snapshot` and `pull_all_backing_stores` are what remain. Any query surface above it reads the whole image, which is what `huginn-mind` does (`query.rs:207-208`, `Docs::from_image(mind.envelopes())` per call).
|
||||
|
||||
**Also worth recording:** every other CultLib commit matching query/filter/predicate/selector is about **schema-alias or shard-membership resolution**, not field predicates — e.g. `54543b5` "Resolve Python shard discovery filters by schema alias", `1b9140d` "Resolve C# subscription filters by schema alias", `2738f4c` "Resolve TS snapshot filters by schema alias", `7ad74cf` "Filter Python snapshots by shard membership". "Filter" in CultLib means "which records by schema and shard", never "which documents by field value".
|
||||
|
||||
## II-2. The RethinkDB lineage — what the archive actually says
|
||||
|
||||
The operator's account is **confirmed as to adoption and abandonment**, and **not confirmed as to ReQL or changefeeds specifically**.
|
||||
|
||||
**Adoption, with the stated reasons.** Discord `#development`, 2020-03-15:
|
||||
|
||||
- `688549686373515270` (00:50): "I've decided [Tympanic] is right, and abusing git for document storage is a bad strategy. I spent a couple days researching a bunch of different db solutions, I feel like I've looked at every one of them."
|
||||
- `688550094030503985` (00:52), the anchor: settled on RethinkDB for four named properties — it stores JSON, supports queries, has a realtime push feature like Firebase realtime db, and has a C# client library, "which even Firebase bafflingly lacks".
|
||||
- `688550271554682890`: "db is now live at asgard.gamecult.games:28015" (28015 is RethinkDB's client driver port).
|
||||
|
||||
**The alternative that was considered and passed over.** 2020-03-11, `#development`: Tympanic `687397982638833793` "What would be a reason to not just go straight to something like SQL? It all depends on how much data we expect to have." Metacrat `687400730948272258` "Maybe you're right about SQL though. I had to use it for my database class and didn't particularly enjoy it, but it's battle tested"; `687411066522697873` "I kinda feel like saying let's cross that bridge when we get to it, migrate to SQL if document store + git ends up being a bottleneck".
|
||||
|
||||
**Abandonment.** 2020-2021 RethinkDB was the real production database — editor tooling wrote to it (`691363946623402014`, 2020-03-22), the game would not run without it (`716084893888806983`, 2020-05-30). Then 2021-06-23, `857099401398845442`: "Pretty big change in how we're handling data here. From now on, the Rethink database is obsolete. We can still go back to it, but for now I've configured it to use JSON files when present and fall back to entries defined in a msgpack file", followed immediately by `857099432168128553` "this will make modding trivial".
|
||||
|
||||
**What that message is.** It is the CultCache origin: files plus MessagePack, chosen for moddability, replacing a server. Also `1023147757860565012` (2022-09-24) "No really take a look at rethinkdb" — still recommending it a year after dropping it.
|
||||
|
||||
**What the archive does not contain.** Searches of the indexed Discord history for ReQL, for changefeeds, and for query/filter-language design return **no hit naming ReQL or changefeeds at all**. The realtime feature is referenced only obliquely and by analogy — "a realtime push feature like Firebase realtime db". So:
|
||||
|
||||
- The inheritance the archive *documents* is: document store, JSON/schemaless records, a real client library, and realtime push. Push/watch is the dimension that visibly survives — CultMesh's `watch`/`observe` on a document handle (`CultMeshPrimitives.cs:~2010`, `packages/cultmesh-ts/src/index.ts:~886`) and `CultMeshPollingWatchOptions` (`CultMeshSnapshots.cs:~558`).
|
||||
- The inheritance the archive **does not** document is the query side. No term tree, no serialized predicate, no filter document, no selector exists anywhere in CultCache or CultNet (see I-3, I-4). CultNet's only wire selection is two id allowlists.
|
||||
- Stated plainly: on the evidence available, **the borrowing was in change notification and document-shaped storage, not in querying.** Whether that was deliberate or simply never reached is not answerable from the archive; see the open questions.
|
||||
|
||||
One adjacent scar worth recording, since it is about query complexity in our own code: `1510998723730346114` (2026-06-01), on the Aetheria codebase — a TODO cursing a LINQ query nested ten levels deep.
|
||||
|
||||
---
|
||||
|
||||
# Part III — External prior art
|
||||
|
||||
## III-1. RethinkDB ReQL wire encoding (named prior art)
|
||||
|
||||
**What it is.** A query represented not as text but as a tree of typed terms, serialized as nested arrays. Each language driver is a builder over that one wire format.
|
||||
|
||||
**Evidence.** `rethinkdb/rethinkdb`, `src/rdb_protocol/ql2.proto`, branch `next` at commit `a2cf1308b57dca2e05d744f225a082881bab3fca` (2026-03-28), 862 lines — https://github.com/rethinkdb/rethinkdb/blob/next/src/rdb_protocol/ql2.proto . Driver-authoring spec: https://rethinkdb.com/docs/writing-drivers/ . Python driver `ast.py`: https://github.com/rethinkdb/rethinkdb-python/blob/master/rethinkdb/ast.py
|
||||
|
||||
**The encoding, confirmed with one correction.**
|
||||
|
||||
- A term serializes as `[termType, [args...], {optargs}]`, and **the optargs object is omitted entirely when empty** — `ast.py:164-168`, `res = [self.term_type, self._args]; if len(self.optargs) > 0: res.append(self.optargs)`. So a term is a 2- or 3-element array, not always 3.
|
||||
- Leaf data is not wrapped; a datum is bare JSON. Documented example: `r.db("blog").table("users")` → `[15, [[14, ["blog"]], "users"]]`, where 15 = TABLE and 14 = DB.
|
||||
- Outer frame: `[QueryType, term, globalOptargs]`. `r.expr("foo")` → `[1,"foo",{}]`. `Query.QueryType`: START=1, CONTINUE=2, STOP=3, NOREPLY_WAIT=4, SERVER_INFO=5.
|
||||
- Protobuf-era messages: `Query { type=1, query(Term)=2, token=3, OBSOLETE_noreply=4, accepts_r_json=5, global_optargs(repeated AssocPair)=6 }`; `Term { type=1, datum=2, args(repeated Term)=3, optargs(repeated AssocPair)=4 }`; `Datum.DatumType` = R_NULL, R_BOOL, R_NUM, R_STR, R_ARRAY, R_OBJECT, R_JSON.
|
||||
- Framing: 8-byte LE token, 4-byte LE length, UTF-8 JSON. Response is token, length, then `{t, r, b, p, n}` = ResponseType, result, backtrace, profile, notes.
|
||||
|
||||
**Term types: closed enum, stable ids, never recycled.**
|
||||
|
||||
- `Term.TermType` holds **186 named values**; highest id is `BIT_SAR = 196`.
|
||||
- Retired ops are left as comments rather than reused: `// OBSOLETE_GROUPED_MAPREDUCE = 46;`, `// OBSOLETE_GROUPBY = 47;` (ql2.proto:452-453). Superseded ops keep their slot: `BETWEEN_DEPRECATED = 36;`. Ids are non-contiguous by design (`HTTP = 153`, `UUID = 169` sit among low-numbered ops), which is only workable because the enum is a fixed registry.
|
||||
|
||||
**Protocol versions.** `VersionDummy.Version`: V0_1 `0x3f61ba36`, V0_2 `0x723081e1`, V0_3 `0x5f75e83e`, V0_4 `0x400c2d20`, V1_0 `0x34c2bdc3`. `VersionDummy.Protocol`: PROTOBUF `0x271ffc41`, JSON `0x7e6970c7`. Originally protobuf-framed (ql2.proto:5-31: send version magic as LE32, auth-key length and key, then a length-prefixed serialized `Query` blob). The protocol-type selector arrives at **V0_3**, which is where JSON-over-socket becomes selectable. **V0_4** keeps the same handshake shape and adds parallel query execution. **V1_0** replaces the handshake with SCRAM-SHA-256 (RFC 7677) exchanged as NUL-terminated JSON objects, and protobuf is no longer a choice.
|
||||
|
||||
**P2 — was a schema published for the query document? No.** No JSON Schema or other machine-readable schema for the query document was ever published. The driver-authoring spec describes the array encoding in prose plus worked examples and points implementers at `ql2.proto` for the integer constants; third-party drivers (Go `rethinkdb-go/ql2/ql2.pb.go`, Deno, Clojure `rethinkdb-protobuf`) each vendor a copy of the `.proto` and code-generate from it. The `.proto` describes the *protobuf* framing; for the JSON protocol the term-array shape is documented prose only.
|
||||
|
||||
**What drivers owned vs. the wire.** Drivers were builders, not query engines. Driver: fluent API → term tree; 8-byte token allocation; length framing; handshake/SCRAM; JSON serialization; response demux by token; cursor and `CONTINUE` batching; pseudotype decoding on the way back (times, binary, geometry); variable-id allocation for FUNC. Server: all query semantics, type checking, arity checking, optarg validation, and backtraces. Errors return `RUNTIME_ERROR`/`COMPILE_ERROR` with a `b` backtrace of frames indexing into the term tree the driver sent; the driver renders that backtrace against its own tree (`compose()` in `ast.py`) but does **not** pre-validate. Structurally confirmed: `ast.py` is ~500 near-empty subclasses carrying only `term_type = P_TERM.X`.
|
||||
|
||||
**Function / lambda terms — the part that makes the shape unbounded.**
|
||||
|
||||
Term ids: `FUNC = 69`, `VAR = 10`, `IMPLICIT_VAR = 13`, `MAKE_ARRAY = 2`, `FUNCALL = 64`, `JAVASCRIPT = 11`.
|
||||
|
||||
- FUNC signature comment: `FUNC = 69; // ARRAY, Top -> ARRAY -> Top`. Proto text (ql2.proto:605-644), verbatim: "An anonymous function. Takes an array of numbers representing variables (see [VAR] above), and a [Term] to execute with those in scope."
|
||||
- VAR (ql2.proto:295-303), verbatim: "It's the responsibility of the client to translate from their local representation of a variable to a unique _non-negative_ integer for that variable. (We do it this way instead of letting clients provide variable names as strings to discourage variable-capturing client libraries, and because it's more efficient on the wire.)"
|
||||
- Encoding shape: `[69, [[2, [varId...]], body]]` — arg 0 is a MAKE_ARRAY of integer variable ids, arg 1 is the body term. A reference to parameter *n* is `[10, [varId]]`.
|
||||
- How a client closure is captured (`ast.py:1958-1980`): the driver reads the lambda's arity by reflection, mints a fresh globally-monotonic integer per parameter, builds `Var` placeholders, **calls the client lambda once at build time with those placeholders**, and records the term tree that the lambda's operator overloading produces. The lambda body is never inspected, compiled or transmitted; only the residue of ReQL operations survives. Host-language control flow inside the lambda runs on the client at build time and leaves no trace.
|
||||
- `r.row` is a bare `[13,[]]`. `func_wrap` (`ast.py:1951-1955`) scans a built term for an IMPLICIT_VAR and retro-wraps it as `Func(lambda x: val)`. That is why `r.row` does not work in subqueries (https://rethinkdb.com/api/python/row/) — with nesting there is no way to say which implicit scope is meant. The Java driver dropped `r.row` entirely.
|
||||
- **Implication for describing a query's shape in advance:** the term tree is a general expression tree, not a bounded predicate grammar. FUNC introduces lambda abstraction with integer binders, FUNCALL(64) applies them, BRANCH(65) is a conditional, FOR_EACH(68) iterates, and functions are first-class (HTTP's `page` optarg takes `FUNC | STRING`). Terms recurse with no depth or arity bound, and the "type system" is the doc comments (`Sequence, Function(1) -> OBJECT`), not anything enforceable. A query can be validated in advance only as far as "well-formed nested array whose head integer is a member of a 186-entry enum"; arity and type conformance are decided by the server at compile time. **The enum is closed and bounded; what can be built out of it is not.**
|
||||
- **Arbitrary client code: no.** The wire carries no client bytecode, source or serialized closure. The single escape hatch is `JAVASCRIPT = 11` — a JS string written by the developer, evaluated server-side in RethinkDB's embedded V8, default timeout 5s (https://rethinkdb.com/api/python/js/). Its second signature returns `Function(*)`, so a JS string can stand where a FUNC is expected: the one place the term grammar hands off to an opaque body.
|
||||
|
||||
**P1/P3/P4/P5.** P1: the query is pure data by construction — the drivers exist precisely because the query is the wire format, which is the property being looked for. P3: nothing to depend on; there is no reusable Rust library of the *encoding* separate from a client. P4: yes, unambiguously a front end for a database engine — the server owns every semantic. P5: Apache 2.0 (`LICENSE` at `rethinkdb@next`; GitHub reports `spdx_id: NOASSERTION` due to third-party notices). RethinkDB Inc. shut down 2016; CNCF bought the copyright and assets for $25,000, relicensed from AGPL to Apache 2.0 and contributed it to the Linux Foundation, announced 2017-02-06 (https://www.cncf.io/blog/2017/02/06/cncf-purchases-rethinkdb-source-code-contributes-linux-foundation-apache-license/); community-maintained under the `rethinkdb` org since, not a CNCF hosted project. Last server release **v2.4.4, 2023-12-11** (prior v2.4.3 2023-02-05, v2.4.2 2022-05-07). Repo not archived, 26,998 stars, 1,352 open issues, last default-branch commit `a2cf1308` 2026-03-28. Release cadence roughly one per one-to-two years.
|
||||
|
||||
**Rust drivers.** `reql` — crates.io max 0.11.2, published 2023-07-24, 96,744 all-time downloads, 886 in 90 days, MIT/Apache-2.0, repo `rethinkdb/rethinkdb-rs`, last push 2023-07-24, 213 stars, dormant ~3 years. `unreql` — a `reql` fork, max 0.2.1 published 2026-02-10, 13,437 all-time, 1,589 in 90 days, MIT, `https://github.com/vettich/un-rethinkdb-rs`; currently the only Rust driver with recent releases and its 90-day downloads now exceed `reql`'s. Also `rethinkdb-rs/thinker` (superseded, "Use the `reql` crate instead") and `mobc-reql` 0.6.4 (pool wrapper).
|
||||
|
||||
**Coverage of C1..C5.** C1 yes and then some — per-field predicates are just terms, but unbounded. C2 yes (`pluck`, `without`, `MAKE_OBJ`). C3 yes — ordering as terms, and cursors as protocol-level `CONTINUE` on a token rather than as a value in the query. C4 yes as general traversal (`eq_join`, `get_all` with an index), nothing citation-specific. C5 yes (`count`, `contains`).
|
||||
|
||||
## III-2. Qdrant's filter model
|
||||
|
||||
Recorded in detail because Cut 11 depends on Qdrant, and because VoidBot already builds these documents (I-8).
|
||||
|
||||
**REST JSON shape.** Top level `Filter`: `must`, `should`, `must_not` (each a `Condition` or an array of them), plus `min_should: {conditions: [...], min_count: uint>=1}`. Recursive — a `Condition` may itself be a `Filter`. `Condition` variants: `FieldCondition`, `IsEmptyCondition`, `IsNullCondition`, `HasIdCondition`, `HasVectorCondition`, `SliceCondition`, `NestedCondition`, `Filter`. `FieldCondition = {key, match?, range?, geo_bounding_box?, geo_radius?, geo_polygon?, values_count?, datetime_range?}`. `Match` variants: `{value}`, `{any:[...]}`, `{except:[...]}`, `{text}`, `{text_any}`, `{phrase}`, `{prefix}`. `Range = {lt,gt,gte,lte}` (floats; RFC-3339 strings for datetime). Nested keys use dot paths with array projection: `"country.cities[].population"`.
|
||||
|
||||
```json
|
||||
{
|
||||
"must": [
|
||||
{ "key": "city", "match": { "value": "London" } },
|
||||
{ "key": "price", "range": { "gte": 100.0, "lte": 450.0 } },
|
||||
{ "nested": { "key": "diet", "filter": {
|
||||
"must": [ { "key": "food", "match": { "value": "meat" } },
|
||||
{ "key": "likes", "match": { "value": true } } ] } } }
|
||||
],
|
||||
"must_not": [ { "key": "color", "match": { "any": ["red","black"] } } ],
|
||||
"should": [ { "key": "location", "geo_radius": {
|
||||
"center": { "lon": 13.403683, "lat": 52.520711 }, "radius": 1000.0 } } ],
|
||||
"min_should": { "conditions": [ { "is_empty": { "key": "reports" } } ], "min_count": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
**gRPC/protobuf shape**, `qdrant/qdrant` `lib/api/src/grpc/proto/qdrant_common.proto`, Apache-2.0: `Filter { repeated Condition should=1, must=2, must_not=3; optional MinShould min_should=4 }`; `Condition` is a `oneof` of 8 arms; `FieldCondition { string key=1; Match match=2; Range range=3; ... optional bool is_empty=9, is_null=10 }`; `Match` is a `oneof` of 11 scalar-typed arms (`keyword`, `integer`, `boolean`, `text`, `keywords`, `integers`, `except_integers`, `except_keywords`, `phrase`, `text_any`, `prefix`).
|
||||
|
||||
**The two shapes are not isomorphic.** gRPC flattens `Match` into scalar-typed oneof arms; REST uses one polymorphic `value`. A single internal shape cannot be both without choosing.
|
||||
|
||||
**P2 — yes, a schema is published.** `https://github.com/qdrant/qdrant/blob/master/docs/redoc/master/openapi.json`, OpenAPI 3.0.1, 404 component schemas, every filter type present and verified (`Filter`, `Condition`, `FieldCondition`, `Match`, `Range`, `DatetimeRange`, `GeoBoundingBox`, `GeoRadius`, `GeoPolygon`, `ValuesCount`, `IsEmptyCondition`, `IsNullCondition`, `HasIdCondition`, `HasVectorCondition`, `NestedCondition`, `MinShould`, `SliceCondition`). It is **generated from `schemars` derives** on `lib/segment/src/types.rs`, where `Filter` carries `Deserialize, Serialize, JsonSchema, Validate`. Same generator we use.
|
||||
|
||||
**Structural hazard, verified.** `Condition` and `Match` are `#[serde(untagged)]` / OpenAPI `anyOf` **with no discriminator** — variants are told apart by structure alone; `Filter`'s `must`/`should`/`must_not` additionally use a one-or-array codec. Untagged serde enums force `deserialize_any`, and `rmp-serde` has an open reported failure there: msgpack-rust issue #148, untagged enums containing enums fail under rmp-serde while working under serde_json. This is a fact about copying that shape onto a MessagePack wire.
|
||||
|
||||
**`qdrant-client` (P1/P3/P4/P5).** P1: **protobuf, not serde.** `Filter` implements `prost::Message`, `Clone`, `PartialEq`, `Debug`, `Default` — **no `Serialize`/`Deserialize`**. The crate's `serde` feature is misleading: `src/serde_impl.rs` and `src/serde_deser.rs` cover payload `Value`/`Struct`/`ListValue` only, never `Filter`/`Condition`. P3: **209 transitive crates with default features, 129 with `default-features = false`** — against our current 139 for the whole workspace. Non-optional `tokio 1.52.3` and `tonic 0.14.6`; plus `reqwest 0.13.4` (via the default `download_snapshots` feature), hyper, h2, axum, tower/tower-http, rustls + ring + webpki, mio, socket2, prost. P4: yes, assumes a running Qdrant server over gRPC; not an in-memory evaluator. P5: Apache-2.0, v1.19.0 released 2026-08-04 (server v1.19.1, 2026-09-04), repo `qdrant/rust-client`, 29 contributors, commits through 2026-09-01 — org-maintained, tracks server releases, not single-maintainer.
|
||||
|
||||
**There is no types-only Qdrant crate.** `qdrant` on crates.io is a **0.0.0 placeholder** (last touched 2024-04-18). The server's `segment` crate v0.6.0 holds the real serde+schemars types but is **workspace-internal and unpublished** (`segment` on crates.io is an unrelated Meilisearch analytics fork). `qdrant_rest_client` 0.2.1 is a third-party WASM SDK from second-state.
|
||||
|
||||
**Coverage.** C1 yes over payload fields, with real boolean algebra and ranges. C2 no (projection is a separate `with_payload` parameter, not part of the filter). C3 no in-filter ordering; paging is `offset`/`limit` or `scroll` with a point-id continuation, not part of the filter document. C4 no. C5 yes (`count` endpoint takes a filter and returns a count without bodies).
|
||||
|
||||
## III-3. Baseline for reading dependency counts
|
||||
|
||||
`serde` + `schemars` + `rmp-serde` alone resolve to **22 transitive crates** (verified with `cargo generate-lockfile`, cargo 1.95.0, counting `[[package]]`). Current versions: serde 1.0.229 (MIT/Apache-2.0), schemars 1.2.2 (MIT, 2026-07-27), rmp-serde 1.3.1 (MIT, 2025-12-23). Read against the workspace's 139.
|
||||
|
||||
## III-4. Expression evaluators and scripting languages
|
||||
|
||||
**A cross-cutting fact first: none of these derive schemars anywhere in their repositories** (GitHub code search, `total_count` 0 for each). And none resolve tokio, reqwest, hyper, sqlx or lalrpop.
|
||||
|
||||
| crate | version / date | P1 | P3 transitive | P5 |
|
||||
|---|---|---|---|---|
|
||||
| `evalexpr` | 13.1.0, 2025-11-26 | **String-parsed.** `Node` AST is public, but `feature_serde` implements **`Deserialize` only, from a string** (`deserialize_str` → `build_operator_tree`). **No `Serialize` on `Node`** — one-way. | 0 default; 24 with serde+regex+rand | **AGPL-3.0-only.** 413★, 20 contributors, last commit 2025-11-26, effectively single-maintainer (ISibboI) |
|
||||
| `rhai` | 1.26.1, 2026-09-10 | **String-parsed.** `AST` has private fields, internals behind an `internals` feature. No serde on the AST. Its `serde` feature serializes `Dynamic` *values*; `metadata` emits function signatures. Neither is an AST. | 45 (48 w/ serde). futures-core/task/util, slab, pin-project-lite (async plumbing, no runtime); wasm-bindgen, js-sys | MIT/Apache-2.0. 5,682★, commit 2026-09-16, led by Stephen Chung |
|
||||
| `fasteval` | 0.2.4, **2020-01-25** | **String-parsed.** Slab-arena AST, **no serde dependency at all.** Algebraic expressions, not a general predicate language | 0 | MIT. **Dormant** — last commit 2020-09-21, 2 contributors. Forks `fasteval2` (2023-01) and `fasteval3` (2023-11) also stale |
|
||||
| `cel` (was `cel-interpreter`/`cel-parser`) | cel 0.14.5, 2026-09-07 | **String-parsed.** AST (`Ast`, `Expr`, `CallExpr`, `ComprehensionExpr`) is public but derives only `Clone, Debug, Default, PartialEq` — **no serde, not even behind a cfg_attr.** Upstream Google CEL defines protobuf `ParsedExpr`/`CheckedExpr`; **cel-rust does not implement it** | 67 — the largest here. **LOUD: `antlr4rust` 0.5.2 (ANTLR runtime) AND `nom` 7.1.3 — two parsing frameworks.** Plus futures/parking_lot, chrono, regex, `cc` + find-msvc-tools (a C compiler in the build graph), 6 windows-* crates | MIT. 677★, pushed 2026-09-16, multi-maintainer |
|
||||
| `jsonlogic-rs` | 0.5.0, 2024-12-13 | **Structured data on the wire** (JsonLogic rules are JSON trees) — **but the Rust type is `serde_json::Value`.** No typed enum with derived Serialize/Deserialize; shape checked at runtime | 34. phf + generator/macros, rand stack, deprecated proc-macro-hack, clap 2.33.1 | MIT. Last commit 2024-12-13, 56★, single-maintainer |
|
||||
| `jsonlogic` | 0.5.1, **2020-03-05** | Same: `Value` in, `Value` out | 12 | MIT. **Abandoned**, 5★, **1 contributor** |
|
||||
| `datalogic-rs` | 5.5.0, 2026-09-12 | **Structured JSON rule, not a string.** Compiles to an arena AST behind `Logic`; **`Logic` is not Serialize/Deserialize** — the serializable artifact is the input JSON | **15 — the cleanest non-trivial tree in the survey.** bumpalo, datavalue-rs, fast-float2, itoa, ryu, self_cell, smallvec, serde. No parser generator, no async, no network, no DB | Apache-2.0. 89★, 6 contributors, 0 open issues, commit 2026-09-12, effectively single-maintainer (GoPlasmatic). Ships Node, WASM, Python, Go, Java, **.NET**, PHP and C bindings plus a 1,804-case conformance suite |
|
||||
| `jmespath` | 0.5.0, 2026-01-19 | **String-parsed.** `ast::Ast` public, 18 variants, public fields, derives `Clone, Debug, Display, PartialEq` — **no serde.** Serialize in the repo is on `Variable`, the data type. **`Ast` is not `Send`/`Sync`** (holds `Rcvar`) | 22. serde, serde_json, zmij, itoa, memchr, once_cell, wasm-bindgen; build-dep `slug` → `deunicode` | MIT. 161★, 16 contributors, org-maintained, bursty |
|
||||
| `jsonpath-rust` | 1.0.11, 2026-09-09 | **String-parsed.** `JpQuery`, `Segment`, `Selector`, `Filter`, `FilterAtom` derive `Debug, Clone, PartialEq` only — **no serde, no schemars** | 24. **LOUD: `pest` 2.7 + pest_derive/generator/meta + ucd-trie — a full parser generator.** Plus the regex stack | MIT. 159★, 23 contributors, commit 2026-09-09, ~90M downloads |
|
||||
|
||||
**P4 for the whole family:** none assumes a backing store. But each is bound to a document model — jsonlogic/jsonpath-rust/jmespath to `serde_json::Value`, datalogic-rs to `DataValue`/bumpalo, evalexpr/rhai/fasteval to their own context types. None reads typed Rust structs directly.
|
||||
|
||||
**Why the family as a whole does not meet P1, stated plainly:** six of eight are string-parsed languages whose wire form is source text. Four expose a public AST; **none of the four derive serde on it.** `evalexpr` is the only one with any AST serde impl and it is Deserialize-from-string with no Serialize. Two put structured data on the wire (the JsonLogic family), but as untyped `serde_json::Value` rather than a typed tagged union. **Zero generate a JSON Schema.**
|
||||
|
||||
## III-5. Filter and predicate crates
|
||||
|
||||
**`predicates` 3.1.4** — MIT/Apache-2.0, updated 2026-02-11, ~225M downloads, assert-rs org (Nick Stevens, Ed Page), 21 contributors. P3: 13 transitive, regex optional, nothing flagged. P4: pure in-memory `fn(&T) -> bool`.
|
||||
**Why it does not fit:** P1 fails absolutely. `Predicate<T>` is a trait; combinators produce nested Rust generic types (`AndPredicate<…>`, `FnPredicate<F,T>`) or `BoxPredicate<T>`, and `FnPredicate` wraps a Rust closure. **There is no data-shaped AST and nothing crosses a wire.** A TS or C# caller cannot construct one. Nothing to derive a schema from either.
|
||||
|
||||
**`filtrex` — does not exist as a Rust crate.** `crates.io/api/v1/crates/filtrex` returns **404**. Filtrex is JavaScript (joewalnes/filtrex, fork cshaa/filtrex) plus an Elixir port (rcdilorenzo/filtrex). Recorded because it is named in the brief's family list and is otherwise easy to assume into existence.
|
||||
|
||||
**nom-based filter languages** — the published examples are small and each carries a flag. `dsq-parser` 0.2.0 (MIT/Apache-2.0, 2026-02-18, **571 downloads**, docs.rs build **failed**): parses a DSQ string into an `Expr` AST, serde non-optional so the AST is serde-shaped, but the authoring surface is a string; **flag: `nom ^8.0`**. `tellaro-query-language` 2.0.0 (2026-09-05, 3,554 downloads): **flag: a 213-line Pest grammar plus an optional `opensearch` feature that is a network client**; crates.io reports its licence as **"non-standard"**. `filter-expr` 0.2.0 (MIT, 2026-08-13, 1,324 downloads), hand-rolled parser; **flag: `async-trait` — its evaluation path is async.** Meilisearch's `filter-parser`, the most-cited real nom filter language, is **not published** (`crates.io/api/v1/crates/filter-parser` → 404); it is workspace-internal.
|
||||
|
||||
**`sea-query` 1.0.2** — MIT/Apache-2.0, 2026-08-12, ~1.8M downloads/month, SeaQL org, multi-maintainer. P3: 16 transitive, 15 direct and nearly all optional value-type integrations; **no tokio or DB driver in the core** (those live in `sea-query-binder` and the `sea-query-*-driver` crates); `#![forbid(unsafe_code)]`.
|
||||
**Why it does not fit — two independent disqualifiers.** P1: in 1.0 `SimpleExpr` is a type alias for the `Expr` enum, and the docs.rs trait-impl list for `sea_query::expr::Expr` shows `Clone`, `Debug`, many `From<T>`, `PartialEq`, `PgExpr`, `SqliteExpr` and auto traits — **no `Serialize`, no `Deserialize`**; same for `Condition`. Its `serde` feature (one of 44) covers **value** types only (bigdecimal, chrono, uuid, rust_decimal, time, ipnetwork, pgvector). P4: output is a SQL string plus bound parameters for MySQL/Postgres/SQLite; it cannot evaluate over an in-memory collection. P2: no schemars.
|
||||
|
||||
**`diesel` 2.3.13** — MIT/Apache-2.0, 2026-09-04, ~36M downloads, lead maintainer Georg Semmler (effectively one primary maintainer, very actively maintained).
|
||||
**Why it does not fit — categorically.** The DSL is compile-time typed: each fragment is a distinct generic Rust type (`Eq<column, Bound<…>>`) implementing `QueryFragment<Backend>`. **The query exists only as a monomorphized Rust type, so there is nothing to serialize**, and a TS/C# caller can never produce one. Diesel's serde involvement is row values. P3 flag: backend features pull libpq / libmysqlclient / libsqlite3, and async needs `diesel-async` → tokio. P4: hard-assumes a database engine.
|
||||
|
||||
**`sqlx` 0.9.0** — MIT/Apache-2.0, 2026-05-21, 147M downloads, LaunchBadge org.
|
||||
**Why it does not fit:** it has no query IR at all. "Compile-time checked queries without a DSL" means SQL strings validated against a live database at compile time; there is no query object, serializable or otherwise. P3 is flagged on every axis the brief names: **mandatory async runtime (tokio/async-std/smol), full PostgreSQL/MySQL/SQLite wire-protocol drivers, TLS stacks.** P4: assumes a live database at compile *and* run time.
|
||||
|
||||
## III-6. Query builders and analytic IRs with a serializable IR
|
||||
|
||||
**`polars-plan` `Expr` (polars 0.55.2)** — MIT.
|
||||
- P1: **serializable.** `polars_plan::dsl::Expr` implements `Serialize` + `Deserialize` (confirmed on the docs.rs trait-impl list for 0.51.0 — the **0.55.2 docs.rs build failed**, last successful render is 0.51.0). Python exposes it as `Expr.meta.serialize()` / `Expr.deserialize()`, `format="binary"` (default) or `"json"`.
|
||||
- Exact feature flags: on the umbrella `polars` crate, `serde` (→ polars-buffer/core/utils) and **`serde-lazy`** (→ polars-core/io/lazy/ops/time/utils — *this* is the one that reaches the expression layer). On `polars-plan` itself the feature is plain `serde`, plus `ir_serde` for the resolved IR.
|
||||
- **The wire format is explicitly unstable.** Polars documentation states serialization is not stable across versions; a LazyFrame serialized in one version may not deserialize in another. Recurrent serde-feature build breakage: issues #3431, #3875, #18697.
|
||||
- P2: **yes, real schemars.** `polars-plan` has a **`dsl-schema`** feature pulling **schemars ^1.2.1** (was ^0.8.22 at 0.51.0), plus serde_json and sha2; it auto-enables `serde` and propagates across the sub-crates. **`dsl-schema` is not exposed on the umbrella `polars` crate's 161-feature list** — it is reachable only via `polars-plan` directly.
|
||||
- P3: very large — ~22-57 MB, **~1M SLoC**. **Flag: `polars-plan` 0.55.2 deps include `tokio` and `futures`.** Plus rayon and the arrow/compute stack; `polars-io` brings object-storage/cloud network clients when enabled. No parser generator, no DB driver.
|
||||
- P4: **runs over in-memory collections natively**, no database engine required.
|
||||
- P5: 0.55.2, 2026-08-06. 120 releases, **55 of them breaking.** ~792K downloads/month, 771 dependents. Ritchie Vink plus 100+ contributors, commercially backed (Polars Inc). **`polars-plan`'s own docs.rs page states it is an internal sub-crate "not intended for external usage"** — no public API stability guarantee.
|
||||
|
||||
**`datafusion-proto` 55.1.0 (with `datafusion`'s `LogicalPlan`)** — Apache-2.0, 2026-09-11, 67 versions, quarterly-ish majors with frequent breaking changes, 5.2M downloads, ASF project with many maintainers.
|
||||
- P1: **serializable typed data via protobuf, not serde.** `LogicalPlan`/`Expr` are Rust enums without serde derives; `datafusion-proto` converts bidirectionally to generated prost messages. TS/C# callers *can* construct these — the `.proto` is the contract. Caveat: round-tripping UDFs and extension nodes requires registering codecs on the Rust side.
|
||||
- P2: the protobuf schema is the machine-readable schema (`datafusion/proto/proto/datafusion.proto` + `datafusion_common.proto`, split into `datafusion-proto-common`/`-models` at 55.x). No schemars. `serde_json` is optional and debug-path only.
|
||||
- P3: **heavy.** 22 direct deps: arrow ^59.2.0, **`object_store ^0.13.2` (a network/cloud storage client)**, prost ^0.14.1 and 16 `datafusion-*` crates. **`datafusion-execution` 55.1.0 pulls `tokio` (twice, target-gated), `tokio-util`, `async-trait`, `futures`, `dashmap`, `parquet` and `object_store` unconditionally.** The transitive graph runs to many hundreds: you cannot take `datafusion-proto` without most of a query engine.
|
||||
- P4: ships a full execution engine. It does not require an external DB (an in-memory `MemTable` works), but you embed a catalog and a scheduler and tokio.
|
||||
- `datafusion-substrait` 55.1.0 (Apache-2.0, 2026-09-11, ~2.4M downloads, ~9,085 SLoC) converts `LogicalPlan` ⇄ Substrait protobuf and **inherits the entire DataFusion tree** — tokio, object_store, arrow, parquet all present.
|
||||
|
||||
**`substrait` (substrait-rs) 0.65.0** — Apache-2.0, 2026-08-27, edition 2024, MSRV 1.88.
|
||||
- P1: **serializable typed data is the crate's entire purpose** — "Cross-Language Serialization for Relational Algebra". Ships prost-generated `substrait::proto` (`Plan`, `Rel`, `Expression`, `FunctionArgument`, …) plus `substrait::text` for YAML extensions. TS and C# construct these from the published `.proto` with standard codegen; that is designed usage, not a workaround.
|
||||
- P2: the protobuf schema **is** the machine-readable schema, published at `substrait-io/substrait` `proto/substrait/*.proto`, packaged at `substrait-io/substrait-packaging`, bindings at `substrait-io/substrait-protobuf`. The simple-extensions YAML additionally has a real **JSON Schema 2020-12** at `text/simple_extensions_schema.yaml`. Spec nuance: Substrait is defined **only** in Protobuf; the JSON you see is Protobuf-JSON output, not an official text format. The crate's `serde` feature "generates serde implementations that match the Protobuf JSON Mapping **via pbjson**" — so serde output is proto-canonical JSON, not an independent serde shape. **No schemars.**
|
||||
- P3: direct deps indexmap, prost, serde, serde_json, substrait-extensions, substrait-prost; feature-gated hex, serde_yaml, thiserror; build-deps semver, toml, prost-types. ~6-9.5 MB / **~196K SLoC**, the bulk being generated prost code. **No tokio, no network client, no DB driver, no parser generator.** The only flag is the optional `protoc`/`protox` features, which invoke a protobuf compiler at build time (`protox` is pure Rust, no external binary). Seven features, none default: embed-descriptor, extensions, parse, protoc, protox, semver, serde.
|
||||
- P4: **no backing store and no engine.** Pure IR types plus serialization; **it does not evaluate anything** — you supply the evaluator.
|
||||
- P5: 162 versions, fast cadence. **17.5M lifetime / ~892K recent downloads.** Owner Jacques Nadeau with co-owners Matthijs Brobbel, Victor Barua, YongChul Kwon, Weston Pace, 13+ contributors — a cross-vendor spec body, not single-maintainer. The repo itself is modest (92★). Still 0.x: no 1.0 stability promise on the Rust binding, and the spec versions independently.
|
||||
|
||||
## III-7. Document-store and protocol filter documents, reusable as a shape
|
||||
|
||||
**Only four of the surveyed protocols put a structured document on the wire:** MongoDB filters, Elasticsearch/OpenSearch query DSL, Firestore StructuredQuery, and Kubernetes `LabelSelector` (plus JSON Schema, structured but a different kind of predicate). OData, AIP-160, CESQL, GraphQL documents and the Kubernetes `labelSelector`/`fieldSelector` query parameters are all **strings**.
|
||||
|
||||
**Only three publish a machine-readable schema of the query shape itself:** OpenSearch, Elastic, Firestore. Kubernetes publishes one for `LabelSelector` specifically. **MongoDB — the most imitated shape of all — publishes none.**
|
||||
|
||||
### MongoDB query documents and BSON
|
||||
Structured. Keys are dot-notation field paths; values are literals (implicit `$eq`) or operator documents. `$and`/`$or`/`$nor` take arrays of filter documents; **`$not` wraps an operator document, not a full filter** — a known asymmetry. Operators: `$eq $ne $gt $gte $lt $lte $in $nin $exists $type $regex $mod $all $elemMatch $size $bitsAllSet $expr $jsonSchema $text $where`. Example: `{ "$and": [ {"status":"active"}, {"score":{"$gte":10,"$lt":100}}, {"tags":{"$in":["a","b"]}}, {"meta.region":{"$ne":"eu"}} ] }`.
|
||||
|
||||
**P2: no.** A BSON 1.1 wire spec exists (bsonspec.org/spec.html) and driver behaviour specs at github.com/mongodb/specifications, but **no published JSON Schema or IDL for the legal shape of a filter document.** Operator semantics are prose. `$jsonSchema` is a validator operator, not a schema of the filter language.
|
||||
|
||||
`bson` v3.1.0, 2025-11-20, MIT — **fully standalone, does not require the driver.** Default features are only `compat-3-0-0` (a no-op shim); **`serde` is optional**, and with it `bson::Document` implements Serialize/Deserialize. **No tokio anywhere.** Optional chrono-0_4, jiff-0_2, time-0_3, uuid-1, serde_json-1, serde_with-3, large_dates; also zero-copy `RawDocument`/`RawDocumentBuf`. MessagePack caveat: BSON has types with no MessagePack analogue (ObjectId, Decimal128, Binary subtypes, Timestamp, MinKey/MaxKey), which round-trip through serde as extended-JSON-ish maps rather than natively.
|
||||
|
||||
`mongodb` v3.9.1, 2026-09-10, Apache-2.0 — **loud: `tokio ^1.17` is a non-optional dependency** (io-util, sync, macros, net, process, rt, time, fs), plus tokio-util, futures-*, a crypto stack (hmac, md-5, sha1, sha2, pbkdf2, stringprep), base64, rand; optional rustls/openssl, reqwest, hickory-dns, mongocrypt, aws-sigv4. Notably `bson` itself is *optional* there.
|
||||
|
||||
### Elasticsearch / OpenSearch Query DSL
|
||||
Structured. `bool` with `must`/`should`/`must_not`/`filter` plus `minimum_should_match` and `boost`; leaves `term`, `terms`, `range` (gt/gte/lt/lte/format/time_zone), `match`, `match_phrase`, `exists`, `prefix`, `wildcard`, `regexp`, `ids`, `nested`, `script`. Polymorphism is "a single-key object selects the variant" — a tagged union expressed as an object with exactly one key, which is precisely why Elastic had to write a custom spec language.
|
||||
|
||||
**P2: yes, both vendors.** Elastic: `elastic/elasticsearch-specification` (Apache-2.0); source of truth is TypeScript definitions and the compiler emits `output/schema/schema.json`, **a custom JSON type-model — not JSON Schema and not OpenAPI** — covering ~500 endpoints and ~3000 types including `QueryContainer`, `BoolQuery`, `TermQuery`, `RangeQuery`. OpenSearch: `opensearch-project/opensearch-api-specification` (Apache-2.0, **OpenAPI 3.1.0**), query DSL in `spec/schemas/_common.query_dsl.yaml` (~1400 lines), `QueryContainer` with 60+ variants, plain OpenAPI/JSON-Schema-flavoured.
|
||||
|
||||
Rust: the official `elasticsearch` 9.1.0-alpha.1 (2025-08-08, Apache-2.0) is **a full HTTP client, reqwest + tokio**, and its bodies are `serde_json::Value` anyway — no typed DSL. Types-only alternatives exist: `elasticsearch-dsl` 0.4.13 (~2023, MIT/Apache-2.0, explicitly independent of the official client, no HTTP, serde types, **no runtime deps**) and `opensearch-dsl` (a fork). Both stale-ish and community-maintained.
|
||||
|
||||
### Firestore StructuredQuery (protobuf)
|
||||
Structured, protobuf, `google/firestore/v1/query.proto` in googleapis/googleapis, Apache-2.0. `StructuredQuery { select, from[], where: Filter, order_by[], start_at, end_at, offset, limit, find_nearest }`. `Filter` is a **oneof of exactly three**: `composite_filter`, `field_filter`, `unary_filter`. `CompositeFilter { op: AND|OR, filters: repeated Filter }` — recursion lives here and only here. `FieldFilter { field: FieldReference, op, value: Value }` with ops `LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, EQUAL, NOT_EQUAL, ARRAY_CONTAINS, IN, ARRAY_CONTAINS_ANY, NOT_IN`. `UnaryFilter { field, op }` with `IS_NAN, IS_NULL, IS_NOT_NAN, IS_NOT_NULL`. `FieldReference { field_path }`, dot-delimited. **There is no NOT combinator** — negation exists only as `NOT_EQUAL`/`NOT_IN`/`IS_NOT_*`.
|
||||
|
||||
JSON mapping is standard protobuf JSON (lowerCamelCase, enums as string names):
|
||||
`{"where":{"compositeFilter":{"op":"AND","filters":[{"fieldFilter":{"field":{"fieldPath":"status"},"op":"EQUAL","value":{"stringValue":"active"}}},{"fieldFilter":{"field":{"fieldPath":"score"},"op":"GREATER_THAN_OR_EQUAL","value":{"integerValue":"10"}}},{"unaryFilter":{"field":{"fieldPath":"deletedAt"},"op":"IS_NULL"}}]}}}`
|
||||
|
||||
Rust: **no first-party Google crate.** Generated bindings (`googleapis-tonic-google-firestore-v1`, `gcloud-sdk`, `firestore`) — **loud: tonic ⇒ hyper + tokio + prost; `gcloud-sdk` adds an HTTP and auth stack.** The runtime-free path is taking the Apache-2.0 `.proto` and running prost yourself, which is build-time codegen and gives protobuf encoding rather than MessagePack.
|
||||
|
||||
### Kubernetes label selectors and fieldSelector — two distinct surfaces
|
||||
`labelSelector` **as a query parameter on LIST/WATCH is a string**: `environment=production,tier!=frontend`, `environment in (production,qa)`, `partition`, `!partition`. Comma means AND. **No OR at all.** The `LabelSelector` **API object** embedded in Deployment/Service specs is **structured**: `{matchLabels:{k:v}, matchExpressions:[{key, operator: In|NotIn|Exists|DoesNotExist, values:[...]}]}`, all terms AND, **no OR, no nesting, no recursion.** `fieldSelector` is **string only** (`metadata.name=foo,status.phase!=Running`) with no structured equivalent and per-resource, mostly undocumented selectable fields.
|
||||
|
||||
**P2: yes for `LabelSelector`** — `io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector` and `.LabelSelectorRequirement` in the Kubernetes OpenAPI (kubernetes/kubernetes `api/openapi-spec`, served live at `/openapi/v3`). `k8s-openapi` v0.28.0, 2026-06-15, Apache-2.0, generated from that spec, serde-derived, **no async runtime, types only** (the runtime hazard lives in `kube`, which pulls tokio/hyper/tower). Requires a `v1_xx` feature flag and is a very large crate if you want two structs.
|
||||
|
||||
**Partial fit, stated plainly:** the smallest well-schematized structured predicate in the survey and proof the pattern works — but flat and conjunction-only.
|
||||
|
||||
### JSON:API filter
|
||||
**Unspecified.** Spec v1.1 reserves the `filter` query-parameter family and states it is agnostic about the strategies servers support. No operators, no grammar, no nesting rules, no document shape. jsonapi.org publishes a JSON Schema for the *response* document, not for filters. No Rust crate.
|
||||
**Why it does not fit:** it is a naming convention for a URL query parameter, not a predicate language. The only transferable idea is `filter[field]=value`, a string/URL encoding that cannot carry typed values or nesting.
|
||||
|
||||
### OData `$filter`
|
||||
**String expression:** `$filter=Price gt 20 and (contains(Name,'milk') or Category/Name eq 'Dairy')`. Operators `eq ne gt ge lt le and or not in has`, arithmetic, functions, lambda `any`/`all`, `/` traversal. **A machine-readable grammar is published — as ABNF, not as a data schema:** OASIS OData 4.01 ABNF Construction Rules (docs.oasis-open.org/odata/odata/v4.01/cs01/abnf/odata-abnf-construction-rules.txt, source at oasis-tcs/odata-abnf). CSDL JSON schematizes the *entity model*, not the filter (OASIS Standard 4.01, 2020-05-11; validating schemas at oasis-tcs/odata-csdl-schemas). So the data model has a JSON schema and the predicate language does not. No mainstream typed-AST Rust crate exists; **any usable path means an ABNF/PEG parser generator and you own the AST and the MessagePack mapping.**
|
||||
**Why it does not fit:** string-first by design. Serializing it as typed data means inventing the AST OASIS deliberately never published.
|
||||
|
||||
### GraphQL
|
||||
**The query is a string** — a GraphQL document parsed server-side. Filters inside it are arguments typed by **input object types** (`input UserFilter { and: [UserFilter!], or: [UserFilter!], age: IntFilter }`), which is a genuinely reusable predicate shape — but values reach the server either as literals in the query string or as JSON `variables`, so only the variables half is structured data. Spec: GraphQL October 2021 (spec.graphql.org/October2021/). **P2: yes for the type system** — SDL plus standardized introspection (`__schema`, `__type`, `__InputValue`), so clients can discover filter input types at runtime. **No standard schema for filter semantics**: there is no canonical `IntFilter`/`and`/`or` vocabulary, and Hasura, Postgraphile, Prisma and Dgraph each invent their own operator names. Rust: async-graphql, juniper, graphql-parser — **flag: a parser, and for servers an async runtime.**
|
||||
**Partial fit:** the input-object pattern is directly stealable; the transport is not.
|
||||
|
||||
### JSON Schema as a predicate language
|
||||
Draft **2020-12** is current (json-schema.org/draft/2020-12); IETF standardization is ongoing (`draft-ietf-jsonschema-json-schema-03`, Aug 2026). The ecosystem is fragmented across draft-07 / 2019-09 / 2020-12. A schema is itself JSON, and it is **self-describing** — meta-schema at json-schema.org/draft/2020-12/schema.
|
||||
**Can express:** full boolean algebra including **`not`** — real negation, which both MongoDB and Firestore lack — plus type checks, numeric ranges, string constraints (`pattern`, `minLength`, `format`), `enum`, `const`, array constraints (`contains`, `minContains`, `prefixItems`, `uniqueItems`), object constraints, `if`/`then`/`else`, and `$ref`/`$dynamicRef` recursion.
|
||||
**Cannot express:** comparison between two fields of the same document (no `a.x > a.y`); any reference to an external value or parameter — every bound is a literal baked into the document; sorting, projection, aggregation or joins; arithmetic; dot-path addressing in a predicate sense — a deep-field range check requires nested `properties` mirroring the document rather than `"a.b.c": {gte: 5}`. Parameterizing a query means rewriting the schema document.
|
||||
Rust: `jsonschema` (MIT, active) — **flag: a regex engine, fancy-regex, URL resolution, and optionally `reqwest` for remote `$ref` fetching.** `schemars` generates schemas from Rust types but is not an evaluator.
|
||||
|
||||
### CloudEvents SQL (CESQL)
|
||||
**String expression**, SQL-like: `subject LIKE '%mySubject%' AND myext = 'value' AND (type = 'a' OR type = 'b')`, evaluating to boolean/integer/string. **v1.0 approved 2024-06-13**, github.com/cloudevents/spec/blob/main/cesql/spec.md, Apache-2.0 (CNCF), SDKs for Go and Java. **No document schema** — the spec defines an ANTLR-based grammar, not a serializable AST — and it operates over the flat CloudEvents attribute set, so there are no nested paths. **No Rust crate**; `cloudevents-sdk` has no CESQL module. **Flag: porting means a parser generator.**
|
||||
|
||||
### Cross-cutting structural facts for this family
|
||||
- **Recursion and negation matrix:** MongoDB has `$and`/`$or`/`$nor` plus operator-scoped `$not`; Elasticsearch's `bool` has `must_not` and nests freely; **Firestore has AND/OR recursion but no NOT combinator**; Kubernetes `LabelSelector` has neither recursion nor OR; JSON Schema has full boolean algebra including `not`.
|
||||
- **Tagged-union encoding differs sharply.** MongoDB and Elasticsearch use "object with a single magic key" (`{"$gt":5}`, `{"term":{…}}`) — cheap in JavaScript, painful in C# and for MessagePack codegen, and the same untagged-discrimination problem Qdrant has (III-2). Firestore's protobuf `oneof` with named enum operators and Substrait's proto messages are the explicitly-tagged counterexamples in the set, and both map cleanly to TypeScript discriminated unions and C# sealed hierarchies.
|
||||
- **Runtime-free Rust type sources that were found:** `bson` 3.1.0 + serde (MIT, no tokio, standalone); `k8s-openapi` 0.28.0 (Apache-2.0, types-only, large); prost-generating from Firestore's `query.proto` yourself (Apache-2.0 proto, build-time codegen); `elasticsearch-dsl` 0.4.13 (MIT/Apache, no HTTP, stale). **Carrying runtimes:** `mongodb` (mandatory tokio), `elasticsearch` (reqwest + tokio), `kube` (tokio/hyper/tower), and every OData/AIP-160/CESQL path (parser generators).
|
||||
|
||||
## III-8. Cursor and pagination prior art as data
|
||||
|
||||
**Google AIP-158** (https://google.aip.dev/158). Request: `int32 page_size` (optional; the API may choose a default and return fewer) and `string page_token`. Response: repeated results as the **first** field, `string next_page_token` (empty means end), optional `int32 total_size`. Opacity, verbatim: page tokens **must** be opaque (but URL-safe) strings and **must not** be user-parseable. **No encoding is mandated.** The AIP *suggests* an internal protobuf message, serialized and base64-encoded, for non-sensitive tokens — and warns that base64-encoding an otherwise-transparent token is **not** sufficient obfuscation. A server-side handle is not required: tokens may carry state, but scope is strict — they must be limited to indicating where to continue and **must not provide any form of authorization**, which is re-checked every call. Expiry is permitted, ~3 days named as reasonable if the server stores data behind the token. No cryptographic guidance.
|
||||
|
||||
**AIP-159** covers the `-` wildcard for cross-collection reads and **contains nothing about page tokens.** Only tangentially relevant: cross-parent requests should avoid `order_by` because ordering is ambiguous.
|
||||
|
||||
**Relay GraphQL Cursor Connections** (https://relay.dev/graphql/connections.htm). `Connection { edges: [Edge], pageInfo: PageInfo! }`; `Edge { node, cursor }` where `node` may be Scalar/Enum/Object/Interface/Union or Non-Null thereof and **must not be a list**, and `cursor` is a String or a custom scalar that serializes as a String; `PageInfo { hasPreviousPage: Boolean!, hasNextPage: Boolean!, startCursor: String, endCursor: String }`. Arguments `first`/`after` and `last`/`before`. Verbatim: the cursor field's result "should be considered opaque by the client, but will be passed back to the server." `startCursor`/`endCursor` must correspond to the first and last nodes and **can be null when there are no results**. **The spec mandates no encoding — base64 appears nowhere in it.** Base64 of `type:id` or of an offset is ecosystem convention (graphql-js, graphql-ruby, Relay tooling), not spec text. Neither the HTML nor the markdown source carries a printed version or date; the spec's own example uses the placeholder `after: "opaqueCursor"`.
|
||||
|
||||
**Rust crates offering an opaque-cursor type.** The exact names `cursor-pagination`, `page-token`, `seek-pagination` and `opaque-cursor` return **zero crates.io results**.
|
||||
|
||||
- **`use-cursor` 0.0.1** (crates.io/crates/use-cursor, repo RustUse/use-api). Types `OpaqueCursor`, `AfterCursor`, `BeforeCursor`, `NextCursor`, `PreviousCursor`, `CursorDirection`. P1: **a string newtype, not typed data** — `OpaqueCursor::new("…")` / `as_str()`, **no serde.** P2: no. P3: **zero dependencies.** P4: store-agnostic, and it does no encoding or decoding at all — a validated string wrapper plus direction slots. P5: MIT/Apache-2.0, a single release 2026-05-25, **132 downloads**, single maintainer, Rust 1.95 / edition 2024, ~170 LoC. One release, no track record.
|
||||
- **`api-paginate` 0.1.1** (crates.io/crates/api-paginate). `PaginationParams`, `PaginatedResponse`, `CursorPagination { cursor: Option<String>, limit: u32 }`. P1: **the envelope is serde-typed; the cursor itself is `Option<String>`**, with no encode/decode helpers. P2: no schemars; OpenAPI via an optional `utoipa ^4`. P3: serde plus optional utoipa; dev-only criterion/proptest; **no runtime, network, DB or parser deps.** P4: store-agnostic. P5: MIT/Apache-2.0, 0.1.0 2026-09-01 and 0.1.1 2026-09-12, **27 total downloads**, single maintainer, ~196 LoC. Two weeks old.
|
||||
- **`paginator-utils` / `paginator-rs` 0.4.0** (crates.io/crates/paginator-rs). Keyset cursors, "URL-safe Base64 and validated on decode", `next_cursor`/`prev_cursor` derived from boundary rows. P1: string cursors over base64; the surrounding page structs are serde. P2: no schemars (a `paginator-zod` sibling generates Zod/TS schemas, 32 downloads). P3: utils deps are base64 ^0.22, serde, serde_json — clean, **no async runtime in core** — but the family fans out into `paginator-axum`/`-actix`/`-rocket`/`-sqlx`/`-sea-orm`/`-surrealdb`, which pull web servers and DB drivers. P4: core is store-agnostic. P5: MIT, 0.4.0 2026-09-07, 10 releases since 2025-05-07, ~16.8K downloads, **single maintainer**.
|
||||
- **`bomboni_request` 0.5.0** — the only AIP-158-shaped page-token implementation found. `PlainPageTokenBuilder` plus base64, AES-GCM-encrypted and RSA-encrypted variants; tokens are typed and serde-serializable. P2: no schemars. P3: **loud.** Non-optional aes-gcm 0.11, rsa 0.9, blake2, base64ct, **prost 0.14**, **pest + pest_derive 2.8 (a parser generator)**, regex, itertools, serde, thiserror, time, and four internal `bomboni_*` crates; optional **tonic 0.14**, mysql_common, postgres-types, wasm-bindgen. This is a filter/query-language framework, not a cursor primitive. P5: crates.io reports the licence as **"Non-standard"**; 0.5.0 2026-07-20, 65 versions since 2023-11, 106K downloads, single maintainer.
|
||||
- Inspected and **not** opaque-cursor primitives: `page-turner` 1.0.0 (an abstraction over *calling* paginated APIs as a stream; futures 0.3, tokio dev-only; defines no cursor type); `mongodb-cursor-pagination` 0.3.2 (**pulls the MongoDB driver**, stale since 2023); `paginate` 1.1.11 (offset math, 2022); `page-hunter` 0.7.0 (offset model); `rdb-pagination`, `pg_filters`, `diesel_filter`, `diesel_pagination`, `sqlx-data-params`, `clickhouse-filters` (all SQL/ORM-bound); `api-bones` 6.12.0 (a whole REST-envelope framework).
|
||||
|
||||
**Stated plainly: no general-purpose, standalone, typed opaque-cursor crate exists in Rust.** The four obvious names are unpublished; the only crate whose stated purpose is modelling an opaque cursor (`use-cursor`) is a zero-dep **string newtype** at 0.0.1 with no serde that explicitly declines to assume a serialization; `api-paginate` types only the envelope; and everything with real download numbers is framework- or database-bound or is offset arithmetic. **Nothing found offers schemars or JSON Schema for a cursor type** — the closest schema stories are `utoipa` (api-paginate) and `paginator-zod`.
|
||||
|
||||
**Framework-bound cursors worth recording.**
|
||||
- **async-graphql 7.2.1** (2026-09-12, MIT/Apache-2.0): `trait CursorType { type Error: Display; fn decode_cursor(s:&str)->Result<Self,Self::Error>; fn encode_cursor(&self)->String; }` (not object-safe), implemented for primitive ints/floats, bool, char, String, Uuid, `DateTime<Utc>`, Timestamp and ID — plus **`OpaqueCursor<T>` where `T: Serialize + DeserializeOwned`**, "encode/decode the value to base64". **This is the one place in the entire survey where a cursor is a typed serde value with a defined wire encoding, and it is framework-bound.**
|
||||
- **sea-orm** has two mechanisms: `Paginator` (`.paginate(db, page_size)`) is offset/limit page numbers with no token; `Cursor` (`.cursor_by(col)`, PR SeaQL/sea-orm#822) is true keyset with `.after()/.before()/.first()/.last()`, where **the cursor value is the raw typed column value** — no serialization, no base64, nothing wire-shaped.
|
||||
- **diesel** has **no built-in pagination.** The canonical approach is the "Extending Diesel" guide: hand-roll `Paginated<T>` implementing `QueryFragment`, emitting `SELECT *, COUNT(*) OVER () FROM (subquery) LIMIT … OFFSET …`. Offset-based, Postgres-specific for the window count, no cursor type. Third-party helpers (`diesel_pagination` 2.0.0, `diesel_filter` 2.0.0, `length_aware_paginator`) reproduce the same offset pattern.
|
||||
|
||||
## III-9. Two cross-cutting facts that touch the MessagePack constraint
|
||||
|
||||
1. **Nothing surveyed targets MessagePack natively.** Serde-based candidates (polars `Expr`) would go through `rmp-serde`. Protobuf-based candidates (substrait, datafusion-proto, Firestore, qdrant-client) have protobuf-binary and protobuf-JSON as their defined encodings; MessagePack means encoding the proto-derived structure yourself, or going proto → pbjson → serde → rmp, which drops the protobuf wire contract.
|
||||
2. **Untagged-union discrimination is the recurring hazard.** Qdrant (`Condition`, `Match`), MongoDB (`{"$gt":5}`) and Elasticsearch (`{"term":{…}}`) all discriminate variants by structure or by a single magic key rather than an explicit tag. `#[serde(untagged)]` forces `deserialize_any`, and `rmp-serde` has an open reported failure there — msgpack-rust issue #148, untagged enums whose variants contain enums fail under rmp-serde while succeeding under serde_json. Firestore's protobuf `oneof`, Substrait's proto messages and ReQL's leading integer term id are the explicitly-tagged counterexamples in the set.
|
||||
|
||||
**Sources for Part III-2 through III-9:** [qdrant filtering](https://qdrant.tech/documentation/concepts/filtering/) · [qdrant openapi.json](https://github.com/qdrant/qdrant/blob/master/docs/redoc/master/openapi.json) · [qdrant_common.proto](https://raw.githubusercontent.com/qdrant/qdrant/master/lib/api/src/grpc/proto/qdrant_common.proto) · [qdrant-client Filter](https://docs.rs/qdrant-client/latest/qdrant_client/qdrant/struct.Filter.html) · [lib.rs qdrant-client](https://lib.rs/crates/qdrant-client) · [msgpack-rust #148](https://github.com/3Hren/msgpack-rust/issues/148) · [serde enum representations](https://serde.rs/enum-representations.html) · [evalexpr](https://github.com/ISibboI/evalexpr) · [rhai](https://docs.rs/rhai/1.26.1/rhai/) · [fasteval](https://github.com/likebike/fasteval) · [cel](https://docs.rs/cel/0.14.5/cel/) · [datalogic-rs](https://github.com/GoPlasmatic/datalogic-rs) · [jmespath.rs](https://github.com/jmespath/jmespath.rs) · [jsonpath-rust](https://github.com/besok/jsonpath-rust) · [predicates](https://lib.rs/crates/predicates) · [sea-query Expr](https://docs.rs/sea-query/1.0.2/sea_query/expr/enum.Expr.html) · [polars-plan features](https://docs.rs/crate/polars-plan/latest/features) · [substrait-rs](https://docs.rs/substrait/latest/substrait/) · [simple_extensions_schema.yaml](https://github.com/substrait-io/substrait/blob/main/text/simple_extensions_schema.yaml) · [datafusion-proto deps](https://deps.rs/crate/datafusion-proto/55.1.0) · [bson](https://crates.io/crates/bson) · [elasticsearch-specification](https://github.com/elastic/elasticsearch-specification) · [opensearch query_dsl.yaml](https://github.com/opensearch-project/opensearch-api-specification/blob/main/spec/schemas/_common.query_dsl.yaml) · [Firestore query.proto](https://github.com/googleapis/googleapis/blob/master/google/firestore/v1/query.proto) · [AIP-160](https://google.aip.dev/160) · [AIP-158](https://google.aip.dev/158) · [Relay Connections](https://relay.dev/graphql/connections.htm) · [k8s-openapi](https://crates.io/crates/k8s-openapi) · [CESQL](https://github.com/cloudevents/spec/blob/main/cesql/spec.md) · [async-graphql CursorType](https://docs.rs/async-graphql/latest/async_graphql/types/connection/trait.CursorType.html) · [use-cursor](https://crates.io/crates/use-cursor) · [sea_orm::Cursor](https://docs.rs/sea-orm/latest/sea_orm/struct.Cursor.html)
|
||||
|
||||
---
|
||||
|
||||
# Questions this pass could not answer
|
||||
|
||||
1. **Was the query side of RethinkDB deliberately not carried into CultCache, or simply never reached?** The archive names four adoption reasons and none of them is ReQL; ReQL and changefeeds are never mentioned by name in the indexed Discord history. Nothing found says "we chose not to have queries". **To answer:** ask the operator directly. The archive cannot settle intent, only what was said.
|
||||
|
||||
2. **Does the 2020-2021 Aetheria-Economy tree contain the C# ReQL driver usage, and what query shapes were actually written against it?** That would show which ReQL constructs were ever exercised here, which is different from what ReQL offered. **To answer:** check out `GameCult/Aetheria-Economy` around commits from 2020-03 to 2021-06 and grep for the C# driver's term builders. Not indexed by voidbot under a name I could confirm.
|
||||
|
||||
3. **Which of the thirteen document kinds actually need per-kind predicates, and on which fields?** I recorded what the types expose (`severity`, `confidence`, `origin`, `sequence`, `outcome`, `repo`). I did not find a record of what callers have asked for and been unable to express. **To answer:** search the Eureka campaign's own findings and follow-ups for read-side requests, or ask the operator for the driving query list.
|
||||
|
||||
4. **What exactly does Cut 11 intend to own?** `query.rs:65-72` says Cut 11 owns "which fields of each kind are text" and the index. Whether Cut 11 also intends to own structured filter pushdown into Qdrant — which would make the structured-filter shape and Qdrant's filter shape the same question — is not written anywhere I found. The Huginn repo contains no `docs/` or `notes/` directory and no file mentioning "Cut 11" outside `query.rs`'s comments. **To answer:** locate the campaign's target document and cut map (the `PipelineCampaign.target_doc` DocRef would name it) or ask the operator.
|
||||
|
||||
5. **Is there a stored mind image to measure against?** Every portability claim about paging and counting assumes a document population size. I found no fixture corpus outside `fixtures.rs`. **To answer:** run the daemon against a real mind store and count documents per kind, or read `crates/huginn-mind/src/fixtures.rs` as the only current sample and treat it as synthetic.
|
||||
|
||||
6. **Does `epiphany-pipeline` want to grow a reference enumerator, and is that Epiphany's call or Huginn's?** C4 needs one; the leaf has none and references are split between typed `PipelineRef` and bare `Short`. Changing the leaf is a cross-repo change at a pinned rev (`5cda0886`). **To answer:** an operator ruling on which repo owns the hop table.
|
||||
|
||||
7. **Whether `unreql` or any other crate exposes the ReQL term-tree encoding *without* a client.** The survey found drivers, not an encoding library. **To answer:** read `unreql`'s crate structure for a types-only module, or accept that no such split exists.
|
||||
|
||||
8. **Does `rmp-serde` actually fail on the specific untagged shape we would write, or only on the nested-enum case in msgpack-rust #148?** The hazard is recorded (III-2, III-9) but not reproduced against our own types. **To answer:** a ten-line test — define a two-variant `#[serde(untagged)]` enum whose variants contain enums, round-trip it through `rmp_serde::to_vec_named`, and see. This is cheap and would settle whether untagged encoding is available to us at all.
|
||||
|
||||
9. **What does `qdrant-client` actually resolve to with `default-features = false` *inside our lockfile*?** 209/129 are the survey's numbers taken in isolation; the overlap with our existing 139 is unmeasured, and the marginal cost is what matters. **To answer:** add it to `huginn-daemon` on a scratch branch, `cargo generate-lockfile`, diff the `[[package]]` count, discard.
|
||||
|
||||
10. **Can the Qdrant filter shape be obtained as types without the client?** The survey establishes there is no published types-only crate and that `segment` (which has the serde+schemars derives) is workspace-internal. Whether generating Rust types from Qdrant's published OpenAPI, or from `qdrant_common.proto` via prost, produces something usable without tonic was not tested. **To answer:** run a schema-to-Rust generator over `openapi.json` and count what comes out.
|
||||
|
||||
11. **Is the polars `Expr` serialization instability a blocker in practice, and does `dsl-schema` actually emit a usable schema?** Both facts are from documentation and feature lists; neither was exercised. The 0.55.2 docs.rs build failure means the current API surface was not directly inspected. **To answer:** build `polars-plan` with `dsl-schema`, emit the schema, and check whether a serialized `Expr` survives a patch-version bump.
|
||||
|
||||
12. **Does Substrait's expression subset cover per-kind field predicates over our document types without a relational schema underneath it?** Substrait is relational algebra; our image is not tables. Whether `Expression` is usable detached from `Rel`/`ReadRel` was not established. **To answer:** attempt to construct a bare `Expression` tree with `substrait` 0.65.0 and see what it requires.
|
||||
|
||||
13. **How large does a serialized query actually get under each shape, through base64?** The daemon base64s its MessagePack payload (I-1), so encoding verbosity costs 4/3 on top. No candidate was measured. **To answer:** encode the same worked predicate — "open findings of High severity on this campaign" — in each candidate shape and compare byte counts.
|
||||
1246
docs/history/eureka-read-side-cut.md
Normal file
1246
docs/history/eureka-read-side-cut.md
Normal file
File diff suppressed because it is too large
Load diff
659
docs/history/huginn-organ-cut.md
Normal file
659
docs/history/huginn-organ-cut.md
Normal file
|
|
@ -0,0 +1,659 @@
|
|||
<!-- Provenance: copied verbatim from GameCult/Epiphany ef956865 notes/huginn-organ-cut.md; text below this line is unchanged. -->
|
||||
# Huginn wired in: cut map for Cuts 11-15
|
||||
|
||||
Date: 2026-09-29. Imagination output (Opus). Nothing here is committed, and no
|
||||
repository or live service was written to. Self places this map. Its natural
|
||||
home is a section of `F:\Projects\Epiphany\notes\eureka-pipeline-state-cut.md`
|
||||
that replaces the old Cuts 11-15 (lines 5490-5853). Mark those lines as history
|
||||
the same day, so that no reader sees two live designs.
|
||||
|
||||
**Q-H1 reversed, 2026-09-30 (operator).** Self laid out the five things 14-I still needs and recommended the
|
||||
interim unit. The operator: "So, what's blocking Huginn? I want us to be gathering evidence on Eureka ASAP" and then
|
||||
"Go for it." **Q-H1 is now A.** Cut 14 builds the interim unit as mapped, and its deletion line stays 14-I. Self
|
||||
deploys it under the operator's standing deployment authority, which replaces "the operator deploys" in Cut 14.
|
||||
Q-H2 has moved on: authority backups now run to Raven (weekly tier added 2026-09-30), so Cut 14's backup items apply
|
||||
under Q-H2 A. Cut 15 follows once 14 is live. The Q-H1 B text below is history.
|
||||
|
||||
**Cut 15 merged, 2026-09-30 (Self).** GameCult/Eureka `main` is at `bdb9dfd` (`051e1fc`, `2a0e9f7`, `272f5ca`,
|
||||
`f0ff484`, `bdb9dfd`).
|
||||
- Soul took three passes. The first found the Soul brief had stopped reading operator rulings (High). That is fixed:
|
||||
Soul claims against every cited ruling and every direction in force, independent of Hands' promises.
|
||||
- Live checks passed against the mind. `eureka-state` was redeployed on CultLib `8fc74c70`, and checks 3 and 6 now
|
||||
pass from Starfire.
|
||||
- The substrate gaps are tabulated in `references/campaign-state.md`, and each becomes a `follow_up` in the first
|
||||
campaign.
|
||||
- **Operator, on moving to typed state: "how are we going to move to Huginn mid-campaign? Methinks we don't."**
|
||||
Campaigns already in flight finish in prose. The first typed campaign starts fresh in a new session: Idunn as a
|
||||
watchdog that reports to the operator on Discord (store-recovery Q-R4).
|
||||
|
||||
**Cut 14 status, 2026-09-30 (Self).** Huginn is live on Yggdrasil: `huginn.service` on `10.77.0.1:17872/udp`,
|
||||
instance `eureka`, started 13:24 CEST.
|
||||
- The release is `0c4e410` (the `eureka/memory-organ` head), with binary sha256 `a02ae1f5...`.
|
||||
- gamecult-ops `a0320e4`/`14df7ce`, Huginn README `dcb85de`. The ufw rule admits Starfire only.
|
||||
- Checks 1, 2, 4, 5 (at the firewall counters) and 7 pass. Check 4 admitted the mind's identity document
|
||||
`eureka:instance:self` (receipt `mind-commit-379a2a34...`), and it survived a restart.
|
||||
- Check 8 waits for the 04:00 CEST backup on 1 October. The map's freeze-loop script no longer exists. The
|
||||
non-freezing state-backup producer covers `/var/lib/gamecult`, so no freeze loop was added.
|
||||
- **Check 3 fails, and Cut 15 is blocked on it.** `eureka-state` on Windows cannot reach the daemon. The
|
||||
cultnet-rs client binds `127.0.0.1` by default (`cultmesh.rs:256`), and on Windows a loopback-bound socket cannot
|
||||
send to `10.77.0.1` (os error 10051). This is CultLib's defect, and it is fixed there (`hands/rudp-client-bind`).
|
||||
After that come Huginn's pin bump, the Windows rebuild of `eureka-state`, and a rerun of checks 3 and 6.
|
||||
- Self default: the derived Qdrant collection `huginn_mind_eureka` joins the backup's `QDRANT_SKIP`, because the
|
||||
collection is disposable and the mind file is the truth.
|
||||
|
||||
**Operator rulings, 2026-09-29, each asked on its own:**
|
||||
|
||||
- **Q-H1: B, wait for Idunn.** Cut 14's interim systemd unit is **not**
|
||||
built. The organ deploys through Idunn after route continuity (Idunn
|
||||
`docs/route-continuity-cut.md`) lands. Cut 14 is remapped as 14-I once that
|
||||
campaign settles. A Rust runtime-presence implementation shared with Idunn,
|
||||
rather than a third copy, is its prerequisite, and where it lives is a fork
|
||||
to bring then. Cuts 11a, 11b, 13a and 13b proceed. They run against a
|
||||
hand-started daemon in tests only.
|
||||
- **Q-H2: C, no backup yet.** Nothing on Yggdrasil is backed up today (the
|
||||
authority backup unit was never installed). That is recorded as a
|
||||
standing risk, not solved here.
|
||||
- **Q-H4: one mind, instance `eureka`.** The operator asked first: "Wouldn't
|
||||
a single instance be bad for concurrency? ... can multiple Eureka instances
|
||||
share a Mind on Huginn?" Self answered from the Body. The daemon is the
|
||||
mind's only writer, and any number of sessions are its clients. Racing
|
||||
admissions get typed refusals. Focus comes from scoped queries, not from
|
||||
mind size. **Self's addition for Cut 13, from that exchange:** receipts
|
||||
name the instance but not the session that admitted a batch. With several
|
||||
sessions sharing one mind, the admitting session is recorded on the receipt
|
||||
(declared attribution, like the instance).
|
||||
- **Q-H5: park Cut 12**, tag and note, not deleted.
|
||||
- **Q-H6: A, the hybrid, with a standing direction.** The operator: "every bit
|
||||
of prose we're passing around should be viewed as a potential liability,
|
||||
*especially* if it's too big to just shove the whole thing into context. We
|
||||
should be looking for ways to turn what remains into typed state as well."
|
||||
Every Imagination pass looks for the next prose surface that could become
|
||||
typed (body facts, the model page, the subtraction ledger), and proposes it
|
||||
rather than adding prose.
|
||||
|
||||
**Objective (operator, 2026-09-29):** "Huginn wired in". Eureka's pipeline
|
||||
runs on Huginn's typed state. In scope: Cuts 11, 13, 14 and 15. Cut 12 is
|
||||
recommended for parking (see Q-H5). Out of scope: Cut 16 (the proof
|
||||
campaign), the Verse-presence cut (Q23 A), a public or stranger-ready skill,
|
||||
and the landing site.
|
||||
|
||||
**What lands ahead of these cuts** (mapped elsewhere, not re-mapped here):
|
||||
|
||||
- BP-1: CultLib content plane. It is in Hands on `cultmesh/rust-content-plane`, with three commits pushed.
|
||||
- BP-2: the pin move.
|
||||
- RS-3: `Mind::query(selection, semantic)`. It adds `cultnet-rs` to `huginn-mind`, and deletes `PipelineQuery` and `QUERY_LIMIT_MAX`.
|
||||
- BP-3: `HuginnMindResponse::Deferred`, `MAX_DEFERRED_BODY_BYTES`, and a deferral budget of 256 MiB.
|
||||
|
||||
Every Huginn anchor below is against `eureka/memory-organ` at `8c5f784`, which
|
||||
is clean. RS-3 and BP-3 move `query.rs`, `wire.rs` and `serve.rs`, so **each
|
||||
brief re-takes its anchors after they land.**
|
||||
|
||||
## Pins
|
||||
|
||||
| Repo | Branch | HEAD | Used for |
|
||||
|---|---|---|---|
|
||||
| Huginn | `eureka/memory-organ` | `8c5f784` | Cuts 11 and 13 |
|
||||
| Epiphany | `codex/eureka-pipeline-state` | `4f784d04` | the leaf's text fields (`epiphany-pipeline/src/lib.rs:510-578`), read only |
|
||||
| Eureka skill | `main` | `19487f0` | Cut 15 (`~/.claude/skills/eureka`) |
|
||||
| gamecult-ops | `main` | `8554e60` | Cut 14 |
|
||||
| Idunn | (current) | `5bbb461` | read only. The route-continuity target and cut map are the dependency for 14-I |
|
||||
|
||||
## Probe facts (run 2026-09-29, read-only except as noted)
|
||||
|
||||
- **P1. Qdrant on Yggdrasil** is `qdrant 1.17.1`, on `127.0.0.1:6333`.
|
||||
- It holds exactly two collections, both voidbot's:
|
||||
- `voidbot_repository_source_chunks`: 116,877 points;
|
||||
- `voidbot_discord_history_chunks`: 40,788 points.
|
||||
- Both are 1,024-dimensional, `Cosine`, `on_disk: true`, with keyword payload indexes (`sourceId`, `corpusKind`, `repoName`, `pathPrefixes`, …).
|
||||
- **`config.metadata` is null on both.** They were restored, not created by voidbot's code. So voidbot's embedder-compatibility check (`VoidBot/packages/rag/src/qdrant-vector-store.ts:458-477`) has nothing to compare against in production.
|
||||
- **P2. Raven's Ollama is live, and voidbot already uses it.**
|
||||
- `/srv/voidbot/retrieval/voidbot.env` reads `RAG_OLLAMA_BASE_URL=http://10.77.0.4:11434` and `RAG_OLLAMA_MODEL=qwen3-embedding:0.6b`. So the 2026-09-26 cutover has happened.
|
||||
- `gamecult-ops/inventory.md:669` and `runbooks/voidbot-retrieval-recovery-yggdrasil.md:40-62` still name Nightwing. They are stale.
|
||||
- Nightwing (`10.77.0.3:11434`) did not answer.
|
||||
- Epiphany's old Yggdrasil-local endpoint (`10.77.0.1:11435`) did not answer.
|
||||
- **P3. What Raven serves.** Ollama `0.21.1`. Tags:
|
||||
- `qwen3-embedding:0.6b`: digest `ac6da0dfba84…`, Q8_0;
|
||||
- `bge-m3:latest`.
|
||||
- One embed call per model, from Yggdrasil, returned **1,024 dimensions for both**. Each call took **5-6 s cold**. `/api/ps` reports a context of 4,096.
|
||||
- Side effect: this probe loaded `bge-m3` into Raven's VRAM until its 5-minute keep-alive expired.
|
||||
- **P4. How voidbot uses Qdrant**, which is the convention to reuse (`qdrant-vector-store.ts`):
|
||||
- The point id is the first 16 bytes of `sha256(chunk id)`, shaped into a v5-style UUID (`:264-280`). The real id travels in the payload.
|
||||
- Collections are created with `metadata {managedBy, corpusKind, embedderId, vectorLength, schemaVersion}` (`:203-216`).
|
||||
- **`embedderId` is `ollama:<baseUrl>:<model>`** (`ollama-embedder.ts:29-31`). Moving the host therefore reads as a model change, which is the hazard the 2026-09-26 memory warns about. Huginn keys compatibility on **model name plus digest**, not on the URL.
|
||||
- Query text is prefixed `Instruct: …\nQuery: …` when an instruction is configured (`:42-48`).
|
||||
- **P5. WireGuard exists.**
|
||||
- Starfire has the adapter `wg-gamecult-starfire` at `10.77.0.2`, and it is Up.
|
||||
- Yggdrasil has `wg-gamecult` at `10.77.0.1/24`, reachable from Starfire.
|
||||
- `10.77.0.1:17872/udp` is unbound. Ports in use today: 17871 (Odin, via nginx), 17873, 17876 and 17890 (Idunn's host-actuator hub, which is not in R21's list).
|
||||
- **ufw admits UDP on wg only per port:** 3075, 17871, 17876 (from `10.77.0.2` only) and 17890. **17872 needs a rule.**
|
||||
- Starfire's 17877 RUDP requester is not listening today. So a Rust RUDP client on Windows over WireGuard has a precedent, but it is not proven live right now.
|
||||
- **P6. `crates/eureka-state` is an empty stub:** `src/lib.rs` has 0 lines, and `Cargo.toml` has no dependencies.
|
||||
- **P7. The daemon serves one mind per process.**
|
||||
- `serve.rs:91-111` takes exactly `--state-root`, `--instance` and `--bind`.
|
||||
- `Mind::open` takes a lifetime-exclusive redb lock (`mind.rs:127-139`).
|
||||
- `IndexSink::committed` runs **synchronously inside `Daemon::handle`, before the reply** (`daemon.rs:74-83`).
|
||||
- `run` is a single-threaded poll loop (`serve.rs:257-297`). Any blocking call made there stalls every session.
|
||||
- **P8. The hand-off already works one side at a time.** Admission derives:
|
||||
- the source side's withdrawal when `from_instance == mind` (`admission.rs:331-346`);
|
||||
- the target side's stewardship when `to_instance == mind` (`:347-353`).
|
||||
So a hand-off is two ordinary admissions, one per mind.
|
||||
- **P9. Idunn v2's contract for a routed stateful target**, read from source:
|
||||
- The state slot kinds are only `cultcache-file` and `external-file` (`Idunn/src/deployment.rs:182-187`). **There is no `cultcache-directory`.**
|
||||
- A dependency on non-Idunn infrastructure is `kind = "external-operator-binding"`. It is satisfied by the binding's `external_capabilities` (`deployment.rs:1359-1383`, `deployment_plan.rs:146-249`). `shared-infrastructure` needs a managed provider with Odin correlation.
|
||||
- The route challenge for `rudp` is a CultNet `SnapshotRequest` for `gamecult.runtime_presence_health.v2`, keyed by the target (`drivers.rs:5524-5545`). The service must answer it with signed presence, signed with the Idunn activation credential. It must also adopt the process write lease.
|
||||
- Huginn implements none of this.
|
||||
- **No shared Rust implementation exists.** `cultnet-rs` has the contracts and signers (`runtime_authority_contracts.rs`, 3,309 lines). Each Rust service writes its own participant: Ghostlight's `idunn_health.rs` is 2,001 lines, and Odin has its own.
|
||||
- Unit names are `idunn-<target>-<sha256>` per incarnation.
|
||||
- There are **391 failed `idunn-*` transient units** on Yggdrasil today.
|
||||
- **P10. No backup runs on Yggdrasil.**
|
||||
- `gamecult-authority-backup.service` is not installed. The only backup timer is `dpkg-db-backup.timer`.
|
||||
- The script `scripts/backup-gamecult-authority-yggdrasil.sh:74-76` would refuse at its first step anyway: it freezes `epiphany.service` and `epiphany-swarm.service`, which are absent (`freeze_unit` returns 1 when a unit is inactive).
|
||||
- Its path list does not name any Idunn target's state root either, including `odin` and `streampixels-service`.
|
||||
- **P11. How Claude Code reaches MCP servers today** (`~/.claude.json`):
|
||||
- `voidbot` at `:17875` is the **Yggdrasil-hosted** MCP, reached through the SSH tunnel (`start-yggdrasil-tunnel.ps1:24`).
|
||||
- `aquasynth_dings` at `:17878` runs locally.
|
||||
- The target's "Shape decided" says `eureka-state` is a local **stdio** server. That governs here. The HTTP convention recorded in memory is not a ruling.
|
||||
|
||||
**Not probed:**
|
||||
|
||||
- whether `cultnet-rs`'s RUDP client works on Windows;
|
||||
- whether Claude Code accepts a tool schema that `$ref`s a remote `$id`;
|
||||
- whether Qdrant accepts `.` in a collection name;
|
||||
- whether `ureq` without TLS covers both adapters;
|
||||
- which connection id Idunn's RUDP challenge uses.
|
||||
|
||||
Each is assigned to a cut's first step below.
|
||||
|
||||
## Standing rulings, checked against the maps
|
||||
|
||||
| Ruling | Where it is recorded | Holds? |
|
||||
|---|---|---|
|
||||
| An instance owns its mind; a service owns the state | target 14 and 15; map rulings 14 and 15 | yes |
|
||||
| The organ depends on Qdrant directly, not on voidbot | target 16; map 16 | yes |
|
||||
| The organ shares voidbot's Qdrant, with the coupling declared | map 21 (Q9 A) | yes. "Declared" means an Idunn dependency, which the interim deploy (Q-H1 A) cannot express. The runbook states the coupling until 14-I. |
|
||||
| Identity is declared, not authenticated | map 18 (Q6 A) | yes |
|
||||
| The workstation reaches the organ over WireGuard | map 19 (Q7 A) | yes. P5 confirms the mesh; the ufw rule is Cut 14's. |
|
||||
| Eureka has no Persona | target 6; map 9 | yes |
|
||||
| Embeddings: Raven's Ollama (operator, 2026-09-26, for voidbot) | memory `nightwing-embedding-migration-2026-09-26.md` | **the target disagrees.** Target ruling 16 still says "Epiphany's own service embeds against a Yggdrasil-local endpoint … only voidbot uses Nightwing". Both endpoints are dead (P2). Self sweeps that sentence. Huginn uses the endpoint voidbot uses, with the same model. |
|
||||
| `open_items` and `history` are deleted, not kept as presets | read-side map (2026-09-17) | yes. Cut 13's old seven tools shrink to four. |
|
||||
| Q13 A: the client depends on `huginn-mind` for types only | map ~3931 | yes |
|
||||
| No committed mutation harness | operator, 2026-09-22 | yes. Old Cut 11 and 12's "Mutations:" lines are void. Soul runs `cargo-mutants` on each diff. |
|
||||
|
||||
## 0b. Model page
|
||||
|
||||
One row per persistent or long-lived kind these cuts touch. **A bold cell is empty or disputed.**
|
||||
|
||||
| Kind | What names it | What happens to it over time | Who decides |
|
||||
|---|---|---|---|
|
||||
| **Index point**: one per indexable document | A v5-shaped UUID from `sha256(document id)` (P4's function, reused). The payload holds `doc_id`, `kind`, `root`, `ordinal` and `text_sha256`. | Written after the document's batch commits, off the serve loop. Never updated, because documents are immutable. Never deleted, except when its collection is rebuilt. A missing point is recreated by reconciliation. | Only the daemon's index worker writes one. `huginn-mind::index_text` decides what the text is. Qdrant decides nothing. |
|
||||
| **Collection**, one per mind | `huginn_mind_<instance>`. Its metadata is `{managed_by: "huginn", instance, model, model_digest, dimensions, index_text_version}`. | Created on the first reconciliation. Rebuilt whole when its metadata disagrees with the running configuration. **A collection whose mind is retired has no owner.** With one instance this is a recorded follow-up, not a cut. | The daemon serving that instance. It never touches a collection whose `managed_by` or `instance` is not its own. |
|
||||
| **Index text version** | `huginn_mind::index::INDEX_TEXT_VERSION` | Bumped whenever `index_text` changes. A bump makes every collection disagree, so each is rebuilt. | `huginn-mind` |
|
||||
| **Embedding model binding** | Model name plus the digest from `/api/tags`. Never the base URL (P4). | Changes when the operator changes the configuration, or when Raven re-pulls the model and the digest changes. Either one rebuilds the collection. | The operator, through the unit's arguments. The daemon verifies the binding at each reconciliation. |
|
||||
| Index health (behind or unreachable) | none: in-memory in the worker | Lost on restart, and recomputed by reconciliation at startup. Never persisted, never written to the mind. | The worker |
|
||||
| Pending semantic reply | `(session, message_id)` in the serve loop | Lives until the worker answers or the session times out. Lost on restart, and the client asks again. | `serve` |
|
||||
| **Eureka instance** | A slug. **The operator names it (Q-H4).** | Created by the first admission of its `instance` document, which Self writes at the first campaign. Never renamed: a rename is a new mind. | The operator names it. Admission enforces it (A1, `require_instance`). |
|
||||
| MCP registration | `eureka-state` at user scope in `~/.claude.json`, with the environment variables `EUREKA_INSTANCE` and `HUGINN_ENDPOINT` | One stdio child per Claude Code session. It dies with the session and holds nothing. | The operator, because it is configuration |
|
||||
| Deferred body (BP-3), client side | The manifest's `contentHash` | Fetched and verified per call, then dropped. Never written by `eureka-state`. | `cultnet_rs::fetch_content` owns verification. |
|
||||
| **Deployed daemon identity** | Interim (Q-H1 A): the systemd unit `huginn.service` on Yggdrasil, with runtime id `huginn-<slug>` (`daemon.rs:54-56`). Later: `idunn-huginn-<sha>`. | Interim: systemd restarts the unit, which is continuity. **The operator installs a new binary, which is deployment, and no brake gates it (Q-H1).** | Interim: the operator. After 14-I: Idunn. |
|
||||
| Mind store | `/var/lib/gamecult/huginn/minds/<slug>/mind.redb` | Preserved forever. It is the only truth. **No backup owner runs (P10), so this is Q-H2.** | The daemon is the single writer, through the redb lock. |
|
||||
| Firewall admission | A ufw rule: `10.77.0.1 17872/udp on wg-gamecult from 10.77.0.2` | Installed once. It dies in 14-I if Idunn's route owns it. | Interim: the operator |
|
||||
| Hand-off / imported mind | The leaf `hand_off` key | Admitted separately into each mind (P8). Import has no consumer (Q-H5). | `huginn-mind` admission |
|
||||
|
||||
## Operator questions (one batch)
|
||||
|
||||
**Q-H1. How does Huginn get onto Yggdrasil this week?**
|
||||
|
||||
- **A. An interim operator-installed systemd unit, retired into Idunn by a named later cut (14-I).**
|
||||
- The operator builds the binary in the pinned Rust image, at an exact revision, on Yggdrasil.
|
||||
- It binds `10.77.0.1:17872` directly, with no nginx in the path.
|
||||
- The state root is chosen now so that 14-I is a unit swap and no data moves.
|
||||
- Costs:
|
||||
- it contradicts the old authority map's "no operator script deploys Huginn directly";
|
||||
- it has no deployment brake;
|
||||
- the Qdrant coupling is written in a runbook rather than declared to Idunn.
|
||||
- **B. Wait for Idunn.** Huginn is deployed only through Idunn, after two things land: route continuity's route-proof class (its Q1 b) and a shared Rust runtime participant in `cultnet-rs`. "Wired in" does not close this week.
|
||||
- **C. Idunn now, against today's contract.**
|
||||
- Huginn writes a third copy of the service-side presence, activation and lease code (Ghostlight's copy is 2,001 lines).
|
||||
- It also has to answer the route challenge and pass Odin-correlated readiness.
|
||||
- Its RUDP route goes through nginx UDP, whose reload loop is exactly what route continuity exists to fix. Every reload breaks RUDP sessions (route-continuity target).
|
||||
- All of this is reworked when that campaign lands.
|
||||
- **Recommended: A,** with 14-I named, dated and blocked on route continuity plus the shared participant.
|
||||
- Neither B nor C gives a working organ on a path that will survive.
|
||||
- C would build the thing route continuity is deleting.
|
||||
- A's authority gap is real. The operator, not a map, should accept it.
|
||||
- **Depends on it:** Cut 14's whole shape, whether this week closes, and whether 14-I exists.
|
||||
|
||||
**Q-H2. Who backs up the mind?** The target requires a named backup owner. None runs (P10).
|
||||
|
||||
- **A. Revive the authority backup.**
|
||||
- Edit the freeze list: drop the two absent Epiphany units and add `huginn.service`.
|
||||
- Add `var/lib/gamecult/huginn` to the path list.
|
||||
- Install the timer.
|
||||
- Side effect: daily backups resume for voidbot, Bifrost and Idunn configuration, including a daily pause of five containers.
|
||||
- **B. A Huginn-only timer:** freeze `huginn.service`, tar `minds/`, thaw. This makes a second backup mechanism.
|
||||
- **C. Ship with no backup,** and record it as a blocking follow-up.
|
||||
- **Recommended: A.** One owner of backup, and the script is already that owner. It is simply not running. **The operator must accept the side effects, and should know that Idunn targets' state (`odin`, `streampixels-service`) is in no backup either.**
|
||||
- **Depends on it:** Cut 14's gamecult-ops diff, and one verification step.
|
||||
|
||||
**Q-H4. What is the Eureka instance called, and how many are there?**
|
||||
|
||||
- **A. One instance for all Claude Code Eureka work,** for example `eureka`, with host `starfire`.
|
||||
- **B. One per workstation or per operator,** for example `metacrat.starfire`.
|
||||
- **Recommended: A.** Ruling 14 lets one instance steward several repos. Stewardship, not the instance, is what separates repos.
|
||||
- **Depends on it:** the unit's `--instance`, the state path, the collection name, and the MCP environment. It is cheap now and a new mind later.
|
||||
|
||||
**Q-H5. Park Cut 12?** (the consumer audit)
|
||||
|
||||
- Nothing that "wired in" needs calls a cross-mind hand-off or an import:
|
||||
- there is one instance (Q-H4);
|
||||
- no second mind exists;
|
||||
- Epiphany's adoption is the second campaign.
|
||||
- Old Cut 12's `hand_off(&mut Mind from, &mut Mind to)` **cannot be built as specified**:
|
||||
- each mind is locked by its own daemon process (P7);
|
||||
- "both minds or neither" would need a cross-process protocol.
|
||||
- The hand-off already works as two ordinary admissions, one per mind, each derived and idempotent (P8).
|
||||
- **Recommended: park it.** The map keeps a note: the hand-off is two admissions, sender first. Import waits for a consumer. No tag is needed, because no code exists.
|
||||
- **Depends on it:** whether Cut 12 is built at all.
|
||||
|
||||
**Q-H6. What does Eureka keep in typed state, and what stays in prose?**
|
||||
|
||||
- **A. A hybrid.**
|
||||
- Typed documents hold the questions, rulings, `cut_spec` (Hands' contract), `cut_report`, verdicts, findings, follow-ups and resolutions.
|
||||
- The status header becomes a query.
|
||||
- The committed map keeps only what has no kind: body facts, the model page, rationale, and the subtraction ledger narrative.
|
||||
- Per-cut specs live only as `cut_spec`, so no spec exists twice.
|
||||
- **B. Typed state for records only.** Specs stay in the prose map, and `cut_spec` goes unused.
|
||||
- **C. Retire the markdown map entirely** (old Cut 15).
|
||||
- **Recommended: A.**
|
||||
- B leaves the kind the leaf spent most of its bounds on without a consumer.
|
||||
- C discards body facts and the model page, which no kind carries. Step 0b is what stopped re-cutting.
|
||||
- **Depends on it:** Cut 15's text, and the output format of every future Imagination pass, this one included.
|
||||
|
||||
## Cut order
|
||||
|
||||
```
|
||||
[BP-1 → BP-2 → RS-3 → BP-3] (in flight, other maps)
|
||||
│
|
||||
BP-2 ──► 11a Huginn: the index write side (text, adapters, worker, reconcile)
|
||||
│ (after BP-2; may land before or after RS-3, serially on the branch)
|
||||
▼
|
||||
BP-3 ──► 11b Huginn: semantic read (rank the hits through the one evaluator; non-blocking)
|
||||
▼
|
||||
13a Huginn: envelope moves to huginn-mind; eureka-state client core
|
||||
▼
|
||||
13b Huginn: the MCP surface, Windows build and install
|
||||
│
|
||||
11a ──► 14 gamecult-ops: the interim unit, ufw, runbook, backup (Q-H1 A, Q-H2); the operator deploys
|
||||
▼
|
||||
15 Eureka skill: typed-state wiring (Q-H6); after 13b is registered and 14 is live
|
||||
---
|
||||
12 parked (Q-H5) 14-I Idunn path, blocked (see the end)
|
||||
```
|
||||
|
||||
## Cut 11a. The index write side
|
||||
|
||||
- **Repo/branch:** Huginn, `eureka/memory-organ`, after BP-2. The worktree is named by Self.
|
||||
- **First:** Hands checks, in `ureq` 3's own Cargo manifest, that `default-features = false` gives plain HTTP with no TLS. If it does not, Hands uses `reqwest` with `blocking` and no TLS, and reports which.
|
||||
- **Deletes first:**
|
||||
- `NoIndex` (`daemon.rs:20-27`) as the binary's index. It survives only in tests, or goes if the new worker's mock serves them.
|
||||
- The "No crate opens that connection yet" line in `README.md`.
|
||||
|
||||
**Adds, `huginn-mind/src/index.rs`.** This is pure: no network and no JSON.
|
||||
|
||||
- `pub const INDEX_TEXT_VERSION: u32`.
|
||||
- `pub struct IndexEntry { id: PipelineRef, text: String, text_sha256: String, ordinal: u64 }`.
|
||||
- `pub fn index_text(&PipelineDocument) -> Option<String>`. It reads the leaf's fields (`lib.rs:510-578`), joined by newlines and bounded at `INDEX_TEXT_MAX_BYTES` (16 KiB, cut at a char boundary):
|
||||
|
||||
| Kind | Fields |
|
||||
|---|---|
|
||||
| campaign | `title` |
|
||||
| target | `invariants[].statement`, `not_in_scope`, `canonical_implementations` |
|
||||
| question | `title`, `question`, `options[].text` |
|
||||
| ruling | `title`, `ruling`, `operator_quote` |
|
||||
| cut_spec | `title`, `first`, `adds`, `keeps_moves` |
|
||||
| cut_report | `commits[].subject`, `deviations` (`what`, `why`), `undone`, `promises[].text` |
|
||||
| verdict | `claims[].claim` |
|
||||
| finding | `claim`, `failure_scenario`, `evidence[].result` |
|
||||
| follow_up | `item`, `why_it_can_wait` |
|
||||
| resolution | `rationale`, **withdrawn ones included** (the Q17 B obligation) |
|
||||
| hand_off | `reason` |
|
||||
| instance, stewardship | none |
|
||||
|
||||
- `impl Mind { pub fn index_entries(&self, only: Option<&[PipelineRef]>) -> Vec<IndexEntry> }`. The ordinal comes from the writing receipt (RS-1).
|
||||
- Owner: `huginn-mind`, because "which fields are text" is a rule about the leaf's documents, and RS-3 names Cut 11 its owner.
|
||||
- Live consumer: the daemon's worker.
|
||||
- Protected invariant: the text is derived, never stored, and never includes receipts, provenance or status.
|
||||
- Why no existing owner serves: the leaf owns shape, not retrieval text, and the daemon owns no rule.
|
||||
|
||||
**Adds, `huginn-daemon/src/index/`.** Three files: `mod.rs`, `ollama.rs`, `qdrant.rs`. The daemon depends on `ureq` (see First) and `serde_json`, and `serde_json` is used only in the two adapter files: that is the xenos boundary.
|
||||
|
||||
- **Ports**, which are narrow traits so tests mock them:
|
||||
- `Embedder { model_identity() -> ModelIdentity{name, digest, dimensions}; embed(&[String]) -> Vec<Vec<f32>> }`;
|
||||
- `VectorIndex { describe(name) -> Option<CollectionMeta>; recreate(name, meta); ids(name) -> BTreeSet<String>; upsert(name, points) }`.
|
||||
|
||||
`search` is 11b's.
|
||||
- **The worker** is one thread that owns both ports and receives jobs over a channel.
|
||||
- **`IndexSink::committed` builds the entries** (local and cheap) **and only enqueues them.** It never blocks and never waits on the network. This is the fix for P7.
|
||||
- **At startup, one reconciliation:**
|
||||
1. read the model identity;
|
||||
2. `describe` the collection;
|
||||
3. if it is absent, or its metadata disagrees, `recreate` it;
|
||||
4. if the metadata names another `managed_by` or `instance`, refuse and log, and never recreate;
|
||||
5. take `ids`, subtract, then embed and upsert what is missing, in batches of 32, with `wait=true`.
|
||||
- **On failure:** log, keep the failed entries in memory, and retry with a backoff that starts at 30 s and is capped at 10 min.
|
||||
- **Options:** `--qdrant-url`, `--ollama-url` and `--embedding-model` join `parse_options` (`serve.rs:96`). **All three are required**, so there is no index-less mode. An unreachable index is a degraded projection, not a configuration.
|
||||
- **The query instruction constant** for qwen3 lives beside the Ollama adapter. 11b uses it.
|
||||
|
||||
**Authority map.**
|
||||
|
||||
- Owner: the typed mind owns truth, and the worker owns the projection.
|
||||
- Inputs: `IndexEntry`s built by `huginn-mind`, and the configuration.
|
||||
- Outputs: Qdrant points and collection metadata.
|
||||
- Derived state: the whole collection and the worker's in-memory health.
|
||||
- Forbidden writers: nothing else writes `huginn_mind_*`; voidbot never does; admission never waits on the index; the mind never stores index state; there is no `pending_index` document (old Cut 11 is superseded).
|
||||
- Shared paths: `IndexSink::committed` (live writes) and startup reconciliation share one embed-and-upsert routine.
|
||||
- Deletion line: `NoIndex` leaves the binary.
|
||||
|
||||
**Verification** (Yggdrasil, stopgap, Rust image): `ygg-verify.sh F:/Projects/Huginn <sha> rust 'cargo test --locked --workspace'`, then `cargo mutants --in-diff` on the cut's range.
|
||||
|
||||
| Test (behaviour) | Rule |
|
||||
|---|---|
|
||||
| `an_admission_replies_while_the_embedder_is_blocked` (the mock embedder waits on a barrier; `handle` returns `Committed` before the barrier is released) | The index never delays or refuses admission |
|
||||
| `reconcile_indexes_exactly_the_indexable_documents_the_collection_lacks` | The projection converges by set difference |
|
||||
| `a_model_digest_change_rebuilds_the_collection` (same name, different digest) | Compatibility is keyed on model and digest, **never on the URL**. At least one fixture pair must differ only in URL and expect **no** rebuild. |
|
||||
| `a_collection_owned_by_another_instance_is_never_recreated` | No cross-mind writes |
|
||||
| `index_text_ignores_admission_facts_and_indexes_withdrawn_resolutions` | Text is derived from the document alone, and Q17's obligation holds |
|
||||
| `point_ids_are_a_pure_function_of_the_document_id` (a fixed vector, computed by P4's algorithm) | Stable ids across restarts, with voidbot's convention |
|
||||
|
||||
- **Negative greps:**
|
||||
- `rg -n "ureq|reqwest|serde_json|qdrant|ollama" crates/huginn-mind/src` is empty;
|
||||
- `rg -n "thread::spawn" crates/huginn-daemon/src` hits only `index/`.
|
||||
- **Live adapter check (operator-approved, because it writes to the shared Qdrant):** a scratch instance `verify.<rand>` on Yggdrasil admits two documents; its collection appears with P1-style parameters and the metadata above; then the collection is deleted by hand.
|
||||
|
||||
**Estimate:** about 650 lines added, most of them tests. +`ureq`, +`serde_json` (daemon only). −`NoIndex` from the binary.
|
||||
|
||||
**Build budget:** the Huginn workspace, on Yggdrasil only, with one release build for the live check.
|
||||
|
||||
**Hands context:** about 150k tokens: `daemon.rs`, `serve.rs` and `mind.rs` headers plus the new code.
|
||||
|
||||
## Cut 11b. Semantic read
|
||||
|
||||
**Added 2026-09-29 (Self), from Soul's pass on 11a:**
|
||||
- **The mind judges every hit.** The index never deletes points. Withdrawn and
|
||||
superseded documents are indexed on purpose, and point payloads carry no status. So
|
||||
Qdrant supplies candidate ids and scores, and every hit is joined back through the
|
||||
mind: ids the mind doesn't hold are dropped (a restored mind, or a second store), and
|
||||
the rest are filtered by RS-3's in-force derivation, through selection. A hit is
|
||||
never shown on the index's word alone.
|
||||
- **The effective embedding bound is the model's context, not 16 KiB.**
|
||||
`qwen3-embedding:0.6b` takes 4096 tokens and Ollama truncates beyond that silently.
|
||||
Long documents rank on their first ~4k tokens.
|
||||
- **Model identity is re-checked on each flush.** A model re-pulled with the same
|
||||
dimensions and a new digest must not mix vectors until a restart. An idle mind whose
|
||||
collection is lost also stays `Current` until the next upsert fails. Found by Soul on
|
||||
11a's closing batch; 11b owns both.
|
||||
- **11a merged** into `eureka/memory-organ`. It was: Soul closed it on conditions. A fix batch is in Hands: pin the
|
||||
backoff, reconcile again after any failure, label the collection with the mind's
|
||||
identity, and merge BP-3.
|
||||
|
||||
- **Repo/branch:** Huginn, after RS-3 and BP-3. Anchors are re-taken on their tip.
|
||||
- **Deletes first:**
|
||||
- the refusal "semantic query: the index is not wired (Cut 11)", at both sites (`query.rs:296-299`, and the daemon test at `daemon.rs:534-541`, re-taken);
|
||||
- the test `semantic_query_refuses_typed_until_wired`. Its rule dies; the replacements are below.
|
||||
|
||||
**Adds.**
|
||||
|
||||
- **`huginn-mind`:** `Mind::rank(&self, selection: &Selection, hits: &[(PipelineRef, f32)]) -> Result<PipelineSelectionPage, MindRefusal>`. Its rules:
|
||||
1. A selection with a `cursor`, together with `semantic`, is refused `SelectionInvalid` (`field: "cursor"`), because semantic answers are not pageable.
|
||||
2. The rows are restricted to hit ids present in the image as of `head`. A hit with no document is dropped and logged by the caller, never fabricated.
|
||||
3. RS-3's own path runs: `cultnet_rs::validate`, the value door, then `select` over the restricted rows. **The one evaluator decides membership. Qdrant never filters.**
|
||||
4. Order is score descending, with ties broken by ordinal.
|
||||
5. Truncate to `limit`.
|
||||
6. `next: None`.
|
||||
- **`huginn-daemon`:**
|
||||
- A `Query` with `semantic` is not answered inline. `serve` stores `(session, message_id, request)` and sends a `Search { text, top_k }` job to the worker.
|
||||
- The worker embeds with the query instruction and searches the collection, with no filter and an oversample of `min(top_k × 4, 200)`.
|
||||
- The loop drains worker replies on each poll, calls `rank`, and replies on the stored session.
|
||||
- When the worker is unhealthy, or the call fails: `Refused(Unavailable { detail })`, and the detail names the endpoint.
|
||||
- **`VectorIndex` gains `search`.**
|
||||
|
||||
**Authority map:** the owner of membership is `rank`, over RS-3's evaluator. Qdrant supplies only a candidate set and scores. Forbidden: lowering a `FieldPredicate` into a Qdrant filter, because that would be a second evaluator; RS-3 §8 had left this to Cut 11, and this cut refuses it.
|
||||
|
||||
**Verification:**
|
||||
|
||||
| Test | Rule |
|
||||
|---|---|
|
||||
| `semantic_hits_pass_through_the_selections_own_predicates` (a hit of an excluded kind is absent) | One evaluator |
|
||||
| `semantic_results_are_ordered_by_score_not_ordinal` | Ranking |
|
||||
| `a_hit_without_a_document_is_dropped` | Qdrant is not truth |
|
||||
| `an_unreachable_embedder_is_refused_unavailable_never_an_empty_page` | Honest availability |
|
||||
| `another_session_is_answered_while_a_semantic_search_is_outstanding` (blocked mock embedder) | The loop never waits on the network |
|
||||
| `cursor_with_semantic_is_refused` | Not pageable |
|
||||
|
||||
- **Live (operator-approved):** on the scratch instance, a paraphrase query returns a document that shares no exact words with it, and the hit resolves through the same page type.
|
||||
|
||||
**Estimate:** about 350 lines added. No new dependencies.
|
||||
|
||||
**Hands context:** about 150k tokens.
|
||||
|
||||
## Cut 13a. The envelope moves to its owner; the client core
|
||||
|
||||
- **Repo/branch:** Huginn, after 11b.
|
||||
- **Moves (ownership only; one commit that builds on its own):**
|
||||
- `huginn-daemon/src/envelope.rs` (223 lines) moves to `huginn-mind/src/envelope.rs`: `encode_request`, `decode_request`, the response pair, `OperationFailure` and `FAILURE_SCHEMA`.
|
||||
- `huginn-mind` gains `base64`. It already has `cultnet-rs` through RS-3.
|
||||
- The daemon imports the module from `huginn-mind`.
|
||||
- Rule: **one spelling of the operation envelope.** The client and the daemon cannot drift apart.
|
||||
- Verified by the daemon's existing envelope tests, unchanged, run from their new home.
|
||||
- **Adds, `crates/eureka-state` (library part).**
|
||||
- `Cargo.toml`: `huginn-mind` (types, envelope), `cultnet-rs`, `anyhow`, `serde`, `serde_json`.
|
||||
- `HuginnClient { endpoint: SocketAddr, instance: Slug, timeout }`:
|
||||
- its one method is `call(HuginnMindRequest) -> Result<HuginnMindResponse, ClientError>`;
|
||||
- the transport is `CultNetRudpSocketTransportConnection` on `CULTNET_OPERATION_CONNECTION_ID`, channel `schema`, as the daemon expects (`serve.rs:283`);
|
||||
- it resolves `Deferred` through `cultnet_rs::fetch_content`, capped at BP-3's `MAX_DEFERRED_BODY_BYTES` (imported, not copied);
|
||||
- `ClientError::Unavailable { endpoint, detail }` is the only transport error.
|
||||
- Owner: none over state; the client is a transport shim.
|
||||
- Forbidden: opening a store, calling `Mind::open`, `admit` or `admit_prepared`, deriving status, or writing any file.
|
||||
|
||||
**Verification** (Yggdrasil):
|
||||
|
||||
| Test | Rule |
|
||||
|---|---|
|
||||
| `every_operation_round_trips_against_a_live_daemon` (an in-process daemon on `127.0.0.1:0` with a temp mind) | Pipeline smoke |
|
||||
| `a_closed_port_is_unavailable_naming_the_endpoint_within_the_timeout` | Loud unavailability. The timeout is a function of the configured value, so probe two values. |
|
||||
| `a_deferred_answer_resolves_to_the_same_page_as_a_direct_one` | BP-3's client side |
|
||||
| `a_foreign_instance_is_an_answer_not_an_error` | Refusals are data |
|
||||
|
||||
- **Negative grep:** `rg -n "Mind::open|\.admit\(|admit_prepared|OwnedRedb|std::fs::(write|create)" crates/eureka-state/src` is empty.
|
||||
|
||||
**Estimate:** about 300 lines added. The move is net 0.
|
||||
|
||||
**Hands context:** about 120k tokens.
|
||||
|
||||
## Cut 13b. The MCP surface, the build and the install
|
||||
|
||||
- **Landed before it (2026-09-30, Self):** Cut 11b and its follow-ups merged to
|
||||
`eureka/memory-organ` at `e3e0c38` and `ff39d80`. Cut 13a merged at `33641f4`
|
||||
(Soul: no blocker; fixes at `3dc50ee`). 13a's client now has **two** errors:
|
||||
`ClientError::Unavailable` (transport, timeout, out-of-protocol answer) and
|
||||
`ClientError::Rejected { endpoint, code, detail }` (an envelope-level
|
||||
`OperationFailure`). Its `timeout` bounds the whole call; answers are checked
|
||||
against the request's id and operation; every call disconnects its session.
|
||||
13b maps `Rejected` like `Unavailable`: a result with `isError: true` and the
|
||||
typed body, so an MCP caller can tell a permanent mismatch from a down daemon.
|
||||
- **Built (2026-09-30):** `hands/cut13b` at `120e0a6`, green on Yggdrasil; Soul pass running. Windows
|
||||
binary `C:\Users\Meta\.eureka\bin\eureka-state.exe` (from `63ac503`). **Registered by the operator**
|
||||
(user scope), 2026-09-30. In Windows PowerShell the documented form
|
||||
`claude mcp add ... -e K=V -- <exe>` fails with "missing required argument 'commandOrUrl'": the
|
||||
variadic `-e` swallows the name and path. The form that worked puts positionals first:
|
||||
`claude mcp add eureka-state <exe> --scope user -e EUREKA_INSTANCE=eureka -e HUGINN_ENDPOINT=rudp://10.77.0.1:17872`.
|
||||
`whoami` answers `reachable: false` until Cut 14.
|
||||
- **Repo/branch:** Huginn, after 13a.
|
||||
- **First:**
|
||||
1. Hands builds a 20-line scratch rmcp server whose tool schema carries a `$defs`-internal `$ref`, and confirms `claude mcp` lists and calls it. It is not committed.
|
||||
2. Hands confirms the `claude mcp add` syntax from `claude mcp add --help`.
|
||||
- **Adds.**
|
||||
- The binary `eureka-state` (`src/main.rs`) uses `rmcp = "2.2.0"`, the version Epiphany's lockfile already resolves, with `server`, `macros` and `transport-io`, plus `tokio` (`rt`, `macros`, `io-std`) and `schemars`.
|
||||
- **Configuration comes from the environment:** `EUREKA_INSTANCE` and `HUGINN_ENDPOINT` (`rudp://10.77.0.1:17872`). Missing or invalid configuration does not stop startup. `whoami` reports it, and every other tool refuses it, typed. An MCP server that dies at startup vanishes silently from Claude Code.
|
||||
- **Four tools, 1:1 with the wire.** The old `open_items`, `rulings_in_force`, `stewardship` and `history` tools are deleted designs. Their recipes move to the skill (Cut 15).
|
||||
|
||||
| Tool | Input | Output |
|
||||
|---|---|---|
|
||||
| `whoami` | `{}` | `{ instance, endpoint, reachable, status: Option<MindStatus> }` |
|
||||
| `admit` | `{ faculty, agent, session, documents }` | `PipelineAdmissionOutcome`. `instance` and `provenance.tool = "eureka-state"` are filled in by the server, **never taken from input**. |
|
||||
| `view` | `{ id }` | `Option<PipelineDocumentView>` |
|
||||
| `query` | `{ selection, semantic? }` | `PipelineSelectionPage` |
|
||||
|
||||
- Refusals are successful tool results carrying the typed refusal. `Unavailable` is a result with `isError: true` and the typed body. Malformed input is `invalid_params`.
|
||||
- **Input schemas are self-contained:** schemars output with the CultLib selection schema bundled into `$defs`, and no remote `$id` reference.
|
||||
|
||||
**Verification.**
|
||||
|
||||
- On Yggdrasil, a JSON-RPC exchange piped into the Linux build: `initialize`, `notifications/initialized`, then `tools/list`, which lists 4 tools and contains no `$ref` outside `#/$defs`. Then `whoami` against a closed port, which gives `reachable: false` and no JSON-RPC error.
|
||||
- Test `admit_ignores_any_instance_in_input`. The rule: declared identity is configuration, not a per-call argument.
|
||||
- **Windows (Starfire; Windows-only work, one job, nothing else running):** `cargo install --locked --path crates/eureka-state --root C:\Users\Meta\.eureka`. Hands reports the binary path and the build log's target directory.
|
||||
- **Registration is the operator's:** `claude mcp add --scope user eureka-state -e EUREKA_INSTANCE=<Q-H4> -e HUGINN_ENDPOINT=rudp://10.77.0.1:17872 -- C:\Users\Meta\.eureka\bin\eureka-state.exe`, in the syntax confirmed by First.
|
||||
- **Operator only, after Cut 14:** `whoami` from a Claude Code session answers `reachable: true`. This is the only proof of the Windows RUDP path over WireGuard.
|
||||
|
||||
**Estimate:** about 400 lines added. +1 binary, +`rmcp`, +`tokio`.
|
||||
|
||||
**Hands context:** about 180k tokens. rmcp's API is the unknown.
|
||||
|
||||
## Cut 14. Interim deployment (under Q-H1 A and Q-H2 A)
|
||||
|
||||
- **Repos:** gamecult-ops `main`, plus one line in Huginn's `README.md` for the run command. Depends on 11a, because the options exist from then.
|
||||
- **Deletes first:** from `scripts/backup-gamecult-authority-yggdrasil.sh:74-76`, the absent `epiphany-swarm.service` and `epiphany.service` in the freeze loop (under Q-H2 A).
|
||||
|
||||
**Adds, gamecult-ops.**
|
||||
|
||||
- **`systemd/huginn.service`:**
|
||||
- `User=huginn`, with a system user created by the runbook;
|
||||
- `ExecStart=/opt/gamecult/huginn/current/huginn-daemon --state-root /var/lib/gamecult/huginn --instance <Q-H4> --bind 10.77.0.1:17872 --qdrant-url http://127.0.0.1:6333 --ollama-url http://10.77.0.4:11434 --embedding-model qwen3-embedding:0.6b`;
|
||||
- `Restart=on-failure`;
|
||||
- `After=`/`Requires=wg-quick@wg-gamecult.service`, because the bind needs the interface;
|
||||
- **no ordering on Qdrant or Raven**, because the index is a projection;
|
||||
- `StateDirectory`/`ReadWritePaths` limited to `/var/lib/gamecult/huginn`;
|
||||
- `ProtectSystem=strict`;
|
||||
- `MemoryMax=768M`, which covers BP-3's 256 MiB deferral budget plus the mind.
|
||||
|
||||
Hands confirms the WireGuard unit's real name with `systemctl list-units` before writing `Requires=`.
|
||||
- **`runbooks/huginn-yggdrasil.md`:**
|
||||
- Authority: the mind file is truth; the collection is disposable; Qdrant is shared with voidbot, and **this is the undeclared coupling ruling 21 wanted declared**; the embedder is Raven.
|
||||
- Release body: build in `rust@sha256:4c2fd73e…` (the Odin binding's pinned image) at an exact revision, with `cargo build --locked --release -p huginn-daemon`. Install to `/opt/gamecult/huginn/releases/<sha>/`, then flip `current`.
|
||||
- Install: user, directories, unit, and the ufw rule `ufw allow in on wg-gamecult from 10.77.0.2 to 10.77.0.1 port 17872 proto udp comment 'Huginn mind RUDP from Starfire'`.
|
||||
- Verification.
|
||||
- Recovery: stop the unit, delete `huginn_mind_<slug>` to force a rebuild, restore `minds/` from backup.
|
||||
- **Retirement:** this unit, the ufw rule and the release section die in 14-I.
|
||||
- **`inventory.md`:** allocate 17872 to Huginn, and add a Huginn line to the Yggdrasil services list. The stale Nightwing embedding text (P2) belongs to the embedding migration's owner. Hands does not rewrite it. Hands reports it.
|
||||
- **Backup (under Q-H2 A):**
|
||||
- add `var/lib/gamecult/huginn` to the tar list;
|
||||
- add `huginn.service` to the freeze loop;
|
||||
- install and enable `gamecult-authority-backup.timer`, which is operator-run.
|
||||
|
||||
**Authority map (interim).**
|
||||
|
||||
- Owner: the operator owns deployment and continuity until 14-I. Huginn owns its state.
|
||||
- Forbidden writers: nothing but the daemon writes `/var/lib/gamecult/huginn`. Nothing but the runbook's release step writes `/opt/gamecult/huginn`.
|
||||
- Deletion line: 14-I.
|
||||
|
||||
**Verification (operator only; Hands does not deploy):**
|
||||
|
||||
1. The unit reaches `active`.
|
||||
2. The daemon's stderr says it is serving `10.77.0.1:17872`.
|
||||
3. From Starfire, `eureka-state` `whoami` answers `reachable: true`.
|
||||
4. **Restart preserves state:** admit, run `systemctl restart huginn`, then `view` the document.
|
||||
5. **Negative:** a `whoami` from Raven (`10.77.0.4`) times out, because ufw admits Starfire only.
|
||||
6. **Negative:** stop the unit. The next `admit` is `Unavailable` naming the endpoint, and no file appears on Starfire.
|
||||
7. The collection `huginn_mind_<slug>` exists, with its metadata.
|
||||
8. **Backup:** run the backup service once. `tar --list` shows `var/lib/gamecult/huginn/minds/<slug>/mind.redb`.
|
||||
|
||||
**Estimate:** about 150 lines of ops configuration and prose. +1 unit, +1 ufw rule, +1 port, +1 backup path, and backups resume.
|
||||
|
||||
## Cut 15. Skill wiring (under Q-H6 A)
|
||||
|
||||
- **Repo:** `GameCult/Eureka`, `main` at `19487f0` (`~/.claude/skills/eureka`). Depends on 13b being registered and 14 being live.
|
||||
- **The old Cut 15's anchors are stale:** `SKILL.md` is now 533 lines, and "Mind Steward" is now Life. Hands re-takes them. The sections to change:
|
||||
|
||||
| Where | Change |
|
||||
|---|---|
|
||||
| `SKILL.md:8-19` (the substrate paragraph) | Eureka keeps questions, rulings, cut specs, reports, verdicts, findings, follow-ups and resolutions as typed documents in the instance's mind (Huginn), through the `eureka-state` tools. The committed target and map keep what has no kind. Delete "committed docs … memory files" as the store. |
|
||||
| §0 (`:58-72`) | Self calls `whoami` first. If the organ is unreachable, stop and say so. Then Self admits `stewardship` (if absent), `campaign` and `target`. |
|
||||
| §0b and §1 (`:74-165`) | Imagination admits `question`s and `cut_spec`s. The map document keeps body facts, the model page and rationale, and **has no per-cut spec sections**. "Commit the map" applies to that prose only. |
|
||||
| §2 (`:167-189`) | A ruling is admitted with `answers`, `choice` and `operator_quote`. Supersession is a `resolution`, never an edit. |
|
||||
| §3 (`:190-300`) | The Hands brief names the `cut_spec` id and the rulings in force. Hands admits a `cut_report`. `cargo-mutants` survivor triage goes into `cut_report.deviations`, or into a follow-up. |
|
||||
| §4 (`:304-335`) | Soul admits one `verdict` plus its `finding`s. Admitting is a Mind write, not a Body edit, so Soul stays read-only on repos. |
|
||||
| §5 and §6 (`:337-399`) | Triage outcomes are `resolution`s and `follow_up`s. The status header becomes a set of selection recipes. Self reconciles the ledger from `cut_report.structural_delta`. |
|
||||
| Self's discipline (`:401-451`) | Never restate typed state in prose. When the organ is unreachable, stop; never keep a second copy (the target's invariant). **Keep "Record the substrate you needed and did not have".** |
|
||||
| `references/briefs.md` | Each faculty's "read first" becomes selection recipes. Hands gets `view <cut_spec id>`. A new **Rehydrate** section: `whoami`, then the recipes. |
|
||||
| `references/cut-map.md` | Rewrite it as "Campaign state": the document set, the keys, the resolution matrix, and **selection recipes** in RS-3's vocabulary (below). The prose-map template shrinks to body facts, model page and rationale. |
|
||||
| `references/changelog.md` | A dated entry with this map's evidence. |
|
||||
|
||||
**Selection recipes** to write. These replace the deleted presets, spelled in RS-3's aliases:
|
||||
|
||||
- **rulings in force:** `schemas: [epiphany.pipeline.ruling.v2]`, `in_force = true`, `root = <campaign>`;
|
||||
- **open items:** questions, findings and follow-ups with `in_force = true`;
|
||||
- **specs with no report:** `cut_spec`s, with the hop `cut_spec` inbound and absent (Hands must confirm that the substrate can express absence; if it cannot, this recipe is two queries and says so);
|
||||
- **a subject's history:** `resolution`s with `subject` = X, `descending`.
|
||||
|
||||
**Verification (Soul, by reading plus one live exercise).**
|
||||
|
||||
- `rg -n "status header|WriterLeaseHeld|lease|open_items|rulings_in_force|Mind Steward" ~/.claude/skills/eureka` hits only changelog and history lines.
|
||||
- Every leaf kind is named by at least one brief.
|
||||
- No brief asks for prose relay of a typed artifact.
|
||||
- **Live:** a fresh agent given only the skill and the instance slug runs `whoami` and the rulings-in-force recipe against the live organ, and both return typed answers.
|
||||
|
||||
**Estimate:** prose shrinks. `cut-map.md` is replaced, not extended.
|
||||
|
||||
## Cut 12. Parked (Q-H5)
|
||||
|
||||
Recorded, not built:
|
||||
|
||||
- A hand-off is two admissions, one per mind, sender first. Each side's derivation already exists (P8).
|
||||
- Import waits for a consumer, most likely Epiphany's adoption campaign.
|
||||
- Old Cut 12's in-process `hand_off(&mut from, &mut to)` is infeasible under one daemon per mind (P7).
|
||||
|
||||
## 14-I. Idunn path (blocked; not this week)
|
||||
|
||||
Named so that the interim unit has a deletion line. Its dependencies:
|
||||
|
||||
1. **Route continuity** (`Idunn/docs/route-continuity-target.md`, invariants 1, 2 and 5, and its Q1 b):
|
||||
- Huginn declares no `odin.verse-rendezvous` dependency, so it is a route-proof target.
|
||||
- No nginx reload on observation, because every reload breaks RUDP sessions.
|
||||
- Stateful deadlines resolve (its Q3).
|
||||
2. **A shared Rust runtime participant** in `cultnet-rs`: presence signing, activation credential, lease adoption and the challenge answer. Otherwise Huginn writes a third copy (P9). The owner is CultLib.
|
||||
3. **Idunn has no path from a selected dependency's endpoint to a service argument.** Qdrant and Ollama will be `external-operator-binding` dependencies whose endpoints are duplicated in `argument_bindings`, as Ghostlight duplicates Odin's. Route this to Idunn.
|
||||
4. **The slot for `minds/<slug>/mind.redb` is `external-file`**, because redb is not Idunn's single-file CultCache format. It is one slot per instance, so the recipe names the instance.
|
||||
5. Whether the huginn-daemon hub (`serve.rs:138`, which drops other connection ids) receives Idunn's RUDP challenge. Not probed.
|
||||
|
||||
The later Verse-presence cut (Q23 A) comes after 14-I.
|
||||
|
||||
## Subtraction ledger (estimates; Self reconciles at each landing)
|
||||
|
||||
| Cut | Removed | Added | Dependencies, targets, formats |
|
||||
|---|---|---|---|
|
||||
| 11a | `NoIndex` from the binary | ~650 | +`ureq`, +`serde_json` (daemon); +1 collection per mind |
|
||||
| 11b | the unwired refusal and its test | ~350 | none |
|
||||
| 13a | 223 (moved) | ~300 | +`base64` in `huginn-mind` (moved) |
|
||||
| 13b | the old design's 3 preset tools | ~400 | +1 binary; +`rmcp`, +`tokio` |
|
||||
| 12 | the whole cut (parked) | 0 | none |
|
||||
| 14 | 2 dead freeze entries | ~150 ops | +1 unit, +1 ufw rule, +1 port; backups resume |
|
||||
| 15 | per-cut prose sections, status headers | prose shrinks | `cut-map.md` replaced |
|
||||
|
||||
## What in the old specs (cut map lines 5490-5866) is now wrong
|
||||
|
||||
1. **Cut 11's `pending_index` document.** A mind admits exactly fifteen types (`mind.rs:84-88`), and this would be derived state in the truth store. It is replaced by reconciliation by set difference.
|
||||
2. **Cut 11's "indexing at admission, in the same call".** It blocks the single-threaded serve loop (P7). A cold embed takes 5-6 s (P3).
|
||||
3. **Cut 11's flags "following `epiphany.service`'s spelling (R16)" and its `10.77.0.1:11435` precedent.** Both endpoints are dead (P2).
|
||||
4. **The compatibility record ported from voidbot.** Its `embedderId` includes the host URL (P4). Compatibility keys on model and digest instead.
|
||||
5. **One shared collection with an `instance` payload.** It becomes one collection per mind.
|
||||
6. **Cut 12's two-mind in-process hand-off.** It is infeasible (P7), and the hand-off already works per mind (P8).
|
||||
7. **Cut 13's seven tools.** They become four, after the operator deleted `open_items` and `history` as presets.
|
||||
8. **Cut 13's "tool schemas equal the published schemas".** The published request schema will `$ref` CultLib's `$id` (RS-3 F9). MCP tool schemas must be self-contained.
|
||||
9. **Cut 13 is missing Deferred handling** (BP-3 §7).
|
||||
10. **Cut 14's `kind = cultcache-directory`.** No such kind exists (P9).
|
||||
11. **Cut 14's "`shared-infrastructure` dependency on Qdrant, Ghostlight's shape (`:168-187`)".** Ghostlight declares only Odin, and non-Idunn infrastructure is `external-operator-binding` (P9).
|
||||
12. **Cut 14's `idunn-huginn` unit name** in the backup freeze loop. Units are named per incarnation, `idunn-<target>-<sha>` (P9).
|
||||
13. **Cut 14's "Named owner: `gamecult-authority-backup.service`, daily at 03:20 UTC".** It is not installed, and it would refuse to run (P10).
|
||||
14. **Cut 14's route through nginx `rudp://10.77.0.1:17872` with presence health.** Huginn has no runtime participant, and route continuity is rewriting that path (P9).
|
||||
15. **Cut 15's anchors, and its "Mind Steward" name.** Its `open_items` plus `query` status header and its rehydration sequence both use deleted tools.
|
||||
16. **Q14 C** (move the mutation harness into the skill repo). It is moot: the harnesses were retired.
|
||||
226
docs/history/postmortem-eureka-schema-phase.md
Normal file
226
docs/history/postmortem-eureka-schema-phase.md
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
<!-- Provenance: copied verbatim from GameCult/Epiphany ef956865 notes/postmortem-eureka-schema-phase.md; text below this line is unchanged. -->
|
||||
# Eureka pipeline state, schema-ownership phase: postmortem
|
||||
|
||||
Date: 2026-09-16. Written at the phase boundary, not at the end of the
|
||||
campaign. Cuts 1-6d closed Epiphany's stake in the work; Cuts 7-16 build
|
||||
Huginn and the `eureka-state` client and are still running. Evidence: the cut
|
||||
map `notes/eureka-pipeline-state-cut.md`, the branch
|
||||
`codex/eureka-pipeline-state`, and the Soul reports recorded in the map's
|
||||
Landed section.
|
||||
|
||||
## Summary
|
||||
|
||||
Epiphany now owns the schemas for Eureka's pipeline state, in one leaf
|
||||
library that a separate organ can depend on without dragging the runtime in
|
||||
with it. `epiphany-pipeline` is 2,444 lines with its tests, 34 of them, and
|
||||
publishes 13 derived schemas. It has no dependency on `epiphany-core`; the
|
||||
dependency runs the other way. The public surface is five doors:
|
||||
registration, prepare, decode, write-envelope validation, and reference
|
||||
validation.
|
||||
|
||||
The phase landed across 113 commits in three days. At writing, the leaf is
|
||||
pinned by Huginn at `5cda0886` and nothing in Epiphany blocks the organ.
|
||||
|
||||
## Scope and invariants
|
||||
|
||||
The target was schema ownership, and only that. The organ, the index, the
|
||||
CultNet surface and the MCP client were all excluded from this repo by
|
||||
design, because the previous shape put a store, a lease and git preconditions
|
||||
inside Epiphany and the operator rejected it mid-campaign.
|
||||
|
||||
The invariants that had to survive: one owner per decision, a key grammar in
|
||||
which no reader can excuse a document it does not like, schemas that derive
|
||||
byte for byte from the types rather than being maintained beside them, and a
|
||||
leaf that a second repo can consume without inheriting the runtime.
|
||||
|
||||
The earlier attempt failed by bundling. The repo-owned store design is kept
|
||||
in the map under History, clearly marked dead, rather than deleted, because
|
||||
the reason it died is the reason Cut 4 exists.
|
||||
|
||||
## Timeline
|
||||
|
||||
| Cut | What it owned | Outcome |
|
||||
|---|---|---|
|
||||
| 1-2 | Re-pin CultLib, collapse to one commit owner | Landed, Soul-closed |
|
||||
| 3a | Ten document kinds and schemas, plus store, lease, git gates | Landed, then half of it deleted by Cut 4 |
|
||||
| 4 | Delete the repo-owned store whole | 1,302 lines removed, 549 added |
|
||||
| 5 | Collapse the store profile back out | Landed |
|
||||
| 6 | Extract `epiphany-pipeline` as a leaf | Landed after two fix batches |
|
||||
| 6b | The key grammar | Three fix batches; the grammar was redesigned rather than patched a fourth time |
|
||||
| 6c | The Ghostlight shapes | Landed, one fix batch plus two harness repairs |
|
||||
| 6d | Resolution history and stewardship by sequence | Landed both sides; the Huginn follow-up needed its own fix batch |
|
||||
| follow-up | The public reference validator | Landed both sides, `5cda0886` and `5ba7b0c6` |
|
||||
|
||||
## Structural delta
|
||||
|
||||
Cut 4 is the honest headline: it deleted 1,302 lines and added 549, and
|
||||
retired one store format, one lease and two git preconditions. Cut 6 moved
|
||||
the types out of `epiphany-core` rather than copying them, so the extraction
|
||||
was a move and not a fork.
|
||||
|
||||
The estimates were wrong in a consistent direction. Cut 4's test arithmetic
|
||||
was off because the deleted module held 20 tests on Windows rather than 15,
|
||||
and Cut 6's `epiphany-core` estimate of −623 should have read about −1,060
|
||||
once the test module travelled with the code. Both corrections are recorded
|
||||
in the map against the sections they falsify. Cut 6d's test delta was +181,
|
||||
not the +173 claimed.
|
||||
|
||||
Nothing was parked in this phase.
|
||||
|
||||
## What Soul caught
|
||||
|
||||
Green tests passed all of the following.
|
||||
|
||||
**Split and mislocated authority.** The epoch guard had no test that could
|
||||
even construct an envelope to reach it, so an `if false` mutation survived.
|
||||
Self ruled deletion over testing what nothing can construct, and Cut 8 wrote
|
||||
it in the organ where admission can reach it. Separately, the write path was
|
||||
believed live and was in fact test-only; `decode` and the CultCache imports
|
||||
looked live only because dead code mentioned them.
|
||||
|
||||
**A rule open one level up, three times.** The key grammar was patched twice
|
||||
and the same invariant came back both times. The third occurrence was the
|
||||
signal to redesign rather than patch. Soul then found no collision in 212,450
|
||||
adversarial keys against the new grammar.
|
||||
|
||||
**Readers excusing what writers refuse.** The reader accepted a trailing dot
|
||||
on the root and the local while the writer refused it, so a reference with
|
||||
trailing junk passed the reader against its own documentation. A mutant
|
||||
excusing the `Instance` root from the kind check survived: exactly the shape
|
||||
Cut 6b existed to kill.
|
||||
|
||||
**Tests pinning spelling instead of rules.** A depth test restated the
|
||||
literal 64 rather than referencing the bound, so moving both to 60 passed it.
|
||||
Fixtures that used the sequence 1 at every nesting level left a nested
|
||||
resolution's own sequence unpinned.
|
||||
|
||||
**A regression introduced by an earlier fix.** An exact replay of any batch
|
||||
carrying a derived write was refused rather than recognised as already
|
||||
admitted, because the derivation recomputed a sequence over an image that
|
||||
already held the first admission's record. The replay test had used only
|
||||
batches without derivations.
|
||||
|
||||
**Ordering by key rather than by meaning.** The hand-off replay picked its
|
||||
subject by key order, so from the eleventh transfer on, where `n11` sorts
|
||||
before `n2`, it derived a withdrawal of the wrong record. Ten round trips
|
||||
alone did not expose it.
|
||||
|
||||
**Dead code with a test that could not reach it.** The resolution-base arm of
|
||||
the reinstatement rule was unreachable because the depth cap fired first.
|
||||
Deleting it was the point: a rule defended by a test that cannot run is the
|
||||
failure mode of subtraction, not a defence against it.
|
||||
|
||||
## Operator corrections
|
||||
|
||||
**The repo-owned store.** The operator rejected it mid-campaign, and Cut 4
|
||||
deleted it whole rather than demoting it. This is the single largest
|
||||
correction of the phase and the reason the leaf is clean.
|
||||
|
||||
**A hand-off is a transfer, not a lease.** Self had modelled every stewardship
|
||||
hand-off as a loan that returns. The operator's example was Odin holding a
|
||||
swarm of infrastructure tools and spinning off a new steward when the
|
||||
workload justifies it, with no return. The missing context was that Self had
|
||||
only ever seen the cross-cutting case, where borrowing is the natural reading.
|
||||
|
||||
**Withdrawn resolutions are kept.** Self's default threw the record away.
|
||||
The operator's model was a subject as a question with one canonical answer
|
||||
and an affordance for withdrawal, where the withdrawn answer is context to
|
||||
learn from. The operator also named the assumption the ruling rests on: that
|
||||
the context actually reaches the agents. Self had landed the opposite default
|
||||
and reverted it.
|
||||
|
||||
**Single-writer semantics are CultCache's.** Self attributed them to redb.
|
||||
redb is the storage engine underneath; the owned store lock and the
|
||||
compare-and-swap batch are CultCache's, and were built in this same session.
|
||||
|
||||
**Stop spending the scarce model on Hands.** The default model inherits the
|
||||
root's, so every dispatch had been running on Fable without anyone choosing
|
||||
it. The rule now is to name the model on every dispatch.
|
||||
|
||||
## Incidents
|
||||
|
||||
**Cargo through the Bash tool.** `CARGO_TARGET_DIR=C:\...` has its
|
||||
backslashes collapsed, and cargo starts a from-scratch build into a path that
|
||||
does not exist. Nothing landed in the shared directory. Cargo on this host
|
||||
runs through PowerShell only.
|
||||
|
||||
**A scoped `cargo clean` against the brief.** Hands freed and partly retook
|
||||
about 400 MB of the shared target directory. Soul could not establish that
|
||||
another crate's artifacts were evicted. The next brief forbids it by name.
|
||||
|
||||
**Agents ending their turn mid-job, three times.** The briefs now name the
|
||||
mechanism rather than the intent: a wait is a foreground poll loop inside one
|
||||
tool call. One agent left detached timers that re-fired notifications for an
|
||||
hour, so the briefs also forbid background monitors that outlive the run.
|
||||
|
||||
**The harness ate fixes.** An early mutation runner restored from a sidecar
|
||||
that predated the fix it was testing, reverting real work. A later version
|
||||
committed a NUL byte and a literal control character into a target. The
|
||||
current harness reads the original as bytes, writes the sidecar before any
|
||||
write, restores hash-first, attempts every target, and prints the original
|
||||
digest before rethrowing. A tool-enforced timeout was the exact path that
|
||||
left a tree mutated, so the harness now owns its own timeout and kills the
|
||||
process tree inside its own cleanup.
|
||||
|
||||
**Map splices landing at the wrong heading depth.** A specification written
|
||||
in a scratchpad began one level deeper than the map's section, and the fold
|
||||
put every heading under it a level too deep.
|
||||
|
||||
**My own errors in the map**, each corrected in place: a follow-up number
|
||||
used twice, an ungreppable regex, seventeen entries described as nineteen, a
|
||||
zip digest labelled as the DLL's, a claim that CI was green throughout, an
|
||||
overstated scar, and one commit attributed to its neighbour.
|
||||
|
||||
## What worked
|
||||
|
||||
**Mutation suites as the unit of proof, not test counts.** Every operator
|
||||
ruling and every new rule gets a test that fails under its own mutation, and
|
||||
the suite is committed with a no-op control that proves the harness restores
|
||||
bytes exactly. The controls caught the harness eating fixes; test counts
|
||||
never would have.
|
||||
|
||||
**A revert mutation and a loosening mutation per rule.** A plain revert is
|
||||
too easy a target. The loosening is what found the reader excusing a root
|
||||
kind and the unpinned nested sequence. When a loosening turns out to be
|
||||
unfalsifiable by construction, it is demoted to a recorded note with the
|
||||
reason rather than left in the suite implying coverage it does not have.
|
||||
|
||||
**Redesigning on the third occurrence.** The key grammar came back twice
|
||||
under patches. The rule that emerged is to treat the third appearance of an
|
||||
invariant as evidence about the design, not about the instance.
|
||||
|
||||
**Keeping dead designs marked rather than deleted.** The repo-owned store
|
||||
sections carry banners naming their false sentences. A reader who finds the
|
||||
old model finds it labelled.
|
||||
|
||||
**Not capping the Soul loop on a foundation cut.** Cuts 6 and 6b each took
|
||||
four passes and the later passes still found real defects.
|
||||
|
||||
## What to change in the pipeline
|
||||
|
||||
All four of these were approved by the operator on 2026-09-16 after a cost
|
||||
review that put roughly half the overhead on process waste and roughly half
|
||||
on the inescapable cost of a second agent.
|
||||
|
||||
1. **A loosening mutation per rule, alongside the revert.** Landed in the
|
||||
Hands brief.
|
||||
2. **One harness in the Eureka repository.** Owed. The PowerShell harness
|
||||
still lives in `F:\Projects\Epiphany\tools\` and Huginn reaches across for
|
||||
it; CultLib's JavaScript runner needs the same contract by name.
|
||||
3. **Reports are findings, promises and numbers only, and a second Soul pass
|
||||
is scoped to the fix batch diff.** Landed in both briefs.
|
||||
4. **A map commit per cut, rulings excepted.** In effect.
|
||||
|
||||
## Open follow-ups
|
||||
|
||||
- **Cut 9's spec text calls the `V10L` loosening failable.** It was demoted
|
||||
when the reference validator made it unfalsifiable. Stale text in the map's
|
||||
Cut 9 section, harmless to the code.
|
||||
- **FU-9: CultLib's lock failure is text-matched.** `cultcache-rs` needs a
|
||||
typed error. Owner is CultLib, and it can wait because the only consumer
|
||||
matching the text is a test.
|
||||
- **FU-1 through FU-8** stand as recorded in the map, each with its file and
|
||||
its reason.
|
||||
- **Cut 11 must index withdrawn resolutions with their reasons.** This is the
|
||||
second obligation the operator's Q17 ruling created; the first, a history
|
||||
view, landed in Cut 9.
|
||||
45
schemas/README.md
Normal file
45
schemas/README.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Schemas
|
||||
|
||||
This folder is the canonical paperwork shrine for the state contracts Huginn
|
||||
publishes and the pipeline leaf derives.
|
||||
|
||||
If a Persona field or Mind document matters
|
||||
enough to steer the machine, it should have a receipt here instead of living
|
||||
only in one Rust struct or one developer's damp recollection.
|
||||
|
||||
## Canonical surfaces
|
||||
|
||||
- [cultnet/gamecult.persona_state.v0.schema.json](./cultnet/gamecult.persona_state.v0.schema.json):
|
||||
portable Persona state contract for Eureka's Persona, VoidBot repo Personas, and
|
||||
Ghostlight characters. It carries explicit provenance, public presentation
|
||||
metadata, typed `candidateActions`, and a non-authoritative extension bag for
|
||||
source-specific fields; social bonds, status reads, and doctrine stances are
|
||||
typed affect records rather than generic thought blobs. Timestamps use JSON
|
||||
Schema `date-time`, `presentation` is required, and `custom` enum values have
|
||||
companion custom-label fields.
|
||||
- [cultnet/README.md](./cultnet/README.md):
|
||||
JSON Schema publication artifacts for typed CultNet boundaries. Live
|
||||
providers own their schema-catalog responses.
|
||||
|
||||
## Source Of Truth
|
||||
|
||||
The living implementation is in code:
|
||||
|
||||
- [eureka-pipeline](../crates/eureka-pipeline/src/lib.rs): the pipeline document types and their derived schemas
|
||||
|
||||
The rule is simple:
|
||||
|
||||
- code owns executable truth
|
||||
- this folder owns human-readable contract receipts
|
||||
- vendored copies are downstream, not the throne
|
||||
|
||||
## Update Discipline
|
||||
|
||||
When changing any of the following, update this folder in the same pass:
|
||||
|
||||
- canonical organ-state or Persona family names
|
||||
- standing role trait names
|
||||
- schema version identifiers
|
||||
|
||||
If a change lands in code without a matching receipt here, assume the machine
|
||||
has started whispering to itself in the walls again.
|
||||
|
|
@ -9,3 +9,16 @@ type of the same name in `crates/huginn-mind/src/wire.rs`. Regenerate by hand
|
|||
and the test refuses it:
|
||||
`cargo test -p huginn-mind --lib -- --exact wire::tests::published_wire_schemas_match_derivation`
|
||||
compares file to derivation byte for byte.
|
||||
|
||||
## The pipeline catalogue
|
||||
|
||||
`index.json` is the publication manifest for every schema here: the thirteen
|
||||
`epiphany.pipeline.*.v2` schemas, `gamecult.persona_state.v0` and the two
|
||||
`huginn.mind_*` wire schemas. The pipeline
|
||||
schemas are derived from the value types in `crates/eureka-pipeline` and checked
|
||||
byte for byte by its `pipeline_published_schemas_match_derivation` test. Each
|
||||
document payload is `[value]`, a one-element MessagePack array whose element is
|
||||
the named map the schema describes. Text bounds are UTF-8 bytes and enforced by
|
||||
admission, not by the schema. A new named field with a serde default keeps
|
||||
`epiphany.pipeline.epoch.v2`; a breaking change bumps the epoch, and the new
|
||||
binary refuses the old store.
|
||||
|
|
|
|||
89
schemas/cultnet/epiphany.pipeline.campaign.v2.schema.json
Normal file
89
schemas/cultnet/epiphany.pipeline.campaign.v2.schema.json
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"$defs": {
|
||||
"DocRef": {
|
||||
"properties": {
|
||||
"commit": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"end_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"start_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"start_line",
|
||||
"end_line",
|
||||
"commit"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Title": {
|
||||
"description": "A title: 1 to 200 UTF-8 bytes, enforced by admission; JSON Schema\nlength counts characters. Holds at least one alphanumeric character,\nno control character, and no bidi control character (`U+061C`,\n`U+200E`, `U+200F`, `U+202A`-`U+202E`, `U+2066`-`U+2069`).",
|
||||
"maxLength": 200,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.campaign.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"repos": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"slug": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"target_doc": {
|
||||
"$ref": "#/$defs/DocRef"
|
||||
},
|
||||
"title": {
|
||||
"$ref": "#/$defs/Title"
|
||||
},
|
||||
"working_branch": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug",
|
||||
"title",
|
||||
"repos",
|
||||
"working_branch",
|
||||
"target_doc"
|
||||
],
|
||||
"title": "PipelineCampaign",
|
||||
"type": "object"
|
||||
}
|
||||
375
schemas/cultnet/epiphany.pipeline.cut_report.v2.schema.json
Normal file
375
schemas/cultnet/epiphany.pipeline.cut_report.v2.schema.json
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
{
|
||||
"$defs": {
|
||||
"CodeLocation": {
|
||||
"properties": {
|
||||
"end_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"line"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CommitRange": {
|
||||
"properties": {
|
||||
"base": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"head": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"base",
|
||||
"head"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Deviation": {
|
||||
"properties": {
|
||||
"what": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"why": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"what",
|
||||
"why"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Evidence": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"$ref": "#/$defs/EvidenceKind"
|
||||
},
|
||||
"locator": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"result": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"locator",
|
||||
"result"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EvidenceKind": {
|
||||
"enum": [
|
||||
"Command",
|
||||
"Test",
|
||||
"Mutation",
|
||||
"Probe",
|
||||
"SourceRead",
|
||||
"Capture"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"LandedName": {
|
||||
"description": "A name the cut landed, and where it lives.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"MutationRecord": {
|
||||
"description": "One mutation a report ran: a key-safe label a verdict claim can name,\nthe exact edit, and the tree it was applied to.",
|
||||
"properties": {
|
||||
"after": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"before": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"commit": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"failed_as_expected": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/$defs/CodeLocation"
|
||||
},
|
||||
"rule": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"rule",
|
||||
"location",
|
||||
"before",
|
||||
"after",
|
||||
"commit",
|
||||
"failed_as_expected"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"Promise": {
|
||||
"description": "A promise a report makes about what it landed. Not a document and not\nresolvable: it is identified by its report's id and its label, as a\n`TargetInvariant` is by its target's. Soul measures every one (ruling\nA), and the rule that every one is measured is admission's.",
|
||||
"properties": {
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"text": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ReportCommit": {
|
||||
"properties": {
|
||||
"builds": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sha": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"subject": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sha",
|
||||
"subject",
|
||||
"builds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"StructuralDelta": {
|
||||
"properties": {
|
||||
"dependencies_added": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"dependencies_removed": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"formats_added": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"formats_removed": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"lines_added": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lines_removed": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"targets_added": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"targets_removed": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lines_added",
|
||||
"lines_removed",
|
||||
"dependencies_added",
|
||||
"dependencies_removed",
|
||||
"formats_added",
|
||||
"formats_removed",
|
||||
"targets_added",
|
||||
"targets_removed"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.cut_report.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"attempt": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"branch": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"commits": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/ReportCommit"
|
||||
},
|
||||
"maxItems": 128,
|
||||
"type": "array"
|
||||
},
|
||||
"cut_spec": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"deviations": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Deviation"
|
||||
},
|
||||
"maxItems": 32,
|
||||
"type": "array"
|
||||
},
|
||||
"forks": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"landed_names": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/LandedName"
|
||||
},
|
||||
"maxItems": 128,
|
||||
"type": "array"
|
||||
},
|
||||
"mutations": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MutationRecord"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"promises": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Promise"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"range": {
|
||||
"$ref": "#/$defs/CommitRange"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
},
|
||||
"structural_delta": {
|
||||
"$ref": "#/$defs/StructuralDelta"
|
||||
},
|
||||
"undone": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 32,
|
||||
"type": "array"
|
||||
},
|
||||
"verification": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Evidence"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"cut_spec",
|
||||
"attempt",
|
||||
"repo",
|
||||
"branch",
|
||||
"commits",
|
||||
"range",
|
||||
"verification",
|
||||
"mutations",
|
||||
"deviations",
|
||||
"forks",
|
||||
"structural_delta",
|
||||
"landed_names",
|
||||
"undone",
|
||||
"promises"
|
||||
],
|
||||
"title": "PipelineCutReport",
|
||||
"type": "object"
|
||||
}
|
||||
412
schemas/cultnet/epiphany.pipeline.cut_spec.v2.schema.json
Normal file
412
schemas/cultnet/epiphany.pipeline.cut_spec.v2.schema.json
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
{
|
||||
"$defs": {
|
||||
"AuthorityMap": {
|
||||
"properties": {
|
||||
"deletion_line": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"derived_state": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"forbidden_writers": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"inputs": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"outputs": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"shared_paths": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"owner",
|
||||
"inputs",
|
||||
"outputs",
|
||||
"derived_state",
|
||||
"forbidden_writers",
|
||||
"shared_paths",
|
||||
"deletion_line"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodeLocation": {
|
||||
"properties": {
|
||||
"end_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"line"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CutDelete": {
|
||||
"properties": {
|
||||
"lines": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"lines",
|
||||
"note"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CutVerification": {
|
||||
"properties": {
|
||||
"builds": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"negative": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/NegativeCheck"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"operator": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"tests": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/VerificationTest"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"builds",
|
||||
"tests",
|
||||
"negative",
|
||||
"operator"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileChange": {
|
||||
"properties": {
|
||||
"change": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/$defs/CodeLocation"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"location",
|
||||
"change"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"NegativeCheck": {
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"scope": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pattern",
|
||||
"scope"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"StructuralDelta": {
|
||||
"properties": {
|
||||
"dependencies_added": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"dependencies_removed": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"formats_added": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"formats_removed": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"lines_added": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lines_removed": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"targets_added": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"targets_removed": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lines_added",
|
||||
"lines_removed",
|
||||
"dependencies_added",
|
||||
"dependencies_removed",
|
||||
"formats_added",
|
||||
"formats_removed",
|
||||
"targets_added",
|
||||
"targets_removed"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Title": {
|
||||
"description": "A title: 1 to 200 UTF-8 bytes, enforced by admission; JSON Schema\nlength counts characters. Holds at least one alphanumeric character,\nno control character, and no bidi control character (`U+061C`,\n`U+200E`, `U+200F`, `U+202A`-`U+202E`, `U+2066`-`U+2069`).",
|
||||
"maxLength": 200,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"VerificationTest": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"pins": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"pins"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.cut_spec.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"adds": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"authority_map": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/AuthorityMap"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"base": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"branch": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"cut": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"deletes": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/CutDelete"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"depends_on": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"estimate": {
|
||||
"$ref": "#/$defs/StructuralDelta"
|
||||
},
|
||||
"file_changes": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/FileChange"
|
||||
},
|
||||
"maxItems": 256,
|
||||
"type": "array"
|
||||
},
|
||||
"first": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"keeps_moves": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"questions": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
},
|
||||
"revision": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"rulings": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 32,
|
||||
"type": "array"
|
||||
},
|
||||
"title": {
|
||||
"$ref": "#/$defs/Title"
|
||||
},
|
||||
"verification": {
|
||||
"$ref": "#/$defs/CutVerification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"cut",
|
||||
"revision",
|
||||
"title",
|
||||
"repo",
|
||||
"branch",
|
||||
"base",
|
||||
"depends_on",
|
||||
"first",
|
||||
"deletes",
|
||||
"keeps_moves",
|
||||
"adds",
|
||||
"file_changes",
|
||||
"verification",
|
||||
"estimate",
|
||||
"rulings",
|
||||
"questions"
|
||||
],
|
||||
"title": "PipelineCutSpec",
|
||||
"type": "object"
|
||||
}
|
||||
264
schemas/cultnet/epiphany.pipeline.finding.v2.schema.json
Normal file
264
schemas/cultnet/epiphany.pipeline.finding.v2.schema.json
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
{
|
||||
"$defs": {
|
||||
"CodeLocation": {
|
||||
"properties": {
|
||||
"end_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"line"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CommitRange": {
|
||||
"properties": {
|
||||
"base": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"head": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"base",
|
||||
"head"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Evidence": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"$ref": "#/$defs/EvidenceKind"
|
||||
},
|
||||
"locator": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"result": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"locator",
|
||||
"result"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EvidenceKind": {
|
||||
"enum": [
|
||||
"Command",
|
||||
"Test",
|
||||
"Mutation",
|
||||
"Probe",
|
||||
"SourceRead",
|
||||
"Capture"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FindingConfidence": {
|
||||
"enum": [
|
||||
"Confirmed",
|
||||
"Plausible"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FindingOrigin": {
|
||||
"enum": [
|
||||
"Introduced",
|
||||
"PreExisting"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FindingSeverity": {
|
||||
"enum": [
|
||||
"Blocker",
|
||||
"High",
|
||||
"Medium",
|
||||
"Low"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ForeignRef": {
|
||||
"description": "A citation of a document in another repo's pipeline store (D6).",
|
||||
"properties": {
|
||||
"commit": {
|
||||
"$ref": "#/$defs/FullSha"
|
||||
},
|
||||
"id": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/PipelineKind"
|
||||
},
|
||||
"payload_sha256": {
|
||||
"$ref": "#/$defs/Sha256Hex"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"repo",
|
||||
"commit",
|
||||
"kind",
|
||||
"id",
|
||||
"payload_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FullSha": {
|
||||
"description": "A full git commit id: exactly 40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"Para": {
|
||||
"description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.",
|
||||
"maxLength": 4000,
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Sha256Hex": {
|
||||
"description": "A SHA-256 digest: exactly 64 lowercase hex characters.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.finding.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"claim": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"confidence": {
|
||||
"$ref": "#/$defs/FindingConfidence"
|
||||
},
|
||||
"evidence": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Evidence"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"failure_scenario": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"invariants": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"locations": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/CodeLocation"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"origin": {
|
||||
"$ref": "#/$defs/FindingOrigin"
|
||||
},
|
||||
"precedents": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/ForeignRef"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"range": {
|
||||
"$ref": "#/$defs/CommitRange"
|
||||
},
|
||||
"severity": {
|
||||
"$ref": "#/$defs/FindingSeverity"
|
||||
},
|
||||
"verdict": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"verdict",
|
||||
"label",
|
||||
"range",
|
||||
"confidence",
|
||||
"severity",
|
||||
"claim",
|
||||
"invariants",
|
||||
"locations",
|
||||
"failure_scenario",
|
||||
"evidence",
|
||||
"precedents",
|
||||
"origin"
|
||||
],
|
||||
"title": "PipelineFinding",
|
||||
"type": "object"
|
||||
}
|
||||
132
schemas/cultnet/epiphany.pipeline.follow_up.v2.schema.json
Normal file
132
schemas/cultnet/epiphany.pipeline.follow_up.v2.schema.json
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
{
|
||||
"$defs": {
|
||||
"CodeLocation": {
|
||||
"properties": {
|
||||
"end_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"line"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineRef": {
|
||||
"description": "A reference to another document. The id is parsed as a full pipeline id of\nthe declared `kind`, so the kind and the id's kind segment cannot disagree.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/PipelineKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.follow_up.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"item": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"locations": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/CodeLocation"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"why_it_can_wait": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"label",
|
||||
"source",
|
||||
"repo",
|
||||
"locations",
|
||||
"item",
|
||||
"why_it_can_wait",
|
||||
"owner"
|
||||
],
|
||||
"title": "PipelineFollowUp",
|
||||
"type": "object"
|
||||
}
|
||||
66
schemas/cultnet/epiphany.pipeline.hand_off.v2.schema.json
Normal file
66
schemas/cultnet/epiphany.pipeline.hand_off.v2.schema.json
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"Para": {
|
||||
"description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.",
|
||||
"maxLength": 4000,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.hand_off.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "A reassignment of stewardship, recorded in both minds. `documents` names\nwhat travels with it.",
|
||||
"properties": {
|
||||
"documents": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 256,
|
||||
"type": "array"
|
||||
},
|
||||
"from_instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"handed_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"reason": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
},
|
||||
"to_instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"from_instance",
|
||||
"to_instance",
|
||||
"repo",
|
||||
"documents",
|
||||
"reason",
|
||||
"handed_on"
|
||||
],
|
||||
"title": "PipelineHandOff",
|
||||
"type": "object"
|
||||
}
|
||||
44
schemas/cultnet/epiphany.pipeline.instance.v2.schema.json
Normal file
44
schemas/cultnet/epiphany.pipeline.instance.v2.schema.json
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.instance.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "A mind's identity document. A store is canonical to exactly one\ninstance, and this says which; identity lives in the state, not in a\npath.",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"display_name": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"host": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instance",
|
||||
"display_name",
|
||||
"created_at",
|
||||
"host"
|
||||
],
|
||||
"title": "PipelineInstance",
|
||||
"type": "object"
|
||||
}
|
||||
147
schemas/cultnet/epiphany.pipeline.question.v2.schema.json
Normal file
147
schemas/cultnet/epiphany.pipeline.question.v2.schema.json
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"Para": {
|
||||
"description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.",
|
||||
"maxLength": 4000,
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineRef": {
|
||||
"description": "A reference to another document. The id is parsed as a full pipeline id of\nthe declared `kind`, so the kind and the id's kind segment cannot disagree.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/PipelineKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"QuestionOption": {
|
||||
"properties": {
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"text": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Title": {
|
||||
"description": "A title: 1 to 200 UTF-8 bytes, enforced by admission; JSON Schema\nlength counts characters. Holds at least one alphanumeric character,\nno control character, and no bidi control character (`U+061C`,\n`U+200E`, `U+200F`, `U+202A`-`U+202E`, `U+2066`-`U+2069`).",
|
||||
"maxLength": 200,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.question.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"asked_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"depends": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"options": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/QuestionOption"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"question": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"raised_in": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"recommended": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"title": {
|
||||
"$ref": "#/$defs/Title"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"label",
|
||||
"title",
|
||||
"question",
|
||||
"options",
|
||||
"recommended",
|
||||
"depends",
|
||||
"asked_on"
|
||||
],
|
||||
"title": "PipelineQuestion",
|
||||
"type": "object"
|
||||
}
|
||||
234
schemas/cultnet/epiphany.pipeline.resolution.v2.schema.json
Normal file
234
schemas/cultnet/epiphany.pipeline.resolution.v2.schema.json
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"Para": {
|
||||
"description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.",
|
||||
"maxLength": 4000,
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineRef": {
|
||||
"description": "A reference to another document. The id is parsed as a full pipeline id of\nthe declared `kind`, so the kind and the id's kind segment cannot disagree.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/PipelineKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ResolutionOutcome": {
|
||||
"description": "How a subject was resolved. Every referent is a parsed `PipelineRef`, so a\nresolution names its records by ids of the kinds they declare, validated\nwhere every other referent is; a `Fixed` commit is a `Sha` whose referent\nis outside the document set (ruling B). Whether a named document exists,\nand how many may supersede one subject, are admission's rules.",
|
||||
"oneOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Overturned, in whole or in part, by later records. The list is bounded\nhere because the enum is outside `value_types!`, the only place a list\nmaximum is emitted automatically.",
|
||||
"properties": {
|
||||
"Superseded": {
|
||||
"properties": {
|
||||
"by": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"by"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Superseded"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"Answered": {
|
||||
"properties": {
|
||||
"by": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"by"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Answered"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"Fixed": {
|
||||
"properties": {
|
||||
"by": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commit": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"commit"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Fixed"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"Deferred": {
|
||||
"properties": {
|
||||
"to": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"to"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Deferred"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"Recorded": {
|
||||
"properties": {
|
||||
"reason": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reason"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Recorded"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"Withdrawn": {
|
||||
"properties": {
|
||||
"reason": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reason"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Withdrawn"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.resolution.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "The record of how a subject was closed and by what. `sequence` is per\nsubject and set by the writer, `1` for the first, and it is the last\npart of the key, so a subject's resolutions share a prefix and a\nwithdrawn one is kept under its subject rather than overwritten.\nWhether a sequence is the previous plus one, and whether an earlier\nresolution still stands, are admission's rules: this crate refuses\nneither `0` nor a gap, exactly as it refuses neither `revision: 0` nor\na `revision` with no predecessor.",
|
||||
"properties": {
|
||||
"outcome": {
|
||||
"$ref": "#/$defs/ResolutionOutcome"
|
||||
},
|
||||
"rationale": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"resolved_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"sequence": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"subject": {
|
||||
"$ref": "#/$defs/PipelineRef"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"subject",
|
||||
"sequence",
|
||||
"outcome",
|
||||
"rationale",
|
||||
"resolved_on"
|
||||
],
|
||||
"title": "PipelineResolution",
|
||||
"type": "object"
|
||||
}
|
||||
174
schemas/cultnet/epiphany.pipeline.ruling.v2.schema.json
Normal file
174
schemas/cultnet/epiphany.pipeline.ruling.v2.schema.json
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"ForeignRef": {
|
||||
"description": "A citation of a document in another repo's pipeline store (D6).",
|
||||
"properties": {
|
||||
"commit": {
|
||||
"$ref": "#/$defs/FullSha"
|
||||
},
|
||||
"id": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/PipelineKind"
|
||||
},
|
||||
"payload_sha256": {
|
||||
"$ref": "#/$defs/Sha256Hex"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"repo",
|
||||
"commit",
|
||||
"kind",
|
||||
"id",
|
||||
"payload_sha256"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FullSha": {
|
||||
"description": "A full git commit id: exactly 40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"Para": {
|
||||
"description": "Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`.",
|
||||
"maxLength": 4000,
|
||||
"type": "string"
|
||||
},
|
||||
"PipelineKind": {
|
||||
"enum": [
|
||||
"Campaign",
|
||||
"Target",
|
||||
"Question",
|
||||
"Ruling",
|
||||
"CutSpec",
|
||||
"CutReport",
|
||||
"Verdict",
|
||||
"Finding",
|
||||
"FollowUp",
|
||||
"Resolution",
|
||||
"Instance",
|
||||
"Stewardship",
|
||||
"HandOff"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RulingAuthority": {
|
||||
"enum": [
|
||||
"Operator",
|
||||
"Standing",
|
||||
"Defaulted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"Sha256Hex": {
|
||||
"description": "A SHA-256 digest: exactly 64 lowercase hex characters.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Title": {
|
||||
"description": "A title: 1 to 200 UTF-8 bytes, enforced by admission; JSON Schema\nlength counts characters. Holds at least one alphanumeric character,\nno control character, and no bidi control character (`U+061C`,\n`U+200E`, `U+200F`, `U+202A`-`U+202E`, `U+2066`-`U+2069`).",
|
||||
"maxLength": 200,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.ruling.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"answers": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"authority": {
|
||||
"$ref": "#/$defs/RulingAuthority"
|
||||
},
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"choice": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"operator_quote": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"precedents": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/ForeignRef"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"ruled_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"ruling": {
|
||||
"$ref": "#/$defs/Para"
|
||||
},
|
||||
"title": {
|
||||
"$ref": "#/$defs/Title"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"label",
|
||||
"title",
|
||||
"ruling",
|
||||
"ruled_on",
|
||||
"precedents",
|
||||
"authority"
|
||||
],
|
||||
"title": "PipelineRuling",
|
||||
"type": "object"
|
||||
}
|
||||
55
schemas/cultnet/epiphany.pipeline.stewardship.v2.schema.json
Normal file
55
schemas/cultnet/epiphany.pipeline.stewardship.v2.schema.json
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Date": {
|
||||
"description": "A calendar date, `YYYY-MM-DD`.",
|
||||
"maxLength": 10,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"OrgRepo": {
|
||||
"description": "A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 +\n100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or\ntrailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never\n`.` or `..`, and does not end in `.git`, ASCII-case-insensitively.\nIdentity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany`\nare the same repository.",
|
||||
"maxLength": 140,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.stewardship.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "Stewardship over a repo, as an assignment recorded in a mind. One\ninstance may steward several repos, so the repo is part of the key.\n`sequence` is per `(instance, repo)`, set by the writer, `1` for the\nfirst, so a repo transferred away and back is two records under one\nprefix rather than one document overwriting the other. `assigned_on`\nis a field, not a key part. As on a resolution, whether the sequence\nis the previous plus one, and which assignment is in force, are\nadmission's rules and not this crate's.",
|
||||
"properties": {
|
||||
"assigned_on": {
|
||||
"$ref": "#/$defs/Date"
|
||||
},
|
||||
"instance": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"note": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/$defs/OrgRepo"
|
||||
},
|
||||
"sequence": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instance",
|
||||
"repo",
|
||||
"sequence",
|
||||
"assigned_on",
|
||||
"note"
|
||||
],
|
||||
"title": "PipelineStewardship",
|
||||
"type": "object"
|
||||
}
|
||||
117
schemas/cultnet/epiphany.pipeline.target.v2.schema.json
Normal file
117
schemas/cultnet/epiphany.pipeline.target.v2.schema.json
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"$defs": {
|
||||
"DocRef": {
|
||||
"properties": {
|
||||
"commit": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"end_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"start_line": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"start_line",
|
||||
"end_line",
|
||||
"commit"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"TargetInvariant": {
|
||||
"properties": {
|
||||
"label": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"statement": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"statement"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.target.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"canonical_implementations": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"doc": {
|
||||
"$ref": "#/$defs/DocRef"
|
||||
},
|
||||
"invariants": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/TargetInvariant"
|
||||
},
|
||||
"maxItems": 32,
|
||||
"type": "array"
|
||||
},
|
||||
"not_in_scope": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"maxItems": 32,
|
||||
"type": "array"
|
||||
},
|
||||
"revision": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"revision",
|
||||
"invariants",
|
||||
"not_in_scope",
|
||||
"canonical_implementations",
|
||||
"doc"
|
||||
],
|
||||
"title": "PipelineTarget",
|
||||
"type": "object"
|
||||
}
|
||||
166
schemas/cultnet/epiphany.pipeline.verdict.v2.schema.json
Normal file
166
schemas/cultnet/epiphany.pipeline.verdict.v2.schema.json
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
{
|
||||
"$defs": {
|
||||
"ClaimOutcome": {
|
||||
"enum": [
|
||||
"Holds",
|
||||
"Falsified",
|
||||
"Unproven"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommitRange": {
|
||||
"properties": {
|
||||
"base": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
},
|
||||
"head": {
|
||||
"$ref": "#/$defs/Sha"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"base",
|
||||
"head"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Evidence": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"$ref": "#/$defs/EvidenceKind"
|
||||
},
|
||||
"locator": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"result": {
|
||||
"$ref": "#/$defs/Line"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"locator",
|
||||
"result"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"EvidenceKind": {
|
||||
"enum": [
|
||||
"Command",
|
||||
"Test",
|
||||
"Mutation",
|
||||
"Probe",
|
||||
"SourceRead",
|
||||
"Capture"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"Label": {
|
||||
"description": "A key label: `[A-Za-z0-9_-]{1,64}`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"Line": {
|
||||
"description": "Text of at most 1,000 UTF-8 bytes.",
|
||||
"maxLength": 1000,
|
||||
"type": "string"
|
||||
},
|
||||
"Sha": {
|
||||
"description": "A git commit id: 7-40 lowercase hex characters.",
|
||||
"maxLength": 40,
|
||||
"type": "string"
|
||||
},
|
||||
"Short": {
|
||||
"description": "Text of at most 200 UTF-8 bytes.",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
},
|
||||
"Slug": {
|
||||
"description": "A campaign slug or key segment: label parts joined by `.`.",
|
||||
"maxLength": 64,
|
||||
"type": "string"
|
||||
},
|
||||
"VerdictClaim": {
|
||||
"description": "The promise this claim measured, and the report's mutations it ran. An\n`Unproven` claim with a promise is one Soul could not reach.",
|
||||
"properties": {
|
||||
"claim": {
|
||||
"$ref": "#/$defs/Line"
|
||||
},
|
||||
"evidence": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Evidence"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"findings": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"maxItems": 16,
|
||||
"type": "array"
|
||||
},
|
||||
"mutations": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
"maxItems": 8,
|
||||
"type": "array"
|
||||
},
|
||||
"outcome": {
|
||||
"$ref": "#/$defs/ClaimOutcome"
|
||||
},
|
||||
"promise": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/Label"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"claim",
|
||||
"outcome",
|
||||
"evidence",
|
||||
"findings",
|
||||
"mutations"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.verdict.v2.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"campaign": {
|
||||
"$ref": "#/$defs/Slug"
|
||||
},
|
||||
"claims": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/VerdictClaim"
|
||||
},
|
||||
"maxItems": 64,
|
||||
"type": "array"
|
||||
},
|
||||
"cut_report": {
|
||||
"$ref": "#/$defs/Short"
|
||||
},
|
||||
"pass": {
|
||||
"format": "uint32",
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
},
|
||||
"range": {
|
||||
"$ref": "#/$defs/CommitRange"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"campaign",
|
||||
"cut_report",
|
||||
"pass",
|
||||
"range",
|
||||
"claims"
|
||||
],
|
||||
"title": "PipelineVerdict",
|
||||
"type": "object"
|
||||
}
|
||||
1024
schemas/cultnet/gamecult.persona_state.v0.schema.json
Normal file
1024
schemas/cultnet/gamecult.persona_state.v0.schema.json
Normal file
File diff suppressed because it is too large
Load diff
179
schemas/cultnet/index.json
Normal file
179
schemas/cultnet/index.json
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
{
|
||||
"schemaVersion": "epiphany.cultnet_catalog.v0",
|
||||
"schemas": [
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/cultnet/gamecult.persona_state.v0.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "gamecult.persona_state.v0",
|
||||
"documentType": "gamecult.persona_state.v0",
|
||||
"title": "GameCult Persona State v0",
|
||||
"path": "gamecult.persona_state.v0.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.campaign.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.campaign.v2",
|
||||
"documentType": "epiphany.pipeline.campaign.v2",
|
||||
"title": "Epiphany Pipeline Campaign v2",
|
||||
"path": "epiphany.pipeline.campaign.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.target.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.target.v2",
|
||||
"documentType": "epiphany.pipeline.target.v2",
|
||||
"title": "Epiphany Pipeline Target v2",
|
||||
"path": "epiphany.pipeline.target.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.question.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.question.v2",
|
||||
"documentType": "epiphany.pipeline.question.v2",
|
||||
"title": "Epiphany Pipeline Question v2",
|
||||
"path": "epiphany.pipeline.question.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.ruling.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.ruling.v2",
|
||||
"documentType": "epiphany.pipeline.ruling.v2",
|
||||
"title": "Epiphany Pipeline Ruling v2",
|
||||
"path": "epiphany.pipeline.ruling.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.cut_spec.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.cut_spec.v2",
|
||||
"documentType": "epiphany.pipeline.cut_spec.v2",
|
||||
"title": "Epiphany Pipeline CutSpec v2",
|
||||
"path": "epiphany.pipeline.cut_spec.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.cut_report.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.cut_report.v2",
|
||||
"documentType": "epiphany.pipeline.cut_report.v2",
|
||||
"title": "Epiphany Pipeline CutReport v2",
|
||||
"path": "epiphany.pipeline.cut_report.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.verdict.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.verdict.v2",
|
||||
"documentType": "epiphany.pipeline.verdict.v2",
|
||||
"title": "Epiphany Pipeline Verdict v2",
|
||||
"path": "epiphany.pipeline.verdict.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.finding.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.finding.v2",
|
||||
"documentType": "epiphany.pipeline.finding.v2",
|
||||
"title": "Epiphany Pipeline Finding v2",
|
||||
"path": "epiphany.pipeline.finding.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.follow_up.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.follow_up.v2",
|
||||
"documentType": "epiphany.pipeline.follow_up.v2",
|
||||
"title": "Epiphany Pipeline FollowUp v2",
|
||||
"path": "epiphany.pipeline.follow_up.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.resolution.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.resolution.v2",
|
||||
"documentType": "epiphany.pipeline.resolution.v2",
|
||||
"title": "Epiphany Pipeline Resolution v2",
|
||||
"path": "epiphany.pipeline.resolution.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.instance.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.instance.v2",
|
||||
"documentType": "epiphany.pipeline.instance.v2",
|
||||
"title": "Epiphany Pipeline Instance v2",
|
||||
"path": "epiphany.pipeline.instance.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.stewardship.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.stewardship.v2",
|
||||
"documentType": "epiphany.pipeline.stewardship.v2",
|
||||
"title": "Epiphany Pipeline Stewardship v2",
|
||||
"path": "epiphany.pipeline.stewardship.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.pipeline.hand_off.v2.schema.json",
|
||||
"kind": "document_payload",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "epiphany.pipeline.hand_off.v2",
|
||||
"documentType": "epiphany.pipeline.hand_off.v2",
|
||||
"title": "Epiphany Pipeline HandOff v2",
|
||||
"path": "epiphany.pipeline.hand_off.v2.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "huginn.mind_request.v1",
|
||||
"kind": "wire_message",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "huginn.mind_request.v1",
|
||||
"title": "Huginn mind request",
|
||||
"path": "huginn.mind_request.v1.schema.json"
|
||||
},
|
||||
{
|
||||
"schemaId": "huginn.mind_response.v1",
|
||||
"kind": "wire_message",
|
||||
"wireContracts": [
|
||||
"cultnet.schema.v0"
|
||||
],
|
||||
"schemaVersion": "huginn.mind_response.v1",
|
||||
"title": "Huginn mind response",
|
||||
"path": "huginn.mind_response.v1.schema.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue