feat(import): identity import API + FIFA17 real-profile dry-run importer

openfut-identity:
- insert_existing_mapping(game,kind,core_id,external_id): preserve an existing
  external wire id instead of minting; idempotent for an identical mapping,
  rejects conflicting forward/reverse with IdError::Conflict, persists atomically.
- persisted per-scope allocator watermark (set_watermark/watermark_for) so a
  future mint continues past the source high-water even across burned-id gaps;
  next id = max(base_floor, live_max+1, watermark). Backward-compatible on-disk
  format (legacy bare [Row] still loads). +4 tests (10 total).

openfut-import-fifa17 (new): read-only dry-run analysis of a real FIFA17 Python
profile for a faithful Core import. Enforces disjoint item-class balance;
proposes profile-derived CardDefinitions keyed fifa17_<resourceId> (base vs
versioned never collapse) with a resourceId-group consistency gate (hard-fail on
disagreement, never pick a winner) and honest buildability (roster name +
version formula + metadata, never fabricated); plans owned-instance identity
(preserve Python wire ids, preserve nextItemId watermark); checks active-squad
coverage. --apply/--emit-content refuse to write in this phase. 11 tests.

Real profile (33068179/CAGE) dry-run: 1982 items balance (1962 players + 17
consumables + 3 staff); 1681 supported defs (155 base + 1535 versioned), 9
NoName unsupported, 1 hard conflict (resourceId 169193: one of 4 copies has a
divergent nation/team/league); 1949 importable player instances, watermark
100004617 -> first new alloc 100004617; active squad f433 fully supported.
fmt + clippy -D warnings clean.
This commit is contained in:
funman300
2026-08-12 19:23:19 +00:00
parent 63f02c4fb1
commit a51947562c
8 changed files with 1380 additions and 9 deletions
+70
View File
@@ -0,0 +1,70 @@
//! `openfut-import-fifa17` — real FIFA 17 profile import.
//!
//! This phase implements the **read-only dry-run** only. `--apply` and
//! `--emit-content` are recognised but deliberately refuse to run (and exit
//! nonzero) so nothing is written before the apply/emission phase lands.
use anyhow::{bail, Context, Result};
use std::process::ExitCode;
fn main() -> ExitCode {
match run() {
Ok(code) => code,
Err(e) => {
eprintln!("import-fifa17: {e:#}");
ExitCode::FAILURE
}
}
}
fn print_help() {
eprintln!(
"openfut-import-fifa17 --profile <fifa17_profile.json> [--roster <roster.json>]\n\
\n\
Read-only dry-run: analyses a real FIFA 17 Python profile for a faithful\n\
OpenFUT Core import and reports item accounting, observed definitions,\n\
identity preservation, and squad coverage. Exits nonzero on unsafe blockers.\n\
\n\
--profile <path> (required) source fifa17_profile.json\n\
--roster <path> roster.json for player names (default fifa17-recon/data/roster.json)\n\
--apply REFUSED in this phase (no writes)\n\
--emit-content <dir> REFUSED in this phase (no generated content)\n"
);
}
fn run() -> Result<ExitCode> {
let mut profile_path: Option<String> = None;
let mut roster_path = "fifa17-recon/data/roster.json".to_string();
let mut args = std::env::args().skip(1);
while let Some(a) = args.next() {
match a.as_str() {
"--profile" => profile_path = Some(args.next().context("--profile needs a path")?),
"--roster" => roster_path = args.next().context("--roster needs a path")?,
"--dry-run" => {}
"--apply" => bail!(
"--apply is not implemented in this phase (dry-run + identity import API only); refusing to write the Core DB or identity store"
),
"--emit-content" => bail!(
"--emit-content is not implemented in this phase; refusing to write generated content"
),
"-h" | "--help" => {
print_help();
return Ok(ExitCode::SUCCESS);
}
other => bail!("unknown argument: {other} (try --help)"),
}
}
let profile_path = profile_path.context("--profile <path> is required (try --help)")?;
let profile = openfut_import_fifa17::load_profile(&profile_path)?;
let roster = openfut_import_fifa17::load_roster(&roster_path)?;
let report = openfut_import_fifa17::analyze(&profile, &roster);
print!("{report}");
if report.has_blockers() {
eprintln!("import-fifa17: DRY-RUN FAILED — unsafe blockers present (see above)");
Ok(ExitCode::FAILURE)
} else {
Ok(ExitCode::SUCCESS)
}
}