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.
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
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
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
`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,
};
/* 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 \
((size_t)LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES * \
(LARDON3D_ACQUISITION_CAMPAIGN_PATH_CAPACITY + 700u) + \
@ -41,6 +46,9 @@ Lardon3DTask *lardon3d_project_create_acquisition_campaign_task(
bool lardon3d_project_enqueue_acquisition_campaign(
Lardon3DAppState *state, uint64_t scanset_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(
const Lardon3DTaskDurableSnapshot *snapshot, void *context,
Lardon3DTaskKindBinding *binding);

View file

@ -78,7 +78,9 @@ Lardon3DTask *lardon3d_task_create_typed(
Lardon3DTaskUserdataDestroy userdata_destroy
);
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(
Lardon3DTask *task,
Lardon3DResourceGovernor *governor,
@ -137,6 +139,9 @@ bool lardon3d_task_resource_estimate(
const Lardon3DTask *task,
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(
const Lardon3DTask *task,
Lardon3DTaskExecutionContract *contract
@ -145,7 +150,8 @@ bool lardon3d_task_execution_contract(
* et met à jour le contrat. À appeler uniquement depuis le callback en cours
* d'exécution. Une réponse WAIT du gouverneur est une indisponibilité
* 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_FAILED). */
bool lardon3d_task_sequence_break(

View file

@ -28,6 +28,10 @@ Lardon3DTaskQueue *lardon3d_task_queue_create(
Lardon3DResourceGovernor *governor,
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);
/* La file devient propriétaire de task uniquement en cas de succès.
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 =
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 {
unsigned char *p;
size_t left;
@ -206,6 +209,8 @@ bool checkpoint(Context *c, Lardon3DTask *t, uint32_t cursor) {
bool run_impl(Lardon3DTask *t, void *p) {
auto *c = static_cast<Context *>(p);
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);
if (lardon3d_project_db_load_acquisition_campaign_task(
c->db, lardon3d_task_id(t), blob.data(), blob.size(), &persisted) !=
@ -218,6 +223,10 @@ bool run_impl(Lardon3DTask *t, void *p) {
return false;
Lardon3DProjectDbAcquisitionCampaignCapture retained{};
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(
c->db, lardon3d_task_id(t), group_id, &retained);
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,
group_id) != LARDON3D_PROJECT_DB_OK)
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
const char *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))
return lardon3d_task_fail(t, "Checkpoint de campagne impossible.");
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{};
Lardon3DResourceReservation *reservation = nullptr;
if (!lardon3d_task_sequence_break(t, c->governor, &reservation,
@ -352,6 +367,7 @@ bool request_decode_impl(
unsigned char m[8];
uint32_t version, n, c, rep, select;
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) ||
version != 1 || !q.u32(n) || n == 0 || n > sc ||
n > LARDON3D_ACQUISITION_CAMPAIGN_MAX_SOURCES || !q.u32(c) || c > cc ||
@ -490,6 +506,7 @@ Lardon3DTask *create_task_impl(
return t;
}
/* C ABI boundary: exceptions are trapped so callers never receive C++ throws. */
extern "C" bool lardon3d_acquisition_campaign_request_encode(
const Lardon3DAcquisitionCampaignTaskRequest *r, unsigned char *out,
size_t cap, size_t *size) {

View file

@ -141,6 +141,8 @@ enum class JpegValidationResult {
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) {
const int byte = std::fgetc(file);
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;
for (;;) {
unsigned char marker = 0u;
@ -283,6 +287,9 @@ JpegValidationResult validate_jpeg_image(FILE *file, bool soi_already_consumed,
}
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;
for (size_t image_index = 0u; image_index < JPEG_MAX_CONTAINER_IMAGES; ++image_index) {
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,
"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 {
int descriptor;
uint64_t keypoint_offset;

View file

@ -608,6 +608,8 @@ static const char schema_capture_provenance_v19[] =
"CHECK(parent_asset_id!=child_asset_id));";
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("
"task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
"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) {
/* 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,
"migrate acquisition campaign task v19 to v20");
#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) {
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,
NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, parameters,
updated_at);
@ -2292,6 +2298,11 @@ Lardon3DProjectDbResult lardon3d_project_db_load_acquisition_campaign_task(
Lardon3DProjectDbResult lardon3d_project_db_retain_acquisition_campaign_capture(
Lardon3DProjectDb *database, uint64_t task_id, uint32_t 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) ||
group_id == 0 || next_group_id != group_id) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
(void)pthread_mutex_lock(&database->mutex);

View file

@ -33,6 +33,8 @@ constexpr uint32_t kBackendKindLibRaw = 1;
constexpr uint32_t kEncoderKindOpenCvPng = 1;
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]) {
for (size_t i = 0; i < 4; ++i) if (!std::isfinite(wb[i]) || wb[i] <= 0.0F) return false;
return true;
@ -175,6 +177,8 @@ extern "C" Lardon3DRawDevelopmentResult lardon3d_raw_development_policy_fingerpr
return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR;
std::vector<unsigned char> bytes;
bytes.reserve(kRawFingerprintPayloadSize);
// Record all policy choices and relevant backend versions; decoder
// defaults must never create an untracked representation.
const char magic[] = "L3DRAWD1";
bytes.insert(bytes.end(), magic, magic + sizeof(magic) - 1);
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;
LibRaw raw;
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.use_camera_wb = 0; raw.imgdata.params.use_camera_matrix = 0;
raw.imgdata.params.output_profile = nullptr; raw.imgdata.params.camera_profile = nullptr;

View file

@ -220,6 +220,9 @@ lardon3d_task_start(
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;
if (!task || !lardon3d_resource_reservation_get_active(
governor,
@ -285,6 +288,10 @@ lardon3d_task_start(
copy_text(task->message, sizeof(task->message), "Tâche en cours.");
(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);
(void)pthread_mutex_lock(&task->mutex);
@ -563,6 +570,9 @@ lardon3d_task_sequence_break(
.gpu_slots = information.gpu_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->sequence_count;
*out_reservation = next;

View file

@ -20,6 +20,10 @@ static const unsigned char checkpoint_magic[8] = {
'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
put_u32(unsigned char *output, uint32_t value)
{
@ -288,6 +292,10 @@ lardon3d_task_checkpoint_save(
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)) {
return LARDON3D_TASK_CHECKPOINT_INVALID;
}

View file

@ -16,6 +16,10 @@ enum {
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 {
pthread_mutex_t mutex;
pthread_cond_t not_empty;
@ -61,7 +65,9 @@ unlink_pending(Lardon3DTaskQueue *queue, TaskNode *previous, TaskNode *node)
(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.
* 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. */
@ -154,6 +160,10 @@ static void *
queue_worker(void *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 (;;) {
(void)pthread_mutex_lock(&queue->mutex);
while (!queue->stopping && !queue->pending_head) {