feat: complete heterogeneous calibration workflow

This commit is contained in:
fy59 2026-09-03 13:20:22 +02:00
parent a77d095a85
commit 038cc64032
13 changed files with 898 additions and 33 deletions

View file

@ -11,7 +11,7 @@ REAL_S21_TRACKS=PASS/FROZEN
REAL_A6000_PRE_SFM=PASS/FROZEN
PRODUCT_DEFINITION_V1=PASS/FROZEN
PROMPT_TREE=CURRENT
CURRENT_NEXT=CALIBRATION_V2_WORKFLOW_READY
CURRENT_NEXT=AUTOFOCUS_V2_FOUNDATION
```
This index separates current authority, historical evidence and future product-definition work.

View file

@ -82,8 +82,36 @@ Bootstrap validates the whole artifact and then verifies every entry against
the immutable selected execution's exact item index, `image_id`, and current
representation asset SHA-256 before its first write. It creates or reuses the
existing content-addressed Sparse calibrations per entry, creates or reuses one
scope over every selected image across all groups, and attaches only that
complete scope. A pre-publication error attaches no scope. A later failure may
retain immutable calibration evidence but cannot make the execution READY.
Exact retries reuse calibrations and the scope and converge on the same
attachment.
scope over every selected image across all groups. The original Bootstrap v2
import attaches that complete scope and retains its existing public behavior.
The separately named `publish_unattached` primitive returns the exact
selected-item/image/calibration mapping without attaching the scope; it exists
only so Workflow v2 can establish v26 compatibility before READY. A
pre-publication error attaches no scope. A later failure may retain immutable
calibration evidence but cannot make the execution READY. Exact retries reuse
calibrations and the scope and converge on the same attachment.
## Heterogeneous Workflow v2 composition
Workflow v2 consumes the L3DCALB2 artifact and a complete caller-owned binding
for every selected item. Each binding repeats the selected execution's durable
`item_index -> capture_id` relation. Capture identity is never recovered from
an image ID, path, SHA-256, filename, artifact group identity, or numeric group
ID.
Before unattached publication, Workflow v2 requires complete observed v26
geometric state for every selected Capture. Automatic resolution reports zero
exact candidates as `CALIBRATION_REQUIRED` and multiple exact candidates as
`SELECTION_REQUIRED`. Explicit existing applicability must be exactly
compatible. Explicit publication creates/reuses deterministic profile metadata
from the immutable artifact calibration and its group-local provenance, then
creates/reuses applicability from the declared exact-state exemplar. It does
not create scientific Capture identity or authorize interpolation.
For every selected item, the final resolved v26 sparse `calibration_id` must
equal that image's calibration member in the complete unattached scope. Only
after all items pass that equality does Workflow v2 attach the scope and return
`READY`. Invalid evidence or a wrong assignment is a distinct non-ready error.
Any earlier failure leaves the selected execution unattached; exact retries are
deterministic and converge through the immutable Bootstrap/profile/
applicability/selection APIs. No Project DB schema change is required.

View file

@ -8,7 +8,8 @@ CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN
CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN
CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1=PASS/FROZEN
CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY_V1=PASS/FROZEN
CURRENT_WORKFLOW_NEXT=DEDICATED_PHYSICAL_CALIBRATED_REAL_CAMPAIGN
CALIBRATION_WORKFLOW_V2=PASS/FROZEN
CURRENT_WORKFLOW_NEXT=ADAPTIVE_CAPTURE_SETTINGS_AND_AUTOFOCUS_V2_FOUNDATION
```
## Authority
@ -201,6 +202,32 @@ The software workflow is PASS/FROZEN. It does not establish physical evidence:
historical S21/A6000 campaigns remain CALIBRATION_UNAVAILABLE and
BLOCKED_BY_KNOWN_CALIBRATION_DATA.
## Additive heterogeneous Workflow v2
`CALIBRATION_WORKFLOW_V2=PASS/FROZEN`. This additive composition leaves every
v1 API, artifact and workflow meaning unchanged. It composes v26 Capture
geometric state/applicability with the L3DCALB2 publication path for one
heterogeneous selected execution.
The workflow first verifies the exact durable selected-item-to-Capture mapping
for every caller binding. It never recovers Capture identity from an image ID,
path, SHA-256, filename or operational group ID. Every Capture must have a
complete observed geometric state. Missing or incomplete state reports
`CALIBRATION_REQUIRED`; multiple exact compatible applicability candidates
report `SELECTION_REQUIRED`.
L3DCALB2 is first published through its additive unattached primitive. This
fully validates artifact bytes and selected image/representation bindings and
may create reusable immutable calibrations and a complete scope, but cannot
transition the execution to READY. Workflow v2 then binds each returned
per-image calibration to an exact v26 applicability/selection and verifies the
resolved `sparse_calibration_id` equals that scope member. Only after every
selected item passes may the existing scope-attachment transaction set READY.
Invalid artifact evidence and wrong optical assignments are distinct non-ready
errors. No pre-final failure attaches a scope. Exact retries reuse immutable
calibrations and the same complete scope deterministically.
## Current next boundary
```text

View file

@ -24,7 +24,7 @@ SOURCE_COMMENT_AUDIT PASS
PRODUCT_DEFINITION PASS/FROZEN
PROMPT_TREE CURRENT
USER_FACING_UI_LANGUAGE_NORMALIZATION PASS
CURRENT_NEXT CALIBRATION_V2_WORKFLOW_READY
CURRENT_NEXT AUTOFOCUS_V2_FOUNDATION
```
The current Project DB head is additive:
@ -537,8 +537,10 @@ applicability while retaining that per-image scope model. Unknown state remains
calibration publication. `CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION=PASS/FROZEN`:
additive L3DCALB2/Tooling/Bootstrap v2 now retains independent group-local provenance, validates
complete exact selected-image coverage, and attaches one existing per-image scope only after complete
publication. The next dependency is heterogeneous Workflow v2 truthful READY. Device-specific
autofocus envelopes remain blocked until physical evidence validates them.
publication. `CALIBRATION_WORKFLOW_V2=PASS/FROZEN`: the workflow now proves exact Capture/state
applicability and matching per-image calibration IDs before its sole final scope attachment. The next
dependency is the generic adaptive-settings/autofocus foundation. Device-specific autofocus envelopes
remain blocked until physical evidence validates them.
Calibration Tooling v1 consumes an already acquired Science v1 evidence bundle, validates the bounded
contract and produces deterministic `L3DCALB1` v1.

View file

@ -35,6 +35,33 @@ typedef struct {
uint32_t group_count;
} Lardon3DCalibrationBootstrapV2Output;
typedef struct {
uint32_t selected_item_index;
uint64_t image_id;
uint64_t calibration_id;
} Lardon3DCalibrationBootstrapV2Member;
/* Validate and publish one complete artifact without attaching its scope.
* `members` is caller-owned storage of at least V2_MAX_ENTRIES only when that
* many entries are accepted; `member_capacity` may be smaller and yields
* INVALID_ARGUMENT before database mutation when the artifact will not fit.
* Successful members are ordered by selected_item_index and expose the exact
* immutable calibration created/reused for each selected image.
*
* This primitive exists so Workflow v2 can establish exact Capture-owned v26
* applicability and selection before the single READY attachment. It never
* infers Capture identity from image, path, digest, or group identity, and it
* never attaches the returned complete scope. Exact retry is deterministic;
* immutable calibrations and the unattached scope may survive later workflow
* failure under the existing publication contract. */
Lardon3DCalibrationBootstrapV2Result
lardon3d_calibration_bootstrap_v2_publish_unattached(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char expected_artifact_sha256[LARDON3D_PROJECT_DB_SHA256_SIZE],
Lardon3DCalibrationBootstrapV2Member *members, size_t member_capacity,
Lardon3DCalibrationBootstrapV2Output *output);
/* Import one complete L3DCALB2 artifact for an immutable selected execution.
* All pointers are required and borrowed only for the call; artifact_size is
* bounded by V2_MAX_BYTES and must match expected_artifact_sha256 before any

View file

@ -0,0 +1,80 @@
#ifndef LARDON3D_CALIBRATION_WORKFLOW_V2_H
#define LARDON3D_CALIBRATION_WORKFLOW_V2_H
#include <stddef.h>
#include <stdint.h>
#include <lardon3d/calibration_bootstrap_v2.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC = 1,
LARDON3D_CALIBRATION_WORKFLOW_V2_EXISTING_EXPLICIT = 2,
LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT = 3,
} Lardon3DCalibrationWorkflowV2BindingKind;
typedef struct {
uint32_t selected_item_index;
/* Capture identity must be copied from the selected execution's durable item
* mapping. Workflow rejects any mismatch and never derives it from image_id,
* artifact group number, path, filename, or SHA-256. */
uint64_t capture_id;
Lardon3DCalibrationWorkflowV2BindingKind kind;
/* EXISTING_EXPLICIT requires an existing exact v26 applicability. */
uint64_t applicability_id;
/* PUBLISH_EXPLICIT uses this Capture's complete observed tuple as the exact
* applicability exemplar. It may equal capture_id. Other modes require 0. */
uint64_t exemplar_capture_id;
} Lardon3DCalibrationWorkflowV2Binding;
typedef enum {
LARDON3D_CALIBRATION_WORKFLOW_V2_READY = 0,
LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED,
LARDON3D_CALIBRATION_WORKFLOW_V2_SELECTION_REQUIRED,
LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_ARGUMENT,
LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_EVIDENCE,
LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT,
LARDON3D_CALIBRATION_WORKFLOW_V2_DB_ERROR,
LARDON3D_CALIBRATION_WORKFLOW_V2_OUT_OF_MEMORY,
} Lardon3DCalibrationWorkflowV2Result;
typedef struct {
Lardon3DCalibrationBootstrapV2Output publication;
uint32_t selected_item_count;
} Lardon3DCalibrationWorkflowV2Output;
/* Compose heterogeneous L3DCALB2 publication with exact Project DB v26
* applicability and the final selected-execution READY transition.
*
* All pointers are borrowed for the call. `bindings` must cover every selected
* item exactly once and must repeat the durable selected item -> Capture
* mapping. AUTOMATIC accepts exactly one compatible applicability and reports
* CALIBRATION_REQUIRED/SELECTION_REQUIRED truthfully for zero/multiple choices.
* EXISTING_EXPLICIT verifies and durably selects the requested exact
* applicability. PUBLISH_EXPLICIT creates/reuses deterministic optical profile
* metadata for the artifact calibration, creates/reuses exact exemplar
* applicability, and selects it explicitly.
*
* READY is returned only when every durable Capture resolves to the same
* calibration_id as its complete scope member and that exact scope is attached.
* Invalid evidence or an assignment mismatch is a distinct non-ready error.
* Pre-attachment failure never attaches a scope; immutable publication and
* optical evidence created by an earlier phase may remain and exact retry
* converges. No schema change, solver execution, or Capture inference occurs.
*/
Lardon3DCalibrationWorkflowV2Result lardon3d_calibration_workflow_v2_complete(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char
expected_artifact_sha256[LARDON3D_PROJECT_DB_SHA256_SIZE],
const Lardon3DCalibrationWorkflowV2Binding *bindings, size_t binding_count,
Lardon3DCalibrationWorkflowV2Output *output);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -180,6 +180,7 @@ lardon3d_app = executable(
'src/optical_profiles.c',
'src/calibration_bootstrap.c',
'src/calibration_bootstrap_v2.c',
'src/calibration_workflow_v2.c',
'src/calibration_tooling.c',
'src/calibration_tooling_v2.c',
'src/calibration_workflow.cpp',
@ -916,6 +917,23 @@ calibration_publication_v2_test = executable(
test('calibration-publication-v2', calibration_publication_v2_test, timeout: 30)
calibration_workflow_v2_test = executable(
'test-calibration-workflow-v2',
sources: [
'tests/test_calibration_workflow_v2.c',
'src/calibration_workflow_v2.c',
'src/calibration_tooling_v2.c', 'src/calibration_bootstrap_v2.c',
'src/optical_profiles.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/resource_governor.c', 'src/resource_snapshot.c',
],
c_args: ['-DLARDON3D_PROJECT_DB_TESTING'],
include_directories: include_directories('include'),
dependencies: [threads, sqlite3, openssl, cc.find_library('m')],
)
test('calibration-workflow-v2', calibration_workflow_v2_test, timeout: 30)
calibration_workflow_test = executable(
'test-calibration-workflow',
sources: [

View file

@ -6,7 +6,7 @@
CURRENT_PROJECT_DB_SCHEMA=v26
PRODUCTION_TASK_KINDS=16
USER_FACING_UI_LANGUAGE_NORMALIZATION=PASS
CURRENT_IMPLEMENTATION_CURSOR=CALIBRATION_V2_WORKFLOW_READY
CURRENT_IMPLEMENTATION_CURSOR=ADAPTIVE_CAPTURE_SETTINGS_AND_AUTOFOCUS_V2_FOUNDATION
```
## Authority
@ -38,6 +38,7 @@ Calibration workflow Tooling/Bootstrap READY PASS/FROZEN
Calibration Science v2 heterogeneous optics PASS/FROZEN
Calibration v2 optical-state foundation PASS/FROZEN
Calibration v2 heterogeneous publication PASS/FROZEN
Calibration Workflow v2 PASS/FROZEN
Adaptive capture settings semantics PLANNED
Autofocus v2 foundation PLANNED
Calibration Tooling planarity alignment PASS/FROZEN

View file

@ -20,10 +20,10 @@ CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY_V1=PASS/FROZEN
CALIBRATION_SCIENCE_V2=PASS/FROZEN
CALIBRATION_V2_HETEROGENEOUS_OPTICS=PASS/FROZEN
CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION=PASS/FROZEN
CALIBRATION_V2_WORKFLOW_READY=PLANNED
CALIBRATION_V2_WORKFLOW_READY=PASS/FROZEN
ADAPTIVE_CAPTURE_SETTINGS_CONTRACT=PLANNED
AUTOFOCUS_V2_FOUNDATION=PLANNED
CURRENT_CALIBRATION_NEXT=CALIBRATION_V2_WORKFLOW_READY
CURRENT_CALIBRATION_NEXT=AUTOFOCUS_V2_FOUNDATION
```
## Authority

View file

@ -6,7 +6,7 @@
IMPLEMENTATION_ORDER=DEPENDENCY_DRIVEN
IMPLEMENTATION_AUTHORIZATION=NO
STEP_0_USER_FACING_LANGUAGE_NORMALIZATION=PASS
CURRENT_NEXT=CALIBRATION_V2_WORKFLOW_READY
CURRENT_NEXT=AUTOFOCUS_V2_FOUNDATION
```
## Authority
@ -24,23 +24,24 @@ Default dependency order:
2. Calibration Science v2 design for heterogeneous cameras/lenses/focals, adaptive capture settings and autofocus — PASS/FROZEN;
3. Calibration v2 heterogeneous-optics persistence foundation — PASS/FROZEN;
4. Heterogeneous calibration publication / Tooling / Bootstrap evolution — PASS/FROZEN;
5. Heterogeneous Workflow v2 truthful READY proof — CURRENT;
6. physical autofocus/optical applicability validation and dedicated calibrated real campaign;
7. real Sparse SfM proof;
8. durable Dense/OpenMVS orchestration;
9. mesh / refinement / texturing / export;
10. viewer foundation;
11. offline Coverage Analysis scientific contract and implementation;
12. multi-campaign registration / fusion;
13. generic live acquisition adapter foundation;
14. A6000 HDMI integration;
15. S21 integration;
16. live camera localization;
17. live coverage overlay;
18. actionable Capture Guidance;
19. video ingestion / deterministic keyframes;
20. final integration, UX, restart and performance proof;
21. Product Definition v1 Definition-of-Done closure.
5. Heterogeneous Workflow v2 truthful READY proof — PASS/FROZEN;
6. Adaptive capture settings / generic autofocus foundation — CURRENT;
7. physical autofocus/optical applicability validation and dedicated calibrated real campaign;
8. real Sparse SfM proof;
9. durable Dense/OpenMVS orchestration;
10. mesh / refinement / texturing / export;
11. viewer foundation;
12. offline Coverage Analysis scientific contract and implementation;
13. multi-campaign registration / fusion;
14. generic live acquisition adapter foundation;
15. A6000 HDMI integration;
16. S21 integration;
17. live camera localization;
18. live coverage overlay;
19. actionable Capture Guidance;
20. video ingestion / deterministic keyframes;
21. final integration, UX, restart and performance proof;
22. Product Definition v1 Definition-of-Done closure.
## Adjustment rule

View file

@ -79,10 +79,12 @@ static Lardon3DCalibrationBootstrapV2Result db_result(Lardon3DProjectDbResult re
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR;
}
Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
static Lardon3DCalibrationBootstrapV2Result bootstrap_v2_publish(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char expected_artifact_sha256[32],
Lardon3DCalibrationBootstrapV2Member *published_members,
size_t member_capacity, bool attach_scope,
Lardon3DCalibrationBootstrapV2Output *output) {
if (!database || execution_id == 0 || !artifact || !expected_artifact_sha256 || !output ||
artifact_size < BOOTSTRAP_V2_HEADER_SIZE ||
@ -109,6 +111,8 @@ Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
group_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_GROUPS || entry_count == 0 ||
entry_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES)
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
if (!attach_scope && (!published_members || member_capacity < entry_count))
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_INVALID_ARGUMENT;
size_t expected_size = BOOTSTRAP_V2_HEADER_SIZE + (size_t)group_count * BOOTSTRAP_V2_GROUP_SIZE;
if (entry_count > (SIZE_MAX - expected_size) / BOOTSTRAP_V2_ENTRY_SIZE)
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
@ -295,7 +299,7 @@ Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
if (db_status != LARDON3D_PROJECT_DB_OK) result = db_result(db_status);
}
}
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK && attach_scope) {
db_status = lardon3d_project_db_assign_selected_calibration_scope(database, execution_id,
scope.scope_id);
if (db_status != LARDON3D_PROJECT_DB_OK) result = db_result(db_status);
@ -305,8 +309,42 @@ Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
output->scope = scope;
output->calibration_count = entry_count;
output->group_count = group_count;
if (!attach_scope) {
/* Item order, not artifact group order, is the durable composition key.
* Workflow must bind it back to selected_execution_items.capture_id and
* must never reverse-map Capture identity from image_id. */
for (size_t index = 0; index < entry_count; ++index) {
BootstrapV2Entry *entry = &entries[index];
Lardon3DCalibrationBootstrapV2Member *member =
&published_members[entry->selected_item_index];
member->selected_item_index = entry->selected_item_index;
member->image_id = entry->member.image_id;
member->calibration_id = entry->member.calibration_id;
}
}
}
free(entries);
free(covered);
return result;
}
Lardon3DCalibrationBootstrapV2Result
lardon3d_calibration_bootstrap_v2_publish_unattached(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char expected_artifact_sha256[32],
Lardon3DCalibrationBootstrapV2Member *members, size_t member_capacity,
Lardon3DCalibrationBootstrapV2Output *output) {
return bootstrap_v2_publish(database, execution_id, artifact, artifact_size,
expected_artifact_sha256, members,
member_capacity, false, output);
}
Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char expected_artifact_sha256[32],
Lardon3DCalibrationBootstrapV2Output *output) {
return bootstrap_v2_publish(database, execution_id, artifact, artifact_size,
expected_artifact_sha256, NULL, 0, true, output);
}

View file

@ -0,0 +1,301 @@
#include <lardon3d/calibration_workflow_v2.h>
#include <lardon3d/optical_profiles.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static bool complete_state(const Lardon3DOpticalCaptureGeometricState *state) {
return state->focus_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
state->aperture_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
state->stabilization != LARDON3D_OPTICAL_STABILIZATION_UNKNOWN &&
state->crop_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
state->pipeline_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
state->representation_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
state->decoded_geometry_state ==
LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
state->decoded_width != 0 && state->decoded_height != 0;
}
static bool same_state(const Lardon3DOpticalCaptureGeometricState *left,
const Lardon3DOpticalCaptureGeometricState *right) {
return left->optical_configuration_id == right->optical_configuration_id &&
left->state_version == right->state_version &&
left->provenance == right->provenance &&
left->focus_state == right->focus_state &&
strcmp(left->focus_observation, right->focus_observation) == 0 &&
left->aperture_state == right->aperture_state &&
left->aperture_x1000 == right->aperture_x1000 &&
left->stabilization == right->stabilization &&
left->crop_state == right->crop_state &&
strcmp(left->crop_observation, right->crop_observation) == 0 &&
left->pipeline_state == right->pipeline_state &&
strcmp(left->pipeline_observation, right->pipeline_observation) == 0 &&
left->representation_state == right->representation_state &&
strcmp(left->representation_observation,
right->representation_observation) == 0 &&
left->decoded_geometry_state == right->decoded_geometry_state &&
left->decoded_width == right->decoded_width &&
left->decoded_height == right->decoded_height;
}
static Lardon3DCalibrationWorkflowV2Result
bootstrap_result(Lardon3DCalibrationBootstrapV2Result result) {
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT ||
result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_PROVENANCE_MISMATCH)
return LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_EVIDENCE;
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT)
return LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OUT_OF_MEMORY)
return LARDON3D_CALIBRATION_WORKFLOW_V2_OUT_OF_MEMORY;
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_INVALID_ARGUMENT)
return LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_ARGUMENT;
return LARDON3D_CALIBRATION_WORKFLOW_V2_DB_ERROR;
}
static Lardon3DCalibrationWorkflowV2Result
db_error(Lardon3DProjectDbResult result) {
return result == LARDON3D_PROJECT_DB_CONSTRAINT ||
result == LARDON3D_PROJECT_DB_NOT_FOUND
? LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT
: LARDON3D_CALIBRATION_WORKFLOW_V2_DB_ERROR;
}
static void hex_digest(const unsigned char digest[32], char output[65]) {
static const char digits[] = "0123456789abcdef";
for (size_t index = 0; index < 32; ++index) {
output[2 * index] = digits[digest[index] >> 4];
output[2 * index + 1] = digits[digest[index] & 15];
}
output[64] = '\0';
}
static Lardon3DCalibrationWorkflowV2Result
publish_applicability(Lardon3DProjectDb *database,
const Lardon3DCalibrationWorkflowV2Binding *binding,
uint64_t calibration_id, uint64_t configuration_id,
uint64_t *applicability_id) {
Lardon3DSparseCalibration calibration;
Lardon3DProjectDbResult status =
lardon3d_sparse_calibration_load(database, calibration_id, &calibration);
if (status != LARDON3D_PROJECT_DB_OK)
return db_error(status);
char scientific_hash[65];
char provenance_hash[65];
hex_digest(calibration.scientific_hash, scientific_hash);
hex_digest(calibration.provenance_fingerprint, provenance_hash);
Lardon3DOpticalCalibrationProfile requested = {0};
requested.optical_configuration_id = configuration_id;
requested.sparse_calibration_id = calibration_id;
requested.profile_version =
LARDON3D_CALIBRATION_BOOTSTRAP_V2_ARTIFACT_VERSION;
requested.applicability = LARDON3D_OPTICAL_CALIBRATION_EXACT_CONFIGURATION;
requested.created_at = 0;
(void)snprintf(requested.name, sizeof(requested.name), "L3DCALB2-%s",
scientific_hash);
(void)snprintf(requested.provenance, sizeof(requested.provenance),
"L3DCALB2 group-sha256=%s", provenance_hash);
Lardon3DOpticalCalibrationProfile profile;
status = lardon3d_optical_calibration_profile_create(database, &requested,
&profile);
if (status != LARDON3D_PROJECT_DB_OK)
return db_error(status);
Lardon3DOpticalCalibrationApplicabilityV2 applicability;
status = lardon3d_optical_calibration_applicability_v2_create(
database, profile.calibration_profile_id, binding->exemplar_capture_id,
&applicability);
if (status != LARDON3D_PROJECT_DB_OK)
return db_error(status);
*applicability_id = applicability.applicability_id;
return LARDON3D_CALIBRATION_WORKFLOW_V2_READY;
}
Lardon3DCalibrationWorkflowV2Result lardon3d_calibration_workflow_v2_complete(
Lardon3DProjectDb *database, uint64_t execution_id,
const unsigned char *artifact, size_t artifact_size,
const unsigned char expected_artifact_sha256[32],
const Lardon3DCalibrationWorkflowV2Binding *bindings, size_t binding_count,
Lardon3DCalibrationWorkflowV2Output *output) {
if (output)
memset(output, 0, sizeof(*output));
if (!database || execution_id == 0 || !artifact ||
!expected_artifact_sha256 || !bindings || !output || binding_count == 0 ||
binding_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES)
return LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_ARGUMENT;
Lardon3DProjectDbSelectedExecution execution;
Lardon3DProjectDbResult status = lardon3d_project_db_load_selected_execution(
database, execution_id, &execution);
if (status != LARDON3D_PROJECT_DB_OK)
return db_error(status);
if (execution.item_count != binding_count ||
(execution.stage != LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
execution.stage != LARDON3D_SELECTED_EXECUTION_READY))
return LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
const Lardon3DCalibrationWorkflowV2Binding **ordered =
calloc(binding_count, sizeof(*ordered));
Lardon3DOpticalCaptureGeometricState *states =
calloc(binding_count, sizeof(*states));
Lardon3DCalibrationBootstrapV2Member *members =
calloc(binding_count, sizeof(*members));
if (!ordered || !states || !members) {
free(ordered);
free(states);
free(members);
return LARDON3D_CALIBRATION_WORKFLOW_V2_OUT_OF_MEMORY;
}
Lardon3DCalibrationWorkflowV2Result result =
LARDON3D_CALIBRATION_WORKFLOW_V2_READY;
for (size_t index = 0; index < binding_count; ++index) {
const Lardon3DCalibrationWorkflowV2Binding *binding = &bindings[index];
if (binding->selected_item_index >= binding_count ||
ordered[binding->selected_item_index] || binding->capture_id == 0 ||
(binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC &&
(binding->applicability_id != 0 ||
binding->exemplar_capture_id != 0)) ||
(binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_EXISTING_EXPLICIT &&
(binding->applicability_id == 0 ||
binding->exemplar_capture_id != 0)) ||
(binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT &&
(binding->applicability_id != 0 ||
binding->exemplar_capture_id == 0)) ||
binding->kind < LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC ||
binding->kind > LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT) {
result = LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_ARGUMENT;
break;
}
ordered[binding->selected_item_index] = binding;
}
/* Preflight all scientific Capture bindings before immutable publication.
* A missing/incomplete tuple is a truthful semantic state, while a caller
* mapping that disagrees with selected_execution_items is an error. */
for (size_t index = 0; index < binding_count &&
result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY;
++index) {
const Lardon3DCalibrationWorkflowV2Binding *binding = ordered[index];
Lardon3DProjectDbSelectedExecutionItem selected;
status = lardon3d_project_db_load_selected_execution_item(
database, execution_id, (uint32_t)index, &selected);
if (status != LARDON3D_PROJECT_DB_OK || !selected.has_image ||
selected.capture_id != binding->capture_id) {
result = LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
break;
}
status = lardon3d_optical_capture_geometric_state_load(
database, binding->capture_id, &states[index]);
if (status == LARDON3D_PROJECT_DB_NOT_FOUND ||
(status == LARDON3D_PROJECT_DB_OK && !complete_state(&states[index]))) {
result = LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED;
break;
}
if (status != LARDON3D_PROJECT_DB_OK) {
result = db_error(status);
break;
}
if (binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT) {
Lardon3DOpticalCaptureGeometricState exemplar;
status = lardon3d_optical_capture_geometric_state_load(
database, binding->exemplar_capture_id, &exemplar);
if (status == LARDON3D_PROJECT_DB_NOT_FOUND ||
(status == LARDON3D_PROJECT_DB_OK && !complete_state(&exemplar))) {
result = LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED;
break;
}
if (status != LARDON3D_PROJECT_DB_OK) {
result = db_error(status);
break;
}
if (!same_state(&states[index], &exemplar)) {
result = LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
break;
}
}
if (binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC) {
Lardon3DOpticalCalibrationResolutionV2 resolution;
status = lardon3d_optical_capture_calibration_resolve_v2(
database, binding->capture_id, &resolution);
if (status != LARDON3D_PROJECT_DB_OK) {
result = db_error(status);
break;
}
if (resolution.kind == LARDON3D_OPTICAL_CALIBRATION_REQUIRED)
result = LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED;
else if (resolution.kind ==
LARDON3D_OPTICAL_CALIBRATION_SELECTION_REQUIRED)
result = LARDON3D_CALIBRATION_WORKFLOW_V2_SELECTION_REQUIRED;
}
}
Lardon3DCalibrationBootstrapV2Output publication;
if (result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY) {
Lardon3DCalibrationBootstrapV2Result published =
lardon3d_calibration_bootstrap_v2_publish_unattached(
database, execution_id, artifact, artifact_size,
expected_artifact_sha256, members, binding_count, &publication);
if (published != LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK)
result = bootstrap_result(published);
}
for (size_t index = 0; index < binding_count &&
result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY;
++index) {
const Lardon3DCalibrationWorkflowV2Binding *binding = ordered[index];
uint64_t applicability_id = binding->applicability_id;
if (binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT)
result = publish_applicability(
database, binding, members[index].calibration_id,
states[index].optical_configuration_id, &applicability_id);
if (result != LARDON3D_CALIBRATION_WORKFLOW_V2_READY)
break;
if (binding->kind != LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC) {
status = lardon3d_optical_capture_calibration_select_v2(
database, binding->capture_id, applicability_id);
if (status != LARDON3D_PROJECT_DB_OK) {
result = db_error(status);
break;
}
}
Lardon3DOpticalCalibrationResolutionV2 resolution;
status = lardon3d_optical_capture_calibration_resolve_v2(
database, binding->capture_id, &resolution);
if (status != LARDON3D_PROJECT_DB_OK) {
result = db_error(status);
break;
}
if (resolution.kind == LARDON3D_OPTICAL_CALIBRATION_REQUIRED)
result = LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED;
else if (resolution.kind == LARDON3D_OPTICAL_CALIBRATION_SELECTION_REQUIRED)
result = LARDON3D_CALIBRATION_WORKFLOW_V2_SELECTION_REQUIRED;
else if (resolution.sparse_calibration_id != members[index].calibration_id)
result = LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
}
if (result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY) {
status = lardon3d_project_db_assign_selected_calibration_scope(
database, execution_id, publication.scope.scope_id);
if (status != LARDON3D_PROJECT_DB_OK)
result = db_error(status);
}
if (result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY) {
status = lardon3d_project_db_load_selected_execution(database, execution_id,
&execution);
if (status != LARDON3D_PROJECT_DB_OK ||
execution.stage != LARDON3D_SELECTED_EXECUTION_READY ||
!execution.has_calibration_scope ||
execution.calibration_scope_id != publication.scope.scope_id)
result = LARDON3D_CALIBRATION_WORKFLOW_V2_DB_ERROR;
}
if (result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY) {
output->publication = publication;
output->selected_item_count = (uint32_t)binding_count;
}
free(ordered);
free(states);
free(members);
return result;
}

View file

@ -0,0 +1,342 @@
#include <lardon3d/calibration_tooling_v2.h>
#include <lardon3d/calibration_workflow_v2.h>
#include <lardon3d/optical_profiles.h>
#include <sqlite3.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CHECK(condition) \
do { \
if (!(condition)) { \
fprintf(stderr, "CHECK failed at %s:%d: %s\n", __FILE__, __LINE__, \
#condition); \
return false; \
} \
} while (0)
static bool sql(const char *path, const char *text) {
sqlite3 *database = NULL;
char *error = NULL;
if (sqlite3_open(path, &database) != SQLITE_OK)
return false;
int code = sqlite3_exec(database, text, NULL, NULL, &error);
if (code != SQLITE_OK)
fprintf(stderr, "SQL: %s\n", error ? error : "error");
sqlite3_free(error);
sqlite3_close(database);
return code == SQLITE_OK;
}
static void digest(unsigned char output[32], unsigned char seed) {
for (size_t index = 0; index < 32; ++index)
output[index] = (unsigned char)(seed + index);
}
static void evidence(Lardon3DCalibrationToolingV2Evidence *value,
Lardon3DCalibrationToolingV2Group groups[2],
Lardon3DCalibrationToolingV2Entry entries[2]) {
memset(value, 0, sizeof(*value));
memset(groups, 0, 2 * sizeof(*groups));
memset(entries, 0, 2 * sizeof(*entries));
for (size_t index = 0; index < 2; ++index) {
digest(groups[index].group_identity_sha256, (unsigned char)(1 + index));
groups[index].group_version = 1;
digest(groups[index].optical_state_sha256, (unsigned char)(11 + index));
digest(groups[index].target_sha256, (unsigned char)(21 + index));
digest(groups[index].solver_executable_sha256, (unsigned char)(31 + index));
digest(groups[index].solver_configuration_sha256,
(unsigned char)(41 + index));
digest(groups[index].initialization_evidence_sha256,
(unsigned char)(51 + index));
digest(groups[index].validation_evidence_sha256,
(unsigned char)(61 + index));
groups[index].entries = &entries[index];
groups[index].entry_count = 1;
entries[index].selected_item_index = (uint32_t)index;
entries[index].image_id = index + 1;
memset(entries[index].representation_sha256, (int)(0x11 * (index + 1)), 32);
entries[index].width = index == 0 ? 4000 : 6000;
entries[index].height = index == 0 ? 3000 : 4000;
entries[index].fx = index == 0 ? 3000.0 : 4500.0;
entries[index].fy = entries[index].fx + 1.0;
entries[index].cx = entries[index].width / 2.0;
entries[index].cy = entries[index].height / 2.0;
entries[index].support_images = 20;
entries[index].support_observations = 1000;
entries[index].reprojection_rmse_px = 0.4;
entries[index].maximum_parameter_delta = 0.01;
entries[index].validation_flags =
LARDON3D_CALIBRATION_TOOLING_V2_VALIDATION_FLAGS;
}
value->groups = groups;
value->group_count = 2;
value->entry_count = 2;
}
static bool create_project(const char *path, Lardon3DProjectDb **database,
uint64_t *execution_id) {
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
CHECK(lardon3d_project_db_open(path, database, error) ==
LARDON3D_PROJECT_DB_OK);
lardon3d_project_db_close(*database);
*database = NULL;
CHECK(sql(
path,
"INSERT INTO scansets VALUES(1,'workflow-v2',1,1);"
"INSERT INTO tasks "
"VALUES(1,'quality','photo_quality.triage',1,5,5,1,1,0,0,0,0,1);"
"INSERT INTO tasks "
"VALUES(2,'campaign','acquisition_campaign.run',1,5,5,1,1,0,0,0,0,1);"
"INSERT INTO photo_quality_triage_tasks VALUES(1,1,2,1,X'01');"
"INSERT INTO photo_quality_triage_results VALUES"
"(1,1,0,1,0,1,0,1,1,1,1,1,1,0,0,1,1,0,'GOOD'),"
"(1,2,1,1,0,1,0,1,1,1,1,1,1,0,0,1,1,0,'GOOD');"
"INSERT INTO acquisition_campaign_tasks VALUES(2,1,2,2,X'02');"
"INSERT INTO captures VALUES(1,1,1),(2,1,2);"
"INSERT INTO acquisition_campaign_captures VALUES(2,11,1),(2,12,2);"
"INSERT INTO image_assets VALUES"
"(1,X'1111111111111111111111111111111111111111111111111111111111111111',"
"'assets/images/11/"
"1111111111111111111111111111111111111111111111111111111111111111',1,1,1)"
","
"(2,X'2222222222222222222222222222222222222222222222222222222222222222',"
"'assets/images/22/"
"2222222222222222222222222222222222222222222222222222222222222222',1,1,2)"
";"
"INSERT INTO images VALUES(1,1,1,'a','a',NULL,1),(2,1,2,'b','b',NULL,2);"
"INSERT INTO capture_images VALUES(1,1),(2,2);"
"INSERT INTO camera_body_profiles VALUES(1,'Maker','Body','Body');"
"INSERT INTO lens_profiles VALUES(1,'Maker','L1','L1',2,2,24000,24000);"
"INSERT INTO lens_profiles VALUES(2,'Maker','L2','L2',2,2,50000,50000);"
"INSERT INTO optical_configurations VALUES(1,1,1,24000),(2,1,2,50000);"
"INSERT INTO capture_optical_configurations "
"VALUES(1,1,2,NULL,NULL),(2,2,2,NULL,NULL);"));
CHECK(lardon3d_project_db_open(path, database, error) ==
LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbSelectedExecutionItem items[2] = {
{.item_index = 0,
.quality_group_id = 1,
.campaign_group_id = 11,
.capture_id = 1,
.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE},
{.item_index = 1,
.quality_group_id = 2,
.campaign_group_id = 12,
.capture_id = 2,
.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE}};
Lardon3DProjectDbSelectedExecution execution;
CHECK(lardon3d_project_db_create_selected_execution(*database, 1, 2, items, 2,
1, &execution) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_record_selected_representation(
*database, execution.execution_id, 0, 1, 1) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_record_selected_representation(
*database, execution.execution_id, 1, 2, 2) ==
LARDON3D_PROJECT_DB_OK);
*execution_id = execution.execution_id;
return true;
}
static bool add_state(Lardon3DProjectDb *database, uint64_t capture_id,
uint64_t configuration_id, uint32_t width,
uint32_t height) {
Lardon3DOpticalCaptureGeometricState input = {
.capture_id = capture_id,
.optical_configuration_id = configuration_id,
.state_version = 1,
.provenance = LARDON3D_OPTICAL_GEOMETRIC_STATE_CALLER_EXPLICIT,
.focus_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
.aperture_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
.aperture_x1000 = 8000,
.stabilization = LARDON3D_OPTICAL_STABILIZATION_OFF,
.crop_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
.pipeline_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
.representation_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
.decoded_geometry_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
.decoded_width = width,
.decoded_height = height};
strcpy(input.focus_observation, "fixed");
strcpy(input.crop_observation, "full");
strcpy(input.pipeline_observation, "raw-v1");
strcpy(input.representation_observation, "png-v1");
Lardon3DOpticalCaptureGeometricState output;
return lardon3d_optical_capture_geometric_state_create(
database, &input, &output) == LARDON3D_PROJECT_DB_OK;
}
static bool is_unattached(Lardon3DProjectDb *database, uint64_t execution_id) {
Lardon3DProjectDbSelectedExecution execution;
return lardon3d_project_db_load_selected_execution(
database, execution_id, &execution) == LARDON3D_PROJECT_DB_OK &&
execution.stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
!execution.has_calibration_scope;
}
static bool run_cases(void) {
Lardon3DCalibrationToolingV2Evidence source;
Lardon3DCalibrationToolingV2Group groups[2];
Lardon3DCalibrationToolingV2Entry entries[2];
evidence(&source, groups, entries);
unsigned char artifact[1024], artifact_hash[32];
size_t artifact_size = 0;
CHECK(lardon3d_calibration_tooling_v2_produce(
&source, artifact, sizeof(artifact), &artifact_size,
artifact_hash) == LARDON3D_CALIBRATION_TOOLING_V2_OK);
char directory[] = "/tmp/lardon3d-workflow-v2-XXXXXX";
CHECK(mkdtemp(directory) != NULL);
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/project.db", directory) > 0);
Lardon3DProjectDb *database = NULL;
uint64_t execution_id = 0;
CHECK(create_project(path, &database, &execution_id));
Lardon3DCalibrationWorkflowV2Binding bindings[2] = {
{.selected_item_index = 0,
.capture_id = 1,
.kind = LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT,
.exemplar_capture_id = 1},
{.selected_item_index = 1,
.capture_id = 2,
.kind = LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT,
.exemplar_capture_id = 2}};
Lardon3DCalibrationWorkflowV2Output output;
/* No complete observed tuple is a semantic non-ready state and must not
* trigger the unattached publication primitive. */
CHECK(lardon3d_calibration_workflow_v2_complete(
database, execution_id, artifact, artifact_size, artifact_hash,
bindings, 2,
&output) == LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED);
CHECK(is_unattached(database, execution_id));
CHECK(add_state(database, 1, 1, 4000, 3000));
CHECK(add_state(database, 2, 2, 6000, 4000));
unsigned char wrong_hash[32] = {0};
memset(&output, 0xa5, sizeof(output));
CHECK(lardon3d_calibration_workflow_v2_complete(
database, execution_id, artifact, artifact_size, wrong_hash,
bindings, 2,
&output) == LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_EVIDENCE);
const Lardon3DCalibrationWorkflowV2Output empty_output = {0};
CHECK(memcmp(&output, &empty_output, sizeof(output)) == 0);
CHECK(is_unattached(database, execution_id));
Lardon3DCalibrationBootstrapV2Member probe_members[2];
Lardon3DCalibrationBootstrapV2Output probe_publication;
Lardon3DCalibrationBootstrapV2Result probe =
lardon3d_calibration_bootstrap_v2_publish_unattached(
database, execution_id, artifact, artifact_size, artifact_hash,
probe_members, 2, &probe_publication);
if (probe != LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK)
fprintf(stderr, "unattached probe result=%d\n", (int)probe);
CHECK(probe == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK);
Lardon3DCalibrationWorkflowV2Result completed =
lardon3d_calibration_workflow_v2_complete(
database, execution_id, artifact, artifact_size, artifact_hash,
bindings, 2, &output);
if (completed != LARDON3D_CALIBRATION_WORKFLOW_V2_READY)
fprintf(stderr, "happy workflow result=%d\n", (int)completed);
CHECK(completed == LARDON3D_CALIBRATION_WORKFLOW_V2_READY);
uint64_t scope_id = output.publication.scope.scope_id;
Lardon3DSparseCalibrationMember members[2];
size_t count = 0;
uint64_t next = 0;
CHECK(lardon3d_sparse_calibration_scope_list_members(
database, scope_id, 0, members, 2, &count, &next) ==
LARDON3D_PROJECT_DB_OK);
CHECK(count == 2 && members[0].calibration_id != members[1].calibration_id);
CHECK(lardon3d_calibration_workflow_v2_complete(
database, execution_id, artifact, artifact_size, artifact_hash,
bindings, 2, &output) == LARDON3D_CALIBRATION_WORKFLOW_V2_READY &&
output.publication.scope.scope_id == scope_id);
lardon3d_project_db_close(database);
/* A requested applicability for the other heterogeneous member is exact-
* state incompatible and cannot attach the otherwise complete scope. */
char wrong_path[512];
CHECK(snprintf(wrong_path, sizeof(wrong_path), "%s/wrong.db", directory) > 0);
CHECK(create_project(wrong_path, &database, &execution_id));
CHECK(add_state(database, 1, 1, 4000, 3000));
CHECK(add_state(database, 2, 2, 6000, 4000));
Lardon3DCalibrationBootstrapV2Member published[2];
Lardon3DCalibrationBootstrapV2Output publication;
CHECK(lardon3d_calibration_bootstrap_v2_publish_unattached(
database, execution_id, artifact, artifact_size, artifact_hash,
published, 2,
&publication) == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK);
Lardon3DOpticalCalibrationProfile profile = {
.optical_configuration_id = 2,
.sparse_calibration_id = published[1].calibration_id,
.profile_version = 1,
.applicability = LARDON3D_OPTICAL_CALIBRATION_EXACT_CONFIGURATION};
strcpy(profile.name, "wrong-member");
strcpy(profile.provenance, "test");
Lardon3DOpticalCalibrationProfile stored_profile;
CHECK(lardon3d_optical_calibration_profile_create(
database, &profile, &stored_profile) == LARDON3D_PROJECT_DB_OK);
Lardon3DOpticalCalibrationApplicabilityV2 applicability;
CHECK(lardon3d_optical_calibration_applicability_v2_create(
database, stored_profile.calibration_profile_id, 2,
&applicability) == LARDON3D_PROJECT_DB_OK);
bindings[0].kind = LARDON3D_CALIBRATION_WORKFLOW_V2_EXISTING_EXPLICIT;
bindings[0].applicability_id = applicability.applicability_id;
bindings[0].exemplar_capture_id = 0;
CHECK(lardon3d_calibration_workflow_v2_complete(
database, execution_id, artifact, artifact_size, artifact_hash,
bindings, 2,
&output) == LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT);
CHECK(is_unattached(database, execution_id));
lardon3d_project_db_close(database);
/* Two exact candidates remain visibly ambiguous in AUTOMATIC mode even
* when both happen to reference the artifact's immutable calibration. */
char ambiguous_path[512];
CHECK(snprintf(ambiguous_path, sizeof(ambiguous_path), "%s/ambiguous.db",
directory) > 0);
CHECK(create_project(ambiguous_path, &database, &execution_id));
CHECK(add_state(database, 1, 1, 4000, 3000));
CHECK(add_state(database, 2, 2, 6000, 4000));
CHECK(lardon3d_calibration_bootstrap_v2_publish_unattached(
database, execution_id, artifact, artifact_size, artifact_hash,
published, 2,
&publication) == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK);
for (size_t index = 0; index < 2; ++index) {
memset(&profile, 0, sizeof(profile));
profile.optical_configuration_id = 1;
profile.sparse_calibration_id = published[0].calibration_id;
profile.profile_version = 1;
profile.applicability = LARDON3D_OPTICAL_CALIBRATION_EXACT_CONFIGURATION;
profile.created_at = 0;
CHECK(snprintf(profile.name, sizeof(profile.name), "ambiguous-%zu", index) >
0);
strcpy(profile.provenance, "test ambiguity");
CHECK(lardon3d_optical_calibration_profile_create(
database, &profile, &stored_profile) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_optical_calibration_applicability_v2_create(
database, stored_profile.calibration_profile_id, 1,
&applicability) == LARDON3D_PROJECT_DB_OK);
}
bindings[0].kind = LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC;
bindings[0].applicability_id = 0;
bindings[0].exemplar_capture_id = 0;
CHECK(lardon3d_calibration_workflow_v2_complete(
database, execution_id, artifact, artifact_size, artifact_hash,
bindings, 2,
&output) == LARDON3D_CALIBRATION_WORKFLOW_V2_SELECTION_REQUIRED);
CHECK(is_unattached(database, execution_id));
lardon3d_project_db_close(database);
CHECK(unlink(path) == 0);
CHECK(unlink(wrong_path) == 0);
CHECK(unlink(ambiguous_path) == 0);
CHECK(rmdir(directory) == 0);
return true;
}
int main(void) { return run_cases() ? EXIT_SUCCESS : EXIT_FAILURE; }