diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d9bb568c..850b1573 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -39,6 +39,8 @@ sections to include in release notes: ### Removed +- Schema version from restart checkpoint format; model version is sufficient (#338) + ### Git SHA ## **SIPNET 2.1.0 - "Nitrogen Cycle, Methane, and Restart"** diff --git a/docs/developer-guide/restart-checkpoint.md b/docs/developer-guide/restart-checkpoint.md index 59349432..766ddd72 100644 --- a/docs/developer-guide/restart-checkpoint.md +++ b/docs/developer-guide/restart-checkpoint.md @@ -22,11 +22,11 @@ On resume, SIPNET executes: 3. Validate compatibility checks and restart boundary checks 4. Continue run from resumed climate input -## Restart Schema v1.0 Overview +## Restart Checkpoint Overview Checkpoint format is ASCII text with one key/value per line: -- header: `SIPNET_RESTART 1.0` +- header: `SIPNET_RESTART` - metadata: `meta_info.model_version`, `meta_info.build_info`, `meta_info.checkpoint_utc_epoch`, `meta_info.processed_steps` - schema layout guard metadata: `schema_layout.envi_size`, `schema_layout.trackers_size`, `schema_layout.phenology_trackers_size`, `schema_layout.event_trackers_size` - mode flags: `flags.*` @@ -44,7 +44,6 @@ Example checkpoint content is exercised in On load, SIPNET enforces the following. Lines that start with (warning) log a warning and do not error. - magic header match -- schema version match - model numeric version match - `schema_layout.*` values exactly match the expected struct sizes for the running build - (warning) build info mismatch @@ -75,20 +74,20 @@ If you add saved state or change an existing saved payload: 1. Update the serialized payload type and restart read/write logic in `src/sipnet/restart.c`. 2. Update the `RESTART_SCHEMA_LAYOUT_*` constants, static asserts, and runtime schema-layout validation. -3. Update restart docs/tests and bump `RESTART_SCHEMA_VERSION`. +3. Update restart docs/tests. ## Struct Drift Guards -Restart schema v1.0 includes compile-time and runtime drift guards so struct layout changes cannot silently pass: +Restart checkpoints include compile-time and runtime drift guards so struct layout changes cannot silently pass: - Compile-time guards: `_Static_assert` checks in `src/sipnet/restart.c` for `Envi`, `Trackers`, `PhenologyTrackers`, `EventTrackers`, and expected number of model flags in `Context`. - Runtime guards: `schema_layout.*` fields in each checkpoint are validated on load. - Test guardrails: `tests/sipnet/test_restart_infrastructure/testRestartMVP.c` verifies schema layout keys are present and rejects tampered values. -## Schema Bump Checklist +## Schema Changes Checklist -When intentionally changing the restart schema version: +When intentionally changing the restart checkpoint format: -1. Update `src/sipnet/restart.c` in all schema touchpoints: `RESTART_SCHEMA_VERSION`, `RESTART_SCHEMA_LAYOUT_*`, `_Static_assert` layout guards, and checkpoint read/write + required-key validation logic. -2. Update restart examples/fixtures to the new header and key set, including the restart fixtures in `tests/sipnet/test_restart_infrastructure/testRestartMVP.c`. -3. Update docs that name schema version or key expectations: `docs/developer-guide/restart-checkpoint.md` and `docs/user-guide/running-sipnet.md`. +1. Update `src/sipnet/restart.c` in all schema touchpoints: `RESTART_SCHEMA_LAYOUT_*`, `_Static_assert` layout guards, and checkpoint read/write + required-key validation logic. +2. Update restart examples/fixtures to the new key set, including the restart fixtures in `tests/sipnet/test_restart_infrastructure/testRestartMVP.c`. +3. Update docs that name key expectations: `docs/developer-guide/restart-checkpoint.md` and `docs/user-guide/running-sipnet.md`. diff --git a/src/sipnet/restart.c b/src/sipnet/restart.c index 27ae7ccd..7f971934 100644 --- a/src/sipnet/restart.c +++ b/src/sipnet/restart.c @@ -15,7 +15,6 @@ #include "version.h" #define RESTART_MAGIC "SIPNET_RESTART" -#define RESTART_SCHEMA_VERSION "1.0" #define RESTART_FLOAT_EPSILON 1e-8 /* @@ -23,7 +22,6 @@ * - Number of elements of changed payload struct (#defs immediately below) * - RESTART_SCHEMA_LAYOUT_* constants and related runtime checks * - restart read/write logic, docs, and restart tests - * - RESTART_SCHEMA_VERSION */ #define MODEL_VERSION_BUFFER_SIZE 32 @@ -49,19 +47,19 @@ (8 * NUM_EVENT_TRACKERS_FIELDS) _Static_assert(sizeof(Envi) == RESTART_SCHEMA_LAYOUT_ENVI_SIZE, - "Restart schema 1.0 drift: Envi changed; bump restart schema " - "version and update schema_layout.* checks"); + "Restart schema drift: Envi changed; update schema_layout.* " + "checks"); _Static_assert(sizeof(Trackers) == RESTART_SCHEMA_LAYOUT_TRACKERS_SIZE, - "Restart schema 1.0 drift: serialized trackers payload changed; " - "bump restart schema version and update schema_layout.* checks"); -_Static_assert( - sizeof(PhenologyTrackers) == RESTART_SCHEMA_LAYOUT_PHENOLOGY_TRACKERS_SIZE, - "Restart schema 1.0 drift: PhenologyTrackers changed; bump restart " - "schema version and update schema_layout.* checks"); + "Restart schema drift: serialized trackers payload changed; " + "update schema_layout.* checks"); +_Static_assert(sizeof(PhenologyTrackers) == + RESTART_SCHEMA_LAYOUT_PHENOLOGY_TRACKERS_SIZE, + "Restart schema drift: PhenologyTrackers changed; update " + "schema_layout.* checks"); _Static_assert(sizeof(EventTrackers) == RESTART_SCHEMA_LAYOUT_EVENT_TRACKERS_SIZE, - "Restart schema 1.0 drift: EventTrackers changed; bump restart " - "schema version and update schema_layout.* checks"); + "Restart schema drift: EventTrackers changed; update " + "schema_layout.* checks"); #define NUM_CLIMATE_SIGNATURE_FIELDS 4 typedef struct RestartClimateSignature { @@ -91,8 +89,8 @@ typedef struct RestartContextModelFlags { static RestartContextModelFlags modelFlags; _Static_assert(sizeof(RestartContextModelFlags) == NUM_CONTEXT_MODEL_FLAGS * 4, - "Restart schema 1.0 drift: Model flags changed; bump restart " - "schema version and update schema_layout.* checks"); + "Restart schema drift: Model flags changed; update " + "schema_layout.* checks"); typedef enum StateFieldType { FT_LONGLONG = 0, @@ -387,10 +385,9 @@ static void validateSchemaLayoutValue(const char *restartIn, const char *key, const char *value, long long expected) { long long parsed = parseIntStrict(restartIn, key, value); if (parsed != expected) { - logError( - "Restart schema layout mismatch in %s: key=%s found=%lld expected=%lld " - "(schema %s)\n", - restartIn, key, parsed, expected, RESTART_SCHEMA_VERSION); + logError("Restart schema layout mismatch in %s: key=%s found=%lld " + "expected=%lld\n", + restartIn, key, parsed, expected); exit(EXIT_CODE_BAD_RESTART_PARAMETER); } } @@ -542,21 +539,13 @@ static void readRestartState(const char *restartIn, RestartState *state, } checkLineLength(firstLine, strlen(firstLine), restartIn, in); - char magic[64]; - char schemaVersion[16]; - if (sscanf(firstLine, "%63s %15s", magic, schemaVersion) != 2) { - parseError(restartIn, "invalid header line", NULL); - } + // Strip trailing newline/carriage return for exact comparison + firstLine[strcspn(firstLine, "\r\n")] = '\0'; - if (strcmp(magic, RESTART_MAGIC) != 0) { + if (strcmp(firstLine, RESTART_MAGIC) != 0) { logError("Restart file %s has invalid magic header\n", restartIn); exit(EXIT_CODE_BAD_RESTART_PARAMETER); } - if (strcmp(schemaVersion, RESTART_SCHEMA_VERSION) != 0) { - logError("Restart schema mismatch in %s: found %s expected %s\n", restartIn, - schemaVersion, RESTART_SCHEMA_VERSION); - exit(EXIT_CODE_BAD_RESTART_PARAMETER); - } int meanLength = meanNPP->length; int *seenMeanValues = (int *)calloc((size_t)meanLength, sizeof(int)); @@ -727,7 +716,7 @@ static void writeRestartState(const char *restartOut, const RestartState *state, FILE *out = openFile(restartOut, "w"); // Magic header - fprintf(out, "%s %s\n", RESTART_MAGIC, RESTART_SCHEMA_VERSION); + fprintf(out, "%s\n", RESTART_MAGIC); // Schema batches writeKeysBatch(out, state->metaPF, NUM_META_FIELDS); diff --git a/tests/sipnet/test_restart_infrastructure/testRestartMVP.c b/tests/sipnet/test_restart_infrastructure/testRestartMVP.c index 81909209..a40265e1 100644 --- a/tests/sipnet/test_restart_infrastructure/testRestartMVP.c +++ b/tests/sipnet/test_restart_infrastructure/testRestartMVP.c @@ -6,7 +6,7 @@ #include "utils/tUtils.h" #define CHECKPOINT_FILE "run.restart" -#define RESTART_MAGIC_LINE "SIPNET_RESTART 1.0" +#define RESTART_MAGIC_LINE "SIPNET_RESTART" #define MAX_LINE_LENGTH 1028 static int prepRunFiles(const char *climFile, const char *eventFile) { @@ -475,40 +475,6 @@ static int testModelVersionMismatchFails(void) { return status; } -static int testSchemaMismatchFails(void) { - int status = 0; - int stepStatus = 0; - int rc; - - logTest("Starting testSchemaMismatchFails\n"); - - runShell("rm -f run.out events.out run.restart *.log"); - - stepStatus = prepRunFiles("restart_segment1.clim", "events_segment1.in"); - if (stepStatus) { - logTest("Failed to prepare files for schema mismatch test segment 1\n"); - return stepStatus; - } - status |= (runModel("restart_seg1.in", "schema_mismatch_seg1.log") != 0); - status |= replaceFirstOccurrence(CHECKPOINT_FILE, "SIPNET_RESTART ", - "SIPNET_RESTART 1"); - - stepStatus = prepRunFiles("restart_segment2.clim", "events_segment2.in"); - if (stepStatus) { - logTest("Failed to prepare files for schema mismatch test segment 2\n"); - return status | stepStatus; - } - - rc = runModel("restart_seg2.in", "schema_mismatch_seg2.log"); - status |= (rc != EXIT_CODE_BAD_RESTART_PARAMETER); - - if (status) { - logTest("testSchemaMismatchFails failed (rc=%d)\n", rc); - } - - return status; -} - static int testSchemaLayoutMismatchFails(void) { int status = 0; int stepStatus = 0; @@ -762,7 +728,6 @@ int run(void) { status |= testRestartNotNearMidnightWarns(); status |= testMissingFinalNewlineCheckpointSucceeds(); status |= testModelVersionMismatchFails(); - status |= testSchemaMismatchFails(); status |= testSchemaLayoutMismatchFails(); status |= testMeanValueIndexOutOfRangeFails(); status |= testNonFiniteRestartValuesFail();