minor tweaks
All checks were successful
CI / lint (push) Successful in 16s
CI / fuzz (push) Successful in 2m3s
CI / test (push) Successful in 25s

This commit is contained in:
Kamal Tufekcic 2026-08-03 03:59:53 +03:00
commit 22ab973f0c
13 changed files with 223 additions and 144 deletions

View file

@ -84,9 +84,11 @@ enum Cmd {
},
/// The whole per-game build in ONE in-memory command: derive → fold → (if `--game-dir` is given)
/// validate-live + typed netvars → merge → fold model, writing the release set
/// (`rosetta-<game>.json` + `model-<game>.json` + `manifest.json`) into --out-dir. No per-stage
/// intermediate files. **Pass `--game-dir` for a full, live-validated build; omit it for a fast
/// OFFLINE build (no server, so no live validation and a `null` schema).**
/// (`rosetta-<game>.json` + `manifest.json`, plus `model-<game>.json` when `--corpus-model` was the
/// source — the sidecar is that model rolled N → N+1, so a `--corpus` genesis run writes two files,
/// not three) into --out-dir. No per-stage intermediate files. **Pass `--game-dir` for a full,
/// live-validated build; omit it for a fast OFFLINE build (no server, so no live validation and a
/// `null` schema).**
Produce {
/// A launchable game install → the FULL build (boots a server for validate-live + typed netvars).
/// OMIT for an offline build. The offline/full switch — no separate flag.
@ -98,12 +100,13 @@ enum Cmd {
/// Server library to derive from; defaults to the active game's server lib.
#[arg(long)]
lib: Option<String>,
/// One bundled seed (catalogue + naming sections) — the release form. Replaces the loose
/// --catalogue/--promotable/--candidates/--full-names/--extra-offsets/--extra-sigs flags.
/// One bundled seed (catalogue + naming sections) — the release form. Carries everything the loose
/// --catalogue/--promotable/--candidates/--full-names/--extra-offsets/--extra-sigs flags carry, and
/// CONFLICTS with each of them: pass one form or the other, never a mix.
#[arg(long)]
seed: Option<PathBuf>,
/// Function catalogue (loose form; omit when using --seed).
#[arg(long)]
#[arg(long, conflicts_with = "seed")]
catalogue: Option<PathBuf>,
/// Corpus-signal source A: the raw build binaries to fingerprint on the fly. Exactly ONE of
/// --corpus / --corpus-model is required (--corpus-model is the production forward-derive path).
@ -119,22 +122,22 @@ enum Cmd {
target: PathBuf,
/// Optional: names eligible for promotion into high_confidence (from the naming producer flow).
/// Omit to promote nothing — the catalogue still derives in full.
#[arg(long)]
#[arg(long, conflicts_with = "seed")]
promotable: Option<PathBuf>,
/// Optional: prefiltered per-address context for those names (`{"candidates": [...]}`). Omit for none.
#[arg(long)]
#[arg(long, conflicts_with = "seed")]
candidates: Option<PathBuf>,
/// Optional: the full-slice name universe. When set, the monolith also carries an `experimental`
/// tier — the least-filtered inclusion band (every name guess, graded, each with a resolvable
/// locator but an UNVERIFIED name).
#[arg(long)]
#[arg(long, conflicts_with = "seed")]
full_names: Option<PathBuf>,
/// Multilib ground-truth vtable offsets to fold as high_confidence — `{lib: [{name,class,slot}]}`
/// (e.g. the macOS symbol transfer). Folded directly, bypassing the candidate gate.
#[arg(long)]
#[arg(long, conflicts_with = "seed")]
extra_offsets: Option<PathBuf>,
/// Multilib non-virtual names to fold as sigs — `{lib: [{name,addr}]}`; `make_sig` runs per lib.
#[arg(long)]
#[arg(long, conflicts_with = "seed")]
extra_sigs: Option<PathBuf>,
/// Declared C++ prototypes (`mappings/prototypes.json`) to judge against this build's measured
/// register footprints. Static repo input — omit and no function carries a declared prototype.
@ -171,12 +174,12 @@ enum Cmd {
/// Distill the whole corpus into a shippable model (vtable-alignment hops + reference fingerprints
/// + slot timelines) so derivation needs only the model + the target binary, not the 86 GB corpus.
CorpusModel {
/// One bundled seed — the release form; its catalogue section is what gets distilled. Replaces the
/// loose --catalogue (naming sections are ignored here — the model tracks catalogue names only).
/// One bundled seed — the release form; its catalogue section is what gets distilled. CONFLICTS with
/// the loose --catalogue (naming sections are ignored here — the model tracks catalogue names only).
#[arg(long)]
seed: Option<PathBuf>,
/// Function catalogue (loose form; omit when using --seed).
#[arg(long)]
#[arg(long, conflicts_with = "seed")]
catalogue: Option<PathBuf>,
#[arg(long)]
corpus: PathBuf,
@ -195,11 +198,12 @@ enum Cmd {
/// The existing model N (carries the `abi_obs` window the fold re-windows).
#[arg(long)]
model: PathBuf,
/// One bundled seed — the release form; its catalogue section is folded. Replaces the loose --catalogue.
/// One bundled seed — the release form; its catalogue section is folded. CONFLICTS with the loose
/// --catalogue.
#[arg(long)]
seed: Option<PathBuf>,
/// Function catalogue (loose form; omit when using --seed). Must match the model's distill catalogue.
#[arg(long)]
#[arg(long, conflicts_with = "seed")]
catalogue: Option<PathBuf>,
/// The one new build dir to fold in (holds the just-updated libserver.so etc.).
#[arg(long)]
@ -309,9 +313,11 @@ fn lib_or_default(prof: &profile::GameProfile, lib: Option<String>) -> String {
}
/// Resolve the catalogue for the model commands (`corpus-model`/`fold-model`) from either a `--seed` bundle
/// (release form) or a loose `--catalogue` file. The seed's catalogue section parses to the same functions as
/// the loose `needed-functions.json`, so the distilled/folded model is identical either way. When a seed is
/// given, its sections unpack under a `.seed` dir beside `out` (as `produce` does beside its out-dir).
/// (release form) or a loose `--catalogue` file — never both; `catalogue` declares the conflict, so the
/// `None` arm here means the flag was genuinely absent. The seed's catalogue section parses to the same
/// functions as the loose `needed-functions.json`, so the distilled/folded model is identical either way.
/// When a seed is given, its sections unpack under a `.seed` dir beside `out` (as `produce` does beside
/// its out-dir).
fn model_catalogue(
prof: &profile::GameProfile,
seed: Option<PathBuf>,
@ -393,6 +399,9 @@ fn main() -> Result<()> {
bots,
} => {
// derive inputs come from a single --seed bundle (release form) or the loose flags (dev/verify).
// The bundle arm reads NONE of the loose bindings, which is only honest because each of them
// declares `conflicts_with = "seed"` — clap rejects the mix before dispatch rather than letting
// this arm drop an explicitly passed input on the floor.
let inputs = match seed {
Some(s) => unpack_seed(profile, &s, &out_dir.join(".seed"))?,
None => SeedInputs {
@ -511,3 +520,50 @@ fn main() -> Result<()> {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
/// A dropped input is a silent skip, and this is the one place the CLI could produce one: the `--seed`
/// arms unpack every loose input themselves and never read the loose bindings, so an undeclared
/// conflict means `produce --seed s.json --full-names f.json` runs to exit 0 with `--full-names`
/// ignored — and a monolith with no experimental tier is exactly what a game with no naming harvest
/// legitimately ships, so no collapse floor downstream can tell the two apart.
#[test]
fn a_seed_bundle_refuses_the_loose_inputs_rather_than_ignoring_them() {
Cli::command().debug_assert();
let parse = |argv: &[&str]| {
let full: Vec<&str> = std::iter::once("source2rosetta")
.chain(argv.iter().copied())
.collect();
Cli::try_parse_from(&full)
};
let refused = |argv: &[&str]| {
assert!(
parse(argv).is_err(),
"accepted, so one of these inputs is silently dropped: {argv:?}"
);
};
let produce = ["produce", "--target", "t", "--out-dir", "o", "--seed", "s"];
for flag in [
"--catalogue",
"--promotable",
"--candidates",
"--full-names",
"--extra-offsets",
"--extra-sigs",
] {
refused(&[&produce[..], &[flag, "x"]].concat());
}
let model = ["--out", "o", "--seed", "s", "--catalogue", "c"];
refused(&[&["corpus-model", "--corpus", "c"][..], &model].concat());
refused(&[&["fold-model", "--model", "m", "--build", "b"][..], &model].concat());
// Either form ALONE still parses — the conflict must not have made the loose form unusable.
assert!(parse(&produce).is_ok());
let loose = ["--catalogue", "c", "--full-names", "f"];
let bare = &produce[..produce.len() - 2]; // the same command minus `--seed s`
assert!(parse(&[bare, &loose[..]].concat()).is_ok());
}
}