feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
|
|
|
|
|
|
export module Crafter.Review:State;
|
|
|
|
|
import std;
|
|
|
|
|
import :Json;
|
|
|
|
|
|
|
|
|
|
// unit-state.json (per-unit reuse), the run lock, the usage totals and the
|
|
|
|
|
// stdout summary. unit-state.json is merged on write, because drivers share
|
|
|
|
|
// one gate-state dir, and one full run per hash holds the hash's lock: every
|
|
|
|
|
// driver on the machine reads and writes these, so the files, the locks and
|
|
|
|
|
// the layout do not change.
|
|
|
|
|
export namespace Crafter::Review::State {
|
|
|
|
|
// {version: 1, entries: {}, personaPass: null}
|
|
|
|
|
Json EmptyState();
|
|
|
|
|
// The file as JSON, whatever its shape, or the empty state when it is
|
|
|
|
|
// missing or does not parse.
|
|
|
|
|
Json LoadState(const std::filesystem::path& path);
|
|
|
|
|
// This run's stores and drops applied to the state on disk — pure. Drops
|
|
|
|
|
// win over sets; an entry of `sets` that is not a whole pass gets its
|
|
|
|
|
// clean-read count from the FILE's previous entry (or the one-read
|
|
|
|
|
// synthesis of an entry stored before the count was kept) plus `read`
|
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
|
|
|
// unless that (hash, round) is counted already; `persona` sets
|
|
|
|
|
// personaPass when given. Garbage on disk is the empty state. `sets` is
|
|
|
|
|
// an object; a null `read` counts nothing.
|
|
|
|
|
Json MergeState(const Json& disk, const Json& sets, std::span<const std::string> drops = {}, const Json& read = nullptr, const Json& persona = nullptr);
|
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
|
|
|
// The whole state written as it is, through `<path>.tmp`.
|
|
|
|
|
void SaveWhole(const std::filesystem::path& path, const Json& state);
|
|
|
|
|
// Merge-on-write: the file re-read under `<path>.lock`, this run's deltas
|
|
|
|
|
// applied (MergeState), written atomically; returns the merged state. Two
|
|
|
|
|
// drivers on one gate-state dir would otherwise overwrite each other's
|
|
|
|
|
// stores, and a --no-reuse run would wipe the file from its empty object.
|
feat(modes): the kernel modes - a patch series and a mailing-list reply
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
2026-10-31 22:56:12 +01:00
|
|
|
Json SaveState(const std::filesystem::path& path, const Json& sets, std::span<const std::string> drops = {}, const Json& read = nullptr, const Json& persona = nullptr);
|
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
|
|
|
|
|
|
|
|
// ---- one full run per hash -------------------------------------------------
|
|
|
|
|
// Another full run holds this hash's lock; what() names the holder.
|
|
|
|
|
class RunLocked : public std::runtime_error {
|
|
|
|
|
public:
|
|
|
|
|
using std::runtime_error::runtime_error;
|
|
|
|
|
};
|
|
|
|
|
std::filesystem::path LockPath(const std::filesystem::path& gateDir, std::string_view mode, std::string_view hash8);
|
|
|
|
|
// The holder's JSON, {} when there is none or it does not parse.
|
|
|
|
|
Json ReadLockInfo(const std::filesystem::path& path);
|
|
|
|
|
std::string Describe(const Json& info);
|
|
|
|
|
|
feat(config): modes - a mode is an artifact kind, the lenses it runs and its effort
The config defines the modes (modes.<name>): the kind of artifact the mode
reviews - reply, series or commits, the three shipped partitions; a kind
is a mode of its own name - the lenses it runs, in its kind's gate order,
and the effort it runs at, else the kind's default (max for a reply or a
series, xhigh for a commit range). `run --mode` names any of them, the
dump records mode and kind, and everything that decided by the mode's
name - the partition and hash, the prompts, the schemas, the disposition
and reuse-key rules, what the CLI requires - decides by the kind.
Every lens stays off until a mode lists it; lenses.<name>.run is retired
with a warning that says so, and a lens of another kind, a name that is
no lens, a lens listed twice and an unknown effort are config problems.
The defaults run no lens; the template ships the three kinds as modes
with every lens of their kind at the kind's effort, to trim (the owner's
ruling of 2026-11-01), and init says so as it writes it - a kept config
is the repository's own, and only one that lists no lens gets a step.
The readers of the retired key follow: the CI smoke config, the shipped
skill, the procedure (its description of the config as well as its
mechanics), the manifest template, the run's messages and the comments
that named the three modes. The run lock, the run numbers and the dump
names are the kind's, so one full run per hash holds across the modes
of a kind and their rounds count on together; the dump says which mode
ran. The database broker starts for a lens the mode runs, as every other
reader of the lenses off reads them. A named mode runs through the CLI
in the kernel-modes test.
2026-11-01 01:31:40 +01:00
|
|
|
// An exclusive, non-blocking flock on gate-state/.run-<kind>-<hash8>.lock,
|
feat(pipeline): the pure pipeline, checked against its goldens
The modules that turn a frozen range into passes and passes into a
verdict: :Schema, :Tools (the DENY, git and base lists, a leaf, so the
config and the pool both read them), :Plan, :Artifact, :Inventory,
:Dispositions, :State, :Normalize, :Stream, :Prompt, :Config, and the URL
half of :PgWire. Units, jobs, results, the config and the claude stream
stay JSON from end to end, so every key keeps its place; :Text gives JSON
values truthiness, get, ==, membership and Str(), and throws Dyn::Error
under a name for each kind of unexpected data.
Exactness where the bytes are seen:
- the stream classifiers are hand-written scanners with case-insensitive
folds (the Kelvin sign is a k), Unicode \b and \s, and `.` stopping at
\n; the SQL call detection takes `<exe> sql` with any path before it;
- config problems carry the JSON scanner's and the UTF-8 decoder's own
texts, positions included;
- a float total is Neumaier's compensated sum, and the build passes
-ffp-contract=off: -march=native would fuse a*b+c;
- `crafter-review inventory` and `config` print what the goldens record,
byte for byte (the fixture and 14 configs, broken ones included); a
message names this binary as the command to run.
tests/Goldens: the recorded answers over the fixtures next to them (the
commits fixture, a rich versioned series with CRLF, a folded subject,
Markdown headings in and out of fences, the code-key rewrites); the suite
compares 2304 results as DumpFile writes them, so a key out of order fails
too. All agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:48:55 +01:00
|
|
|
// kept for the life of the process: the kernel releases it when the
|
|
|
|
|
// holder dies, and no child inherits it. The file is never unlinked: an
|
|
|
|
|
// unlink and re-create would hand two drivers one hash.
|
|
|
|
|
class RunLock {
|
|
|
|
|
public:
|
|
|
|
|
// Throws RunLocked naming the holder.
|
|
|
|
|
RunLock(const std::filesystem::path& gateDir, std::string_view mode, std::string_view hash8);
|
|
|
|
|
~RunLock();
|
|
|
|
|
RunLock(const RunLock&) = delete;
|
|
|
|
|
RunLock& operator=(const RunLock&) = delete;
|
|
|
|
|
// The holder's identity, once it knows its run number: one JSON
|
|
|
|
|
// document, truncated and rewritten.
|
|
|
|
|
void WriteInfo(const Json& info);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
std::int32_t fd = -1;
|
|
|
|
|
};
|
|
|
|
|
// Who holds this hash's run lock ({} while the holder has not written
|
|
|
|
|
// itself yet), nullopt when nobody does: a test or dry run takes no lock
|
|
|
|
|
// but numbers its scratch dir past a live holder.
|
|
|
|
|
std::optional<Json> LockHolder(const std::filesystem::path& gateDir, std::string_view mode, std::string_view hash8);
|
|
|
|
|
std::int64_t NextRunNumber(const std::filesystem::path& gateDir, std::string_view mode, std::string_view hash8);
|
|
|
|
|
|
|
|
|
|
// The usage of a set of pass records, summed in the order they come.
|
|
|
|
|
Json UsageTotals(std::span<const Json> records);
|
|
|
|
|
// The stdout summary of a run's dump.
|
|
|
|
|
std::string Summary(const Json& dump);
|
|
|
|
|
}
|