docs: normalize source contract comments

This commit is contained in:
fy59 2026-08-28 06:51:24 +02:00
parent 40d9d27ba6
commit dae5ab2d08
13 changed files with 107 additions and 6 deletions

View file

@ -5,7 +5,7 @@
**ACCEPTED** — architecture decision for the post-Gate C documentation freeze. **ACCEPTED** — architecture decision for the post-Gate C documentation freeze.
Current Sparse SfM gates A through G are **PASS / FROZEN**. Current Sparse SfM gates A through G are **PASS / FROZEN**.
Project Database: current schema **v17**; historical v16 remains frozen. Project Database: current schema **v20**; historical v16 remains frozen.
This record is normative for the current architecture. It does not introduce This record is normative for the current architecture. It does not introduce
an implementation, a public API, a persistence format or a roadmap commitment. an implementation, a public API, a persistence format or a roadmap commitment.

View file

@ -72,6 +72,14 @@ int main(int argc, char **argv) {
6. **Déterminisme** : les tests ne dépendent pas de l'heure, du filesystem 6. **Déterminisme** : les tests ne dépendent pas de l'heure, du filesystem
ou de l'état réseau (sauf test d'import). ou de l'état réseau (sauf test d'import).
## Commentaires source
Les commentaires documentent le pourquoi et les contrats non évidents :
invariants, propriété et durée de vie, persistance, ainsi que limites et
frontières de ressources. Les API publiques documentent leurs contrats non
évidents. Ils ne paraphrasent pas le code ligne par ligne et sont mis à jour
avec tout changement de comportement.
## Tests unitaires vs tests d'intégration ## Tests unitaires vs tests d'intégration
`test-visual-index` couvre les descriptors synthétiques, le retrieval ORB réel, `test-visual-index` couvre les descriptors synthétiques, le retrieval ORB réel,

View file

@ -18,8 +18,13 @@ enum {
LARDON3D_ACQUISITION_CAMPAIGN_REQUEST_VERSION = 1, LARDON3D_ACQUISITION_CAMPAIGN_REQUEST_VERSION = 1,
}; };
/* Derived solely from the frozen source, path, confirmation and group bounds. /* Request wire payload is a deterministic codec, not a memcpy of in-memory structs:
*/ - fixed magic + version prefix
- fixed-width integer fields
- explicit little-endian byte order
- bounded text fields and counts
- strict decode-side validation rejecting malformed inputs.
No native struct layout is used for persistence compatibility. */
#define LARDON3D_ACQUISITION_CAMPAIGN_TASK_REQUEST_MAX_BYTES \ #define LARDON3D_ACQUISITION_CAMPAIGN_TASK_REQUEST_MAX_BYTES \
((size_t)LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES * \ ((size_t)LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES * \
(LARDON3D_ACQUISITION_CAMPAIGN_PATH_CAPACITY + 700u) + \ (LARDON3D_ACQUISITION_CAMPAIGN_PATH_CAPACITY + 700u) + \
@ -41,6 +46,9 @@ Lardon3DTask *lardon3d_project_create_acquisition_campaign_task(
bool lardon3d_project_enqueue_acquisition_campaign( bool lardon3d_project_enqueue_acquisition_campaign(
Lardon3DAppState *state, uint64_t scanset_id, Lardon3DAppState *state, uint64_t scanset_id,
const Lardon3DAcquisitionCampaignTaskRequest *request, uint64_t *task_id); const Lardon3DAcquisitionCampaignTaskRequest *request, uint64_t *task_id);
/* Request payload is treated as immutable durable input: task execution and
* checkpoint/recovery flow from the encoded blob, not from caller memory after
* enqueue. */
bool lardon3d_acquisition_campaign_task_reconstruct( bool lardon3d_acquisition_campaign_task_reconstruct(
const Lardon3DTaskDurableSnapshot *snapshot, void *context, const Lardon3DTaskDurableSnapshot *snapshot, void *context,
Lardon3DTaskKindBinding *binding); Lardon3DTaskKindBinding *binding);

View file

@ -78,7 +78,9 @@ Lardon3DTask *lardon3d_task_create_typed(
Lardon3DTaskUserdataDestroy userdata_destroy Lardon3DTaskUserdataDestroy userdata_destroy
); );
void lardon3d_task_destroy(Lardon3DTask *task); void lardon3d_task_destroy(Lardon3DTask *task);
/* Exécute le callback dans le thread appelant. */ /* Exécute le callback dans le thread appelant. Le callback est invoqué hors
* mutex de tâche; le contract d'exécution et l'état appartiennent à la tâche.
*/
bool lardon3d_task_start( bool lardon3d_task_start(
Lardon3DTask *task, Lardon3DTask *task,
Lardon3DResourceGovernor *governor, Lardon3DResourceGovernor *governor,
@ -137,6 +139,9 @@ bool lardon3d_task_resource_estimate(
const Lardon3DTask *task, const Lardon3DTask *task,
Lardon3DResourceEstimate *estimate Lardon3DResourceEstimate *estimate
); );
/* L'exécution ne reçoit pas de politique d'admission : c'est au gouverneur de
* confirmer la réservation avant l'exécution.
*/
bool lardon3d_task_execution_contract( bool lardon3d_task_execution_contract(
const Lardon3DTask *task, const Lardon3DTask *task,
Lardon3DTaskExecutionContract *contract Lardon3DTaskExecutionContract *contract
@ -145,7 +150,8 @@ bool lardon3d_task_execution_contract(
* et met à jour le contrat. À appeler uniquement depuis le callback en cours * et met à jour le contrat. À appeler uniquement depuis le callback en cours
* d'exécution. Une réponse WAIT du gouverneur est une indisponibilité * d'exécution. Une réponse WAIT du gouverneur est une indisponibilité
* temporaire : la fonction attend un changement de ressources puis retente * temporaire : la fonction attend un changement de ressources puis retente
* l'admission sans échouer la tâche. Retourne false si la tâche est annulée * l'admission sans échouer la tâche. Les bornes de lot se poursuivent après
* cette nouvelle admission. Retourne false si la tâche est annulée
* (TASK_CANCELLED), si le gouverneur répond REJECT ou en cas d'erreur interne * (TASK_CANCELLED), si le gouverneur répond REJECT ou en cas d'erreur interne
* (TASK_FAILED). */ * (TASK_FAILED). */
bool lardon3d_task_sequence_break( bool lardon3d_task_sequence_break(

View file

@ -28,6 +28,10 @@ Lardon3DTaskQueue *lardon3d_task_queue_create(
Lardon3DResourceGovernor *governor, Lardon3DResourceGovernor *governor,
size_t capacity size_t capacity
); );
/* File d'exécution bornée, à ordre d'attente FIFO avec sélection adaptative du
* premier travail admissible, et un seul worker: ownership d'ordonnancement et
* de backpressure seulement. L'admission des demandes reste au Governneur.
*/
void lardon3d_task_queue_destroy(Lardon3DTaskQueue *queue); void lardon3d_task_queue_destroy(Lardon3DTaskQueue *queue);
/* La file devient propriétaire de task uniquement en cas de succès. /* La file devient propriétaire de task uniquement en cas de succès.
Bloquante : attend une place libre si la file est pleine. */ Bloquante : attend une place libre si la file est pleine. */

View file

@ -18,6 +18,9 @@ constexpr unsigned char magic[8] = {'L', '3', 'D', 'A', 'C', 'T', '1', '\0'};
constexpr size_t max_request_size = constexpr size_t max_request_size =
LARDON3D_ACQUISITION_CAMPAIGN_TASK_REQUEST_MAX_BYTES; LARDON3D_ACQUISITION_CAMPAIGN_TASK_REQUEST_MAX_BYTES;
/* Codec is transport-safe and deterministic: no native struct serialization is
* used, integers are fixed-width with explicit endianness, and strings/counts are
* explicitly bounded before write/read. */
struct Writer { struct Writer {
unsigned char *p; unsigned char *p;
size_t left; size_t left;
@ -206,6 +209,8 @@ bool checkpoint(Context *c, Lardon3DTask *t, uint32_t cursor) {
bool run_impl(Lardon3DTask *t, void *p) { bool run_impl(Lardon3DTask *t, void *p) {
auto *c = static_cast<Context *>(p); auto *c = static_cast<Context *>(p);
Lardon3DProjectDbAcquisitionCampaignTask persisted{}; Lardon3DProjectDbAcquisitionCampaignTask persisted{};
// Request is reconstructed from persisted blob and treated as immutable durable
// input during recovery and replay.
std::vector<unsigned char> blob(max_request_size); std::vector<unsigned char> blob(max_request_size);
if (lardon3d_project_db_load_acquisition_campaign_task( if (lardon3d_project_db_load_acquisition_campaign_task(
c->db, lardon3d_task_id(t), blob.data(), blob.size(), &persisted) != c->db, lardon3d_task_id(t), blob.data(), blob.size(), &persisted) !=
@ -218,6 +223,10 @@ bool run_impl(Lardon3DTask *t, void *p) {
return false; return false;
Lardon3DProjectDbAcquisitionCampaignCapture retained{}; Lardon3DProjectDbAcquisitionCampaignCapture retained{};
uint64_t resume = 0; uint64_t resume = 0;
/* Durable replay uses the stable mapping (task_id, group_id) -> capture_id.
* The persisted cursor is the zero-based next-work position: after group N
* retention, its numeric value is N and replay materializes group N + 1.
*/
auto lr = lardon3d_project_db_load_acquisition_campaign_capture( auto lr = lardon3d_project_db_load_acquisition_campaign_capture(
c->db, lardon3d_task_id(t), group_id, &retained); c->db, lardon3d_task_id(t), group_id, &retained);
if (lr == LARDON3D_PROJECT_DB_OK) if (lr == LARDON3D_PROJECT_DB_OK)
@ -248,6 +257,10 @@ bool run_impl(Lardon3DTask *t, void *p) {
c->db, lardon3d_task_id(t), group_id, out.groups[0].capture_id, c->db, lardon3d_task_id(t), group_id, out.groups[0].capture_id,
group_id) != LARDON3D_PROJECT_DB_OK) group_id) != LARDON3D_PROJECT_DB_OK)
return lardon3d_task_fail(t, "Rétention de Capture impossible."); return lardon3d_task_fail(t, "Rétention de Capture impossible.");
/* Limite de reprise acceptée: entre le retour de S3-E et cette rétention
* durable, une identité de capture ne peut pas être déduite à posteriori
* depuis les chemins/métadonnées/ID d'image.
*/
#ifdef LARDON3D_ACQUISITION_CAMPAIGN_TASK_TESTING #ifdef LARDON3D_ACQUISITION_CAMPAIGN_TASK_TESTING
const char *after_retention = const char *after_retention =
std::getenv("LARDON3D_TEST_CAMPAIGN_FAIL_AFTER_RETENTION"); std::getenv("LARDON3D_TEST_CAMPAIGN_FAIL_AFTER_RETENTION");
@ -261,6 +274,8 @@ bool run_impl(Lardon3DTask *t, void *p) {
!checkpoint(c, t, group_id)) !checkpoint(c, t, group_id))
return lardon3d_task_fail(t, "Checkpoint de campagne impossible."); return lardon3d_task_fail(t, "Checkpoint de campagne impossible.");
if (group_id < c->plan.group_count) { if (group_id < c->plan.group_count) {
// sequence_break: libère la réservation courante et renégocie l'admission
// via le Governor avant le groupe suivant.
Lardon3DTaskExecutionContract contract{}; Lardon3DTaskExecutionContract contract{};
Lardon3DResourceReservation *reservation = nullptr; Lardon3DResourceReservation *reservation = nullptr;
if (!lardon3d_task_sequence_break(t, c->governor, &reservation, if (!lardon3d_task_sequence_break(t, c->governor, &reservation,
@ -352,6 +367,7 @@ bool request_decode_impl(
unsigned char m[8]; unsigned char m[8];
uint32_t version, n, c, rep, select; uint32_t version, n, c, rep, select;
uint64_t imported, maxbytes; uint64_t imported, maxbytes;
/* Version 1 is the only accepted codec shape; any mismatch rejects replay. */
if (!q.bytes(m, 8) || std::memcmp(m, magic, 8) || !q.u32(version) || if (!q.bytes(m, 8) || std::memcmp(m, magic, 8) || !q.u32(version) ||
version != 1 || !q.u32(n) || n == 0 || n > sc || version != 1 || !q.u32(n) || n == 0 || n > sc ||
n > LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES || !q.u32(c) || c > cc || n > LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES || !q.u32(c) || c > cc ||
@ -490,6 +506,7 @@ Lardon3DTask *create_task_impl(
return t; return t;
} }
/* C ABI boundary: exceptions are trapped so callers never receive C++ throws. */
extern "C" bool lardon3d_acquisition_campaign_request_encode( extern "C" bool lardon3d_acquisition_campaign_request_encode(
const Lardon3DAcquisitionCampaignTaskRequest *r, unsigned char *out, const Lardon3DAcquisitionCampaignTaskRequest *r, unsigned char *out,
size_t cap, size_t *size) { size_t cap, size_t *size) {

View file

@ -141,6 +141,8 @@ enum class JpegValidationResult {
constexpr size_t JPEG_MAX_CONTAINER_IMAGES = 8u; constexpr size_t JPEG_MAX_CONTAINER_IMAGES = 8u;
// Structural validation deliberately parses JPEG framing without decoding
// pixels, so metadata acceptance does not depend on decoder permissiveness.
bool read_byte(FILE *file, unsigned char &value) { bool read_byte(FILE *file, unsigned char &value) {
const int byte = std::fgetc(file); const int byte = std::fgetc(file);
if (byte == EOF) { if (byte == EOF) {
@ -234,6 +236,8 @@ JpegValidationResult validate_jpeg_image(FILE *file, bool soi_already_consumed,
} }
} }
// Entropy bytes are not marker payload: FF 00 is stuffing and RSTn stays
// within the scan. Only a real following marker resumes marker parsing.
bool in_entropy = false; bool in_entropy = false;
for (;;) { for (;;) {
unsigned char marker = 0u; unsigned char marker = 0u;
@ -283,6 +287,9 @@ JpegValidationResult validate_jpeg_image(FILE *file, bool soi_already_consumed,
} }
JpegValidationResult validate_jpeg_structure(FILE *file) { JpegValidationResult validate_jpeg_structure(FILE *file) {
// Secondary JPEGs are allowed only in the bounded private container form:
// primary APP2 contains MPF\0, every image reaches EOI structurally, and
// inter-image/final padding is zero only. No image pixels are decoded.
bool primary_has_mpf = false; bool primary_has_mpf = false;
for (size_t image_index = 0u; image_index < JPEG_MAX_CONTAINER_IMAGES; ++image_index) { for (size_t image_index = 0u; image_index < JPEG_MAX_CONTAINER_IMAGES; ++image_index) {
bool image_has_mpf = false; bool image_has_mpf = false;

View file

@ -17,6 +17,11 @@ static const unsigned char feature_magic[8] = {'L', '3', 'D', 'F', 'E', 'A', 'T'
_Static_assert(sizeof(float) == 4 && FLT_RADIX == 2 && FLT_MANT_DIG == 24 && FLT_MAX_EXP == 128, _Static_assert(sizeof(float) == 4 && FLT_RADIX == 2 && FLT_MANT_DIG == 24 && FLT_MAX_EXP == 128,
"Feature File v1/v2 exige IEEE-754 binary32."); "Feature File v1/v2 exige IEEE-754 binary32.");
/* L3DFEAT v1/v2 uses explicit fixed-width little-endian headers, never native
* structs. Counts, offsets, dimensions, source SHA-256, and parameter
* fingerprint bind descriptors to their input; malformed/truncated or unknown
* versions are rejected before feature data is consumed. */
struct Lardon3DFeatureReader { struct Lardon3DFeatureReader {
int descriptor; int descriptor;
uint64_t keypoint_offset; uint64_t keypoint_offset;

View file

@ -608,6 +608,8 @@ static const char schema_capture_provenance_v19[] =
"CHECK(parent_asset_id!=child_asset_id));"; "CHECK(parent_asset_id!=child_asset_id));";
static const char schema_acquisition_campaign_v20[] = static const char schema_acquisition_campaign_v20[] =
/* v20 is additive: the generic Task row owns runtime state, while the
typed row owns the immutable campaign request and durable cursor. */
"CREATE TABLE IF NOT EXISTS acquisition_campaign_tasks(" "CREATE TABLE IF NOT EXISTS acquisition_campaign_tasks("
"task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE," "task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
"scanset_id INTEGER NOT NULL REFERENCES scansets(scanset_id)," "scanset_id INTEGER NOT NULL REFERENCES scansets(scanset_id),"
@ -1107,6 +1109,8 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
} }
} }
if (result == LARDON3D_PROJECT_DB_OK && from_version < 20) { if (result == LARDON3D_PROJECT_DB_OK && from_version < 20) {
/* The additive v19->v20 DDL remains inside the surrounding migration
transaction; schema metadata advances only after this succeeds. */
result = execute(database, schema_acquisition_campaign_v20, result = execute(database, schema_acquisition_campaign_v20,
"migrate acquisition campaign task v19 to v20"); "migrate acquisition campaign task v19 to v20");
#ifdef LARDON3D_PROJECT_DB_TESTING #ifdef LARDON3D_PROJECT_DB_TESTING
@ -2247,6 +2251,8 @@ Lardon3DProjectDbResult lardon3d_project_db_record_acquisition_campaign_task(
!parameters->request || parameters->request_size == 0 || parameters->request_size > INT_MAX) { !parameters->request || parameters->request_size == 0 || parameters->request_size > INT_MAX) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
} }
/* The generic Task/checkpoint and typed request persist atomically: recovery
never observes a valid campaign Task without its immutable request blob. */
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint,
NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, parameters, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, parameters,
updated_at); updated_at);
@ -2292,6 +2298,11 @@ Lardon3DProjectDbResult lardon3d_project_db_load_acquisition_campaign_task(
Lardon3DProjectDbResult lardon3d_project_db_retain_acquisition_campaign_capture( Lardon3DProjectDbResult lardon3d_project_db_retain_acquisition_campaign_capture(
Lardon3DProjectDb *database, uint64_t task_id, uint32_t group_id, Lardon3DProjectDb *database, uint64_t task_id, uint32_t group_id,
uint64_t capture_id, uint32_t next_group_id) { uint64_t capture_id, uint32_t next_group_id) {
/* S3-E already returned capture_id. This transaction retains the group
mapping and advances the one-based cursor together; generic progress and
checkpoint advance afterwards. An interruption after S3-E returns and
before this retention transaction cannot safely be repaired by guessing
identity from paths, hashes, metadata, or image IDs. */
if (!database || !valid_task_id(task_id) || !valid_task_id(capture_id) || if (!database || !valid_task_id(task_id) || !valid_task_id(capture_id) ||
group_id == 0 || next_group_id != group_id) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; group_id == 0 || next_group_id != group_id) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
(void)pthread_mutex_lock(&database->mutex); (void)pthread_mutex_lock(&database->mutex);

View file

@ -33,6 +33,8 @@ constexpr uint32_t kBackendKindLibRaw = 1;
constexpr uint32_t kEncoderKindOpenCvPng = 1; constexpr uint32_t kEncoderKindOpenCvPng = 1;
constexpr size_t kRawFingerprintPayloadSize = 200; constexpr size_t kRawFingerprintPayloadSize = 200;
// RAW Policy v1 is part of derived scientific identity. Its fixed
// little-endian L3DRAWD1 payload is not a pathname-derived source identity.
bool valid_wb(const float wb[4]) { bool valid_wb(const float wb[4]) {
for (size_t i = 0; i < 4; ++i) if (!std::isfinite(wb[i]) || wb[i] <= 0.0F) return false; for (size_t i = 0; i < 4; ++i) if (!std::isfinite(wb[i]) || wb[i] <= 0.0F) return false;
return true; return true;
@ -175,6 +177,8 @@ extern "C" Lardon3DRawDevelopmentResult lardon3d_raw_development_policy_fingerpr
return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR; return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR;
std::vector<unsigned char> bytes; std::vector<unsigned char> bytes;
bytes.reserve(kRawFingerprintPayloadSize); bytes.reserve(kRawFingerprintPayloadSize);
// Record all policy choices and relevant backend versions; decoder
// defaults must never create an untracked representation.
const char magic[] = "L3DRAWD1"; const char magic[] = "L3DRAWD1";
bytes.insert(bytes.end(), magic, magic + sizeof(magic) - 1); bytes.insert(bytes.end(), magic, magic + sizeof(magic) - 1);
put_u32(&bytes, 1); put_u32(&bytes, kBackendKindLibRaw); put_u32(&bytes, 1); put_u32(&bytes, kBackendKindLibRaw);
@ -247,6 +251,9 @@ extern "C" Lardon3DRawDevelopmentResult lardon3d_raw_develop_to_capture(
if (!verify_managed_asset(managed_source, output->source_asset.sha256)) return LARDON3D_RAW_DEVELOPMENT_SOURCE_CHANGED; if (!verify_managed_asset(managed_source, output->source_asset.sha256)) return LARDON3D_RAW_DEVELOPMENT_SOURCE_CHANGED;
LibRaw raw; LibRaw raw;
raw.imgdata.params.user_qual = 3; raw.imgdata.params.half_size = 0; raw.imgdata.params.user_qual = 3; raw.imgdata.params.half_size = 0;
// The resolved WB is explicit. Automatic WB, brightness, and color
// choices are disabled so policy rather than hidden decoder state defines
// the derived asset.
raw.imgdata.params.user_flip = 0; raw.imgdata.params.use_auto_wb = 0; raw.imgdata.params.user_flip = 0; raw.imgdata.params.use_auto_wb = 0;
raw.imgdata.params.use_camera_wb = 0; raw.imgdata.params.use_camera_matrix = 0; raw.imgdata.params.use_camera_wb = 0; raw.imgdata.params.use_camera_matrix = 0;
raw.imgdata.params.output_profile = nullptr; raw.imgdata.params.camera_profile = nullptr; raw.imgdata.params.output_profile = nullptr; raw.imgdata.params.camera_profile = nullptr;

View file

@ -220,6 +220,9 @@ lardon3d_task_start(
const Lardon3DResourceReservation *reservation const Lardon3DResourceReservation *reservation
) )
{ {
/* Start assumes admission/contract was already decided by the queue/ governor.
* Task only installs the provided active reservation and owns execution state.
*/
Lardon3DResourceReservationInfo information; Lardon3DResourceReservationInfo information;
if (!task || !lardon3d_resource_reservation_get_active( if (!task || !lardon3d_resource_reservation_get_active(
governor, governor,
@ -285,6 +288,10 @@ lardon3d_task_start(
copy_text(task->message, sizeof(task->message), "Tâche en cours."); copy_text(task->message, sizeof(task->message), "Tâche en cours.");
(void)pthread_mutex_unlock(&task->mutex); (void)pthread_mutex_unlock(&task->mutex);
/* Callback is executed without task mutex held; caller-visible execution
* state transitions remain owned by task internals only.
* Finished callback is also issued after lock release.
*/
bool succeeded = task->callback(task, task->userdata); bool succeeded = task->callback(task, task->userdata);
(void)pthread_mutex_lock(&task->mutex); (void)pthread_mutex_lock(&task->mutex);
@ -563,6 +570,9 @@ lardon3d_task_sequence_break(
.gpu_slots = information.gpu_slots, .gpu_slots = information.gpu_slots,
.io_slots = information.io_slots, .io_slots = information.io_slots,
}; };
// sequence_break releases one active reservation and acquires the
// next one before returning; tasks should continue only from a
// cleanly admitted boundary.
task->has_contract = true; task->has_contract = true;
++task->sequence_count; ++task->sequence_count;
*out_reservation = next; *out_reservation = next;

View file

@ -20,6 +20,10 @@ static const unsigned char checkpoint_magic[8] = {
'L', '3', 'D', 'T', 'A', 'S', 'K', '\0' 'L', '3', 'D', 'T', 'A', 'S', 'K', '\0'
}; };
/* Checkpoint v1 is fixed-size and field-by-field little-endian: magic,
* version, size, checksum, then generic runtime state. Task-kind payloads,
* callbacks, locks, and reservations are intentionally transient and absent. */
static void static void
put_u32(unsigned char *output, uint32_t value) put_u32(unsigned char *output, uint32_t value)
{ {
@ -288,6 +292,10 @@ lardon3d_task_checkpoint_save(
const Lardon3DTaskDurableSnapshot *snapshot const Lardon3DTaskDurableSnapshot *snapshot
) )
{ {
/* The temporary file is synced before rename, then its parent directory is
* synced. Before rename the prior checkpoint remains authoritative; after
* rename a directory-sync failure is reported as published-not-durable,
* never as a rollbackable write. */
if (!path || !path[0] || !valid_snapshot(snapshot)) { if (!path || !path[0] || !valid_snapshot(snapshot)) {
return LARDON3D_TASK_CHECKPOINT_INVALID; return LARDON3D_TASK_CHECKPOINT_INVALID;
} }

View file

@ -16,6 +16,10 @@ enum {
LARDON3D_PENDING_RESOURCE_WAIT_MILLISECONDS = 500, LARDON3D_PENDING_RESOURCE_WAIT_MILLISECONDS = 500,
}; };
/* The queue owns submission order and bounded backpressure only.
* It does not own resource policy; every admissible decision comes from the
* Governor via reserve/evaluate calls.
*/
struct Lardon3DTaskQueue { struct Lardon3DTaskQueue {
pthread_mutex_t mutex; pthread_mutex_t mutex;
pthread_cond_t not_empty; pthread_cond_t not_empty;
@ -61,7 +65,9 @@ unlink_pending(Lardon3DTaskQueue *queue, TaskNode *previous, TaskNode *node)
(void)pthread_cond_signal(&queue->not_full); (void)pthread_cond_signal(&queue->not_full);
} }
/* Parcourt la file d'attente et sélectionne la première tâche admissible. /* Parcourt la file d'attente dans son ordre FIFO et sélectionne la première
* tâche admissible; une attente de ressources peut donc laisser passer une
* tâche antérieure sans lui faire perdre sa place dans la file.
* Les tâches terminales ou refusées sont retirées de la file d'attente. * Les tâches terminales ou refusées sont retirées de la file d'attente.
* Une tâche en attente de ressources reste en file et sera réévaluée. * Une tâche en attente de ressources reste en file et sera réévaluée.
* Retourne NULL si aucune tâche ne peut démarrer immédiatement. */ * Retourne NULL si aucune tâche ne peut démarrer immédiatement. */
@ -154,6 +160,10 @@ static void *
queue_worker(void *context) queue_worker(void *context)
{ {
Lardon3DTaskQueue *queue = context; Lardon3DTaskQueue *queue = context;
/* Single worker only; execution is serialized by design. Queue preserves
* pending FIFO order with adaptive dispatch/backpressure; Governor decides
* admission.
*/
for (;;) { for (;;) {
(void)pthread_mutex_lock(&queue->mutex); (void)pthread_mutex_lock(&queue->mutex);
while (!queue->stopping && !queue->pending_head) { while (!queue->stopping && !queue->pending_head) {