feat: add calibration workflow input boundary

This commit is contained in:
fy59 2026-09-03 09:56:20 +02:00
parent 311b303744
commit e1cf83a158
9 changed files with 865 additions and 6 deletions

View file

@ -0,0 +1,122 @@
# Calibration Workflow
## Status
```text
CALIBRATION_WORKFLOW=IN_PROGRESS
CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN
CURRENT_WORKFLOW_NEXT=EVIDENCE_MATERIALIZATION_V1
```
## Authority
Calibration Science v1, Calibration Tooling v1 and Calibration Bootstrap v1
remain FROZEN scientific and import authorities.
This workflow is bounded orchestration only. It does not introduce a solver,
Project DB schema version, Task kind, Sparse SfM execution or reconstructed
scientific evidence.
## Frozen flow
```text
physical calibration acquisition
-> session.l3dcal
-> external Calibration Evidence Solver v1
-> immutable solver bundle
-> campaign-state evidence
-> Calibration Workflow
-> Calibration Tooling v1
-> L3DCALB1 v1
-> Calibration Bootstrap v1
-> selected execution READY
```
## Input Boundary v1
`CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN`.
The implementation is exposed through:
```text
include/lardon3d/calibration_workflow.h
src/calibration_workflow.cpp
```
Input Boundary v1 performs no Project DB mutation.
It accepts only bounded regular files and rejects special files and symlinks
before potentially blocking reads. File access follows the nonblocking,
close-on-exec regular-file discipline.
The bounded input set is:
```text
session.l3dcal
session.l3dcal.bundle/detection.json
session.l3dcal.bundle/solve.json
session.l3dcal.bundle/evidence.json
session.l3dcal.bundle/producer.json
L3DCAL_CAMPAIGN_STATE_V1
```
It verifies:
- regular bounded files;
- SHA-256 identities;
- strict session syntax;
- structurally valid canonical JSON bundle members;
- exact session SHA binding through `producer.json`;
- decoder/version consistency;
- exact optical-state SHA equality;
- exact optical-state token equality;
- campaign-state identity consistency.
Malformed JSON, oversize files, symlinks, FIFOs, session digest mismatch and
optical-state mismatch are rejected.
Input Boundary v1 does not:
- open or mutate Project DB;
- construct `Lardon3DCalibrationToolingEvidence`;
- call Calibration Tooling;
- produce `L3DCALB1`;
- invoke Calibration Bootstrap;
- change selected-execution state.
## Campaign optical-state evidence
Project DB v23 retains exact explicit optical configuration identity, including
body, objective and focal state, but Calibration Science v1 requires a broader
scientific key including focus, stabilization and processing/decode state.
No equality may be inferred between those domains.
`L3DCAL_CAMPAIGN_STATE_V1` therefore provides immutable external evidence for
the complete Science v1 optical state. A later coordinator stage must verify
this evidence against both the calibration session and each selected Capture's
explicit Project DB optical configuration.
Absence or disagreement remains `CALIBRATION_UNAVAILABLE`.
## Current next boundary
```text
CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1
```
The next stage parses the already validated bundle into bounded in-memory
Science v1 evidence:
- exact per-view evidence;
- exact repeated full-solve parameters;
- fit parameters;
- global and hold-out validation metrics;
- coordinate-equivalence evidence;
- immutable provenance digests.
It still performs no Project DB mutation.
Only after that boundary passes may the workflow bind the exact selected
execution, campaign representations and optical assignments and invoke the
FROZEN Tooling/Bootstrap path.

View file

@ -527,6 +527,8 @@ The solver session contract now also requires explicit measured `white_border >=
`CALIBRATION_SOLVER_BUNDLE_REPAIR_V1=PASS/FROZEN`: retained per-view fields are now present in the actual solver output, `producer.json` is valid canonical JSON, solver-configuration records contain real line feeds, and the deterministic self-test checks these contents rather than byte identity alone.
`CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN`: the workflow now has a bounded non-mutating input boundary for session, solver bundle and campaign optical-state evidence. It rejects special/symlink/oversize files, invalid JSON, provenance digest mismatches and incompatible optical state. The next boundary is Evidence Materialization v1.
A bounded Tooling correction aligned planarity handling with Calibration Science v1: Science v1
defines a categorical physical planarity attestation, not a numeric flatness threshold. Tooling
therefore rejects invented finite `target_flatness_mm` values. `L3DCALB1` v1 and Calibration

View file

@ -0,0 +1,79 @@
#ifndef LARDON3D_CALIBRATION_WORKFLOW_H
#define LARDON3D_CALIBRATION_WORKFLOW_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
LARDON3D_CALIBRATION_WORKFLOW_VERSION = 1,
LARDON3D_CALIBRATION_WORKFLOW_MAX_FILE_BYTES = 128 * 1024 * 1024,
LARDON3D_CALIBRATION_WORKFLOW_MAX_SELECTED_ITEMS = 4096,
LARDON3D_CALIBRATION_WORKFLOW_OPTICAL_STATE_TOKEN_CAPACITY = 1025,
};
typedef enum {
LARDON3D_CALIBRATION_WORKFLOW_OK = 0,
LARDON3D_CALIBRATION_WORKFLOW_INVALID_ARGUMENT,
LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR,
LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE,
LARDON3D_CALIBRATION_WORKFLOW_CAPACITY,
LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE,
LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH,
} Lardon3DCalibrationWorkflowResult;
/* `campaign_state_path` is a canonical external acquisition manifest:
*
* L3DCAL_CAMPAIGN_STATE_V1
* execution <selected-execution-id>
* optical_configuration <explicit-v23-configuration-id>
* optical_state <sha256> <complete-Science-v1-state-token>
* capture <zero-based-selected-item-index> <capture-id>
*
* Capture rows are contiguous and ordered. The later DB-binding stage proves
* that these IDs are exactly the selected execution and that each Capture owns
* the declared explicit optical configuration; this input boundary performs no
* DB access and therefore never treats the manifest alone as that proof. */
typedef struct {
const char *session_path;
const char *detection_path;
const char *solve_path;
const char *evidence_path;
const char *producer_path;
const char *campaign_state_path;
} Lardon3DCalibrationWorkflowInputFiles;
typedef struct {
unsigned char session_sha256[32];
unsigned char detection_sha256[32];
unsigned char solve_sha256[32];
unsigned char evidence_sha256[32];
unsigned char producer_sha256[32];
unsigned char campaign_state_sha256[32];
unsigned char optical_state_sha256[32];
unsigned char solver_executable_sha256[32];
unsigned char solver_configuration_sha256[32];
uint64_t selected_execution_id;
uint64_t optical_configuration_id;
uint32_t capture_count;
uint64_t capture_ids[LARDON3D_CALIBRATION_WORKFLOW_MAX_SELECTED_ITEMS];
char optical_state_token[LARDON3D_CALIBRATION_WORKFLOW_OPTICAL_STATE_TOKEN_CAPACITY];
} Lardon3DCalibrationWorkflowInputBoundary;
/* Validate only the immutable external input boundary. This function performs
* no Project DB access, no calibration solve, no Tooling import and no
* selected-execution mutation. Every path is opened O_NONBLOCK/O_NOFOLLOW,
* must resolve to a bounded regular file, and is SHA-256 checked before its
* syntax/provenance is consumed. */
Lardon3DCalibrationWorkflowResult lardon3d_calibration_workflow_validate_input_boundary(
const Lardon3DCalibrationWorkflowInputFiles *files,
Lardon3DCalibrationWorkflowInputBoundary *boundary);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -180,6 +180,7 @@ lardon3d_app = executable(
'src/optical_profiles.c',
'src/calibration_bootstrap.c',
'src/calibration_tooling.c',
'src/calibration_workflow.cpp',
'src/sparse_sfm_geometry.cpp',
'src/sparse_sfm_incremental.cpp',
'src/sparse_sfm_bundle_adjustment.cpp',
@ -895,6 +896,18 @@ calibration_tooling_test = executable(
test('calibration-tooling', calibration_tooling_test, timeout: 30)
calibration_workflow_test = executable(
'test-calibration-workflow',
sources: [
'tests/test_calibration_workflow.cpp',
'src/calibration_workflow.cpp',
],
include_directories: include_directories('include'),
dependencies: [openssl],
)
test('calibration-workflow', calibration_workflow_test, timeout: 30)
optical_profiles_test = executable(
'test-optical-profiles',
sources: [

View file

@ -6,7 +6,7 @@
CURRENT_PROJECT_DB_SCHEMA=v25
PRODUCTION_TASK_KINDS=16
USER_FACING_UI_LANGUAGE_NORMALIZATION=PASS
CURRENT_IMPLEMENTATION_CURSOR=1_CALIBRATION_WORKFLOW_COORDINATOR
CURRENT_IMPLEMENTATION_CURSOR=1_CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION
```
## Authority
@ -31,6 +31,7 @@ Calibration solver white-border evidence PASS/FROZEN
Calibration solver producer identity PASS/FROZEN
Calibration solver per-view evidence PASS/FROZEN
Calibration solver bundle repair PASS/FROZEN
Calibration workflow input boundary PASS/FROZEN
Calibration Tooling planarity alignment PASS/FROZEN
```

View file

@ -13,14 +13,17 @@ CALIBRATION_SOLVER_PRODUCER_IDENTITY_V1=PASS/FROZEN
CALIBRATION_SOLVER_PER_VIEW_EVIDENCE_V1=PASS/FROZEN
CALIBRATION_SOLVER_BUNDLE_REPAIR_V1=PASS/FROZEN
CALIBRATION_WORKFLOW=IN_PROGRESS
CURRENT_CALIBRATION_NEXT=WORKFLOW_COORDINATOR
CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN
CURRENT_CALIBRATION_NEXT=WORKFLOW_EVIDENCE_MATERIALIZATION_V1
```
## Authority
`docs/architecture/calibration_science_v1.md`, `docs/architecture/calibration_bootstrap.md` and `docs/architecture/calibration_solver_preflight_v1.md` are the current specialized calibration documents.
`docs/architecture/calibration_tooling.md` is the specialized Tooling authority. The public API remains `include/lardon3d/calibration_tooling.h`.
`docs/architecture/calibration_tooling.md` is the specialized Tooling authority.
`docs/architecture/calibration_workflow.md` is the specialized workflow authority. The public API remains `include/lardon3d/calibration_tooling.h`.
A bounded corrective review established that Calibration Science v1 defines target planarity as a categorical physical attestation, not a numeric flatness tolerance. Tooling preserves its public structure layout while requiring `target_flatness_mm` to be NaN, so callers cannot invent a millimetre measurement. The canonical session's `planarity PASS <sha256>` evidence is bound through immutable initialization evidence.
@ -54,7 +57,7 @@ dedicated physical calibration acquisition
-> real Sparse SfM
```
The current implementation gap is the workflow coordinator. It consumes the immutable `session.l3dcal` plus `detection.json`, `solve.json` and `evidence.json`, binds them to the exact selected execution and optical state, constructs the bounded Tooling evidence and never manufactures missing physical evidence.
The workflow coordinator is now implemented through its first bounded checkpoint. Input Boundary v1 validates immutable files, hashes, formats and complete optical-state equality without Project DB mutation. The current implementation gap is Evidence Materialization v1. It consumes the immutable `session.l3dcal` plus `detection.json`, `solve.json` and `evidence.json`, binds them to the exact selected execution and optical state, constructs the bounded Tooling evidence and never manufactures missing physical evidence.
## REQUIRED_PRODUCT_TARGET

View file

@ -6,7 +6,7 @@
IMPLEMENTATION_ORDER=DEPENDENCY_DRIVEN
IMPLEMENTATION_AUTHORIZATION=NO
STEP_0_USER_FACING_LANGUAGE_NORMALIZATION=PASS
CURRENT_NEXT=1_CALIBRATION_WORKFLOW_COORDINATOR
CURRENT_NEXT=1_CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION
```
## Authority
@ -20,7 +20,7 @@ Implementation remains unauthorized until the human explicitly authorizes a tran
Default dependency order:
0. user-facing repository/UI language normalization where appropriate — PASS;
1. final usable calibration workflow — IN PROGRESS; current next sub-boundary: workflow coordinator;
1. final usable calibration workflow — IN PROGRESS; Input Boundary v1 PASS/FROZEN; current next sub-boundary: Evidence Materialization v1;
2. dedicated physical calibrated real campaign;
3. real Sparse SfM proof;
4. durable Dense/OpenMVS orchestration;

View file

@ -0,0 +1,483 @@
#include <lardon3d/calibration_workflow.h>
#include <cerrno>
#include <charconv>
#include <cctype>
#include <cstdint>
#include <cmath>
#include <cstring>
#include <fcntl.h>
#include <locale>
#include <map>
#include <openssl/evp.h>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
namespace {
constexpr size_t kSha256Bytes = 32;
constexpr size_t kJsonDepthMax = 64;
constexpr size_t kJsonKeyMax = 128;
struct MappedFile {
int fd = -1;
const unsigned char *data = nullptr;
size_t size = 0;
unsigned char sha256[kSha256Bytes]{};
~MappedFile() {
if (data && size) munmap(const_cast<unsigned char *>(data), size);
if (fd >= 0) close(fd);
}
MappedFile() = default;
MappedFile(const MappedFile &) = delete;
MappedFile &operator=(const MappedFile &) = delete;
};
bool digest_bytes(const unsigned char *data, size_t size, unsigned char output[32]) {
unsigned int length = 0;
return EVP_Digest(data, size, output, &length, EVP_sha256(), nullptr) == 1 && length == 32;
}
Lardon3DCalibrationWorkflowResult map_regular_file(const char *path, MappedFile *out) {
if (!path || !*path || !out) return LARDON3D_CALIBRATION_WORKFLOW_INVALID_ARGUMENT;
int fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOFOLLOW);
if (fd < 0) {
if (errno == ELOOP) return LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE;
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
struct stat st{};
if (fstat(fd, &st) != 0) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
if (!S_ISREG(st.st_mode)) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE;
}
if (st.st_size <= 0) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
}
if (static_cast<uint64_t>(st.st_size) > LARDON3D_CALIBRATION_WORKFLOW_MAX_FILE_BYTES) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
}
const size_t size = static_cast<size_t>(st.st_size);
void *mapped = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
out->fd = fd;
out->data = static_cast<const unsigned char *>(mapped);
out->size = size;
if (!digest_bytes(out->data, out->size, out->sha256))
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
return LARDON3D_CALIBRATION_WORKFLOW_OK;
}
bool hex_digit(char c, unsigned *value) {
if (c >= '0' && c <= '9') *value = static_cast<unsigned>(c - '0');
else if (c >= 'a' && c <= 'f') *value = static_cast<unsigned>(c - 'a' + 10);
else if (c >= 'A' && c <= 'F') *value = static_cast<unsigned>(c - 'A' + 10);
else return false;
return true;
}
bool parse_sha256(std::string_view text, unsigned char output[32]) {
if (text.size() != 64) return false;
for (size_t i = 0; i < 32; ++i) {
unsigned hi = 0, lo = 0;
if (!hex_digit(text[2 * i], &hi) || !hex_digit(text[2 * i + 1], &lo)) return false;
output[i] = static_cast<unsigned char>((hi << 4u) | lo);
}
return true;
}
bool nonzero_sha(const unsigned char value[32]) {
unsigned char any = 0;
for (size_t i = 0; i < 32; ++i) any |= value[i];
return any != 0;
}
bool token_ok(std::string_view value) {
if (value.empty() || value.size() >= LARDON3D_CALIBRATION_WORKFLOW_OPTICAL_STATE_TOKEN_CAPACITY)
return false;
for (unsigned char c : value) {
if (!(std::isalnum(c) || c == '_' || c == '-' || c == '.' || c == ':')) return false;
}
return true;
}
bool parse_u64_any(std::string_view text, uint64_t *value) {
if (!value || text.empty()) return false;
uint64_t parsed = 0;
auto result = std::from_chars(text.data(), text.data() + text.size(), parsed, 10);
if (result.ec != std::errc() || result.ptr != text.data() + text.size()) return false;
*value = parsed;
return true;
}
bool parse_u64_positive(std::string_view text, uint64_t *value) {
return parse_u64_any(text, value) && *value != 0;
}
bool parse_u32_any(std::string_view text, uint32_t *value) {
uint64_t parsed = 0;
if (!parse_u64_any(text, &parsed) || parsed > UINT32_MAX) return false;
*value = static_cast<uint32_t>(parsed);
return true;
}
bool parse_finite_double(std::string_view text, double *value) {
if (!value || text.empty() || text.size() > 128) return false;
std::istringstream stream{std::string(text)};
stream.imbue(std::locale::classic());
double parsed = 0.0;
stream >> parsed;
if (!stream || !stream.eof() || !std::isfinite(parsed)) return false;
*value = parsed;
return true;
}
std::vector<std::string_view> words(std::string_view line) {
std::vector<std::string_view> out;
size_t at = 0;
while (at < line.size()) {
while (at < line.size() && (line[at] == ' ' || line[at] == '\t')) ++at;
if (at == line.size()) break;
size_t end = at;
while (end < line.size() && line[end] != ' ' && line[end] != '\t') ++end;
out.push_back(line.substr(at, end - at));
at = end;
}
return out;
}
struct SessionIdentity {
std::string decoder;
std::string decoder_version;
std::string optical_state_token;
unsigned char optical_state_sha256[32]{};
};
bool parse_session(std::string_view text, SessionIdentity *identity) {
if (!identity || text.empty() || text.back() != '\n' || text.find('\0') != std::string_view::npos ||
text.find('\r') != std::string_view::npos)
return false;
size_t at = 0;
size_t line_number = 0;
bool target = false, measurement = false, white_border = false, planarity = false;
bool decoder = false, optical_state = false, image = false;
while (at < text.size()) {
size_t end = text.find('\n', at);
if (end == std::string_view::npos) return false;
std::string_view line = text.substr(at, end - at);
at = end + 1;
++line_number;
if (line_number == 1) {
if (line != "L3DCAL_SESSION_V1") return false;
continue;
}
if (line.empty()) continue;
auto w = words(line);
if (w.empty()) return false;
const auto tag = w[0];
if (tag == "target") {
uint32_t squares_x = 0, squares_y = 0; double square = 0, marker = 0;
if (target || w.size() != 8 || !token_ok(w[1]) || w[3] != "DICT_5X5_100" ||
!parse_u32_any(w[4], &squares_x) || !parse_u32_any(w[5], &squares_y) ||
!parse_finite_double(w[6], &square) || !parse_finite_double(w[7], &marker) ||
squares_x != 9 || squares_y != 7 || square != 30.0 || marker != 21.0) return false;
unsigned char hash[32]; if (!parse_sha256(w[2], hash)) return false; target = true;
} else if (tag == "measurement") {
if (measurement || w.size() != 13 || !token_ok(w[1])) return false;
double v = 0; if (!parse_finite_double(w[2], &v) || v <= 0 || v > .1) return false;
for (size_t i = 3; i < w.size(); ++i) if (!parse_finite_double(w[i], &v)) return false;
measurement = true;
} else if (tag == "white_border") {
double v = 0; if (white_border || w.size() != 2 || !parse_finite_double(w[1], &v) || v < 30.0) return false;
white_border = true;
} else if (tag == "planarity") {
unsigned char hash[32];
if (planarity || w.size() != 3 || w[1] != "PASS" || !parse_sha256(w[2], hash)) return false;
planarity = true;
} else if (tag == "decoder") {
if (decoder || w.size() != 3 || !token_ok(w[1]) || !token_ok(w[2])) return false;
identity->decoder.assign(w[1]); identity->decoder_version.assign(w[2]); decoder = true;
} else if (tag == "optical_state") {
if (optical_state || w.size() != 3 || !parse_sha256(w[1], identity->optical_state_sha256) ||
!token_ok(w[2]) || w[2] == "UNKNOWN") return false;
identity->optical_state_token.assign(w[2]); optical_state = true;
} else if (tag == "image") {
unsigned char hash[32]; uint32_t orientation = 0;
if (w.size() != 4 || w[1].empty() || w[1].size() > 4096 || !parse_sha256(w[2], hash) ||
!parse_u32_any(w[3], &orientation) ||
(orientation != 90 && orientation != 180 && orientation != 270 && w[3] != "0")) return false;
image = true;
} else if (tag == "pre_solve" || tag == "clipping") {
unsigned char hash[32]; double v = 0;
if (w.size() != 3 || !parse_sha256(w[1], hash) || !parse_finite_double(w[2], &v)) return false;
} else if (tag == "coordinate") {
unsigned char hash[32]; uint32_t orientation = 0, width = 0, height = 0, count = 0; double dx = 0, dy = 0;
if (w.size() != 10 || !parse_sha256(w[1], hash) || !token_ok(w[2]) || !token_ok(w[3]) ||
!parse_u32_any(w[4], &orientation) || !parse_u32_any(w[5], &width) || !parse_u32_any(w[6], &height) ||
!parse_u32_any(w[7], &count) || !parse_finite_double(w[8], &dx) || !parse_finite_double(w[9], &dy) ||
width == 0 || height == 0 || count < 20 || count > 48 ||
(orientation != 90 && orientation != 180 && orientation != 270 && w[4] != "0")) return false;
} else if (tag == "coordinate_point") {
unsigned char hash[32]; double a = 0, b = 0, c = 0, d = 0;
if (w.size() != 7 || !parse_sha256(w[1], hash) || !token_ok(w[2]) ||
!parse_finite_double(w[3], &a) || !parse_finite_double(w[4], &b) ||
!parse_finite_double(w[5], &c) || !parse_finite_double(w[6], &d)) return false;
} else if (tag == "distance") {
unsigned char hash[32]; double meters = 0; uint32_t band = 0;
if (w.size() != 4 || !parse_sha256(w[1], hash) || !parse_finite_double(w[2], &meters) ||
!parse_u32_any(w[3], &band) || meters <= 0 || band > 2) return false;
} else {
return false;
}
}
return target && measurement && white_border && planarity && decoder && optical_state && image;
}
struct CampaignState {
uint64_t execution_id = 0;
uint64_t optical_configuration_id = 0;
std::string optical_state_token;
unsigned char optical_state_sha256[32]{};
std::vector<uint64_t> captures;
};
bool parse_campaign_state(std::string_view text, CampaignState *state) {
if (!state || text.empty() || text.back() != '\n' || text.find('\0') != std::string_view::npos ||
text.find('\r') != std::string_view::npos) return false;
std::vector<std::string_view> lines;
size_t at = 0;
while (at < text.size()) {
size_t end = text.find('\n', at); if (end == std::string_view::npos) return false;
if (end > at) lines.push_back(text.substr(at, end - at));
at = end + 1;
}
if (lines.size() < 5 || lines[0] != "L3DCAL_CAMPAIGN_STATE_V1") return false;
auto execution = words(lines[1]);
auto configuration = words(lines[2]);
auto optical = words(lines[3]);
if (execution.size() != 2 || execution[0] != "execution" || !parse_u64_positive(execution[1], &state->execution_id) ||
configuration.size() != 2 || configuration[0] != "optical_configuration" ||
!parse_u64_positive(configuration[1], &state->optical_configuration_id) || optical.size() != 3 ||
optical[0] != "optical_state" || !parse_sha256(optical[1], state->optical_state_sha256) ||
!token_ok(optical[2]) || optical[2] == "UNKNOWN") return false;
state->optical_state_token.assign(optical[2]);
for (size_t i = 4; i < lines.size(); ++i) {
auto capture = words(lines[i]);
uint64_t index = 0, capture_id = 0;
if (capture.size() != 3 || capture[0] != "capture" || !parse_u64_any(capture[1], &index) ||
!parse_u64_positive(capture[2], &capture_id) || index != state->captures.size()) return false;
if (state->captures.size() >= LARDON3D_CALIBRATION_WORKFLOW_MAX_SELECTED_ITEMS) return false;
state->captures.push_back(capture_id);
}
return !state->captures.empty();
}
class JsonParser {
public:
explicit JsonParser(std::string_view text) : text_(text) {}
bool parse(std::map<std::string, std::string> *top_scalars) {
top_scalars_ = top_scalars;
skip_ws();
if (!parse_object(0, true)) return false;
skip_ws();
return at_ == text_.size();
}
private:
bool parse_object(size_t depth, bool top) {
if (depth > kJsonDepthMax || !take('{')) return false;
skip_ws(); if (take('}')) return true;
std::set<std::string> keys;
while (true) {
std::string key;
if (!parse_string(&key, true) || key.size() > kJsonKeyMax || !keys.insert(key).second) return false;
skip_ws(); if (!take(':')) return false; skip_ws();
if (!parse_value(depth + 1, top ? &key : nullptr)) return false;
skip_ws(); if (take('}')) return true; if (!take(',')) return false; skip_ws();
}
}
bool parse_array(size_t depth) {
if (depth > kJsonDepthMax || !take('[')) return false;
skip_ws(); if (take(']')) return true;
while (true) {
if (!parse_value(depth + 1, nullptr)) return false;
skip_ws(); if (take(']')) return true; if (!take(',')) return false; skip_ws();
}
}
bool parse_value(size_t depth, const std::string *top_key) {
if (depth > kJsonDepthMax || at_ >= text_.size()) return false;
if (text_[at_] == '{') return parse_object(depth, false);
if (text_[at_] == '[') return parse_array(depth);
if (text_[at_] == '"') {
std::string value;
if (!parse_string(top_key ? &value : nullptr, top_key != nullptr)) return false;
if (top_key) (*top_scalars_)[*top_key] = value;
return true;
}
size_t start = at_;
if (parse_literal("true") || parse_literal("false") || parse_literal("null") || parse_number()) {
if (top_key) (*top_scalars_)[*top_key] = std::string(text_.substr(start, at_ - start));
return true;
}
return false;
}
bool parse_string(std::string *out, bool capture) {
if (!take('"')) return false;
if (capture && out) out->clear();
while (at_ < text_.size()) {
unsigned char c = static_cast<unsigned char>(text_[at_++]);
if (c == '"') return true;
if (c < 0x20) return false;
if (c == '\\') {
if (at_ >= text_.size()) return false;
char e = text_[at_++];
if (e == 'u') {
if (at_ + 4 > text_.size()) return false;
for (size_t i = 0; i < 4; ++i) { unsigned v = 0; if (!hex_digit(text_[at_ + i], &v)) return false; }
at_ += 4;
} else if (std::string_view("\"\\/bfnrt").find(e) == std::string_view::npos) return false;
if (capture) return false;
} else if (capture && out) {
out->push_back(static_cast<char>(c));
}
}
return false;
}
bool parse_number() {
size_t p = at_;
if (p < text_.size() && text_[p] == '-') ++p;
if (p >= text_.size()) return false;
if (text_[p] == '0') ++p;
else {
if (text_[p] < '1' || text_[p] > '9') return false;
while (p < text_.size() && std::isdigit(static_cast<unsigned char>(text_[p]))) ++p;
}
if (p < text_.size() && text_[p] == '.') {
++p; size_t digits = p; while (p < text_.size() && std::isdigit(static_cast<unsigned char>(text_[p]))) ++p;
if (p == digits) return false;
}
if (p < text_.size() && (text_[p] == 'e' || text_[p] == 'E')) {
++p; if (p < text_.size() && (text_[p] == '+' || text_[p] == '-')) ++p;
size_t digits = p; while (p < text_.size() && std::isdigit(static_cast<unsigned char>(text_[p]))) ++p;
if (p == digits) return false;
}
at_ = p; return true;
}
bool parse_literal(std::string_view literal) {
if (text_.substr(at_, literal.size()) != literal) return false;
at_ += literal.size(); return true;
}
void skip_ws() { while (at_ < text_.size() && (text_[at_] == ' ' || text_[at_] == '\n' || text_[at_] == '\r' || text_[at_] == '\t')) ++at_; }
bool take(char c) { if (at_ >= text_.size() || text_[at_] != c) return false; ++at_; return true; }
std::string_view text_;
size_t at_ = 0;
std::map<std::string, std::string> *top_scalars_ = nullptr;
};
bool scalar(const std::map<std::string, std::string>& values, const char *key, std::string *out) {
auto it = values.find(key); if (it == values.end()) return false; *out = it->second; return true;
}
bool parse_json_top(std::string_view text, std::map<std::string, std::string> *top) {
if (text.empty() || text.find('\0') != std::string_view::npos) return false;
JsonParser parser(text); return parser.parse(top);
}
bool require_format(const std::map<std::string, std::string>& top, const char *expected) {
auto it = top.find("format"); return it != top.end() && it->second == expected;
}
} // namespace
extern "C" Lardon3DCalibrationWorkflowResult lardon3d_calibration_workflow_validate_input_boundary(
const Lardon3DCalibrationWorkflowInputFiles *files,
Lardon3DCalibrationWorkflowInputBoundary *boundary) {
if (!files || !boundary || !files->session_path || !files->detection_path || !files->solve_path ||
!files->evidence_path || !files->producer_path || !files->campaign_state_path)
return LARDON3D_CALIBRATION_WORKFLOW_INVALID_ARGUMENT;
std::memset(boundary, 0, sizeof(*boundary));
MappedFile session, detection, solve, evidence, producer, campaign;
MappedFile *mapped[] = {&session, &detection, &solve, &evidence, &producer, &campaign};
const char *paths[] = {files->session_path, files->detection_path, files->solve_path,
files->evidence_path, files->producer_path, files->campaign_state_path};
for (size_t i = 0; i < 6; ++i) {
Lardon3DCalibrationWorkflowResult r = map_regular_file(paths[i], mapped[i]);
if (r != LARDON3D_CALIBRATION_WORKFLOW_OK) return r;
}
SessionIdentity session_identity;
if (!parse_session(std::string_view(reinterpret_cast<const char *>(session.data), session.size), &session_identity))
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
CampaignState campaign_state;
if (!parse_campaign_state(std::string_view(reinterpret_cast<const char *>(campaign.data), campaign.size), &campaign_state))
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
std::map<std::string, std::string> detection_top, solve_top, evidence_top, producer_top;
if (!parse_json_top(std::string_view(reinterpret_cast<const char *>(detection.data), detection.size), &detection_top) ||
!parse_json_top(std::string_view(reinterpret_cast<const char *>(solve.data), solve.size), &solve_top) ||
!parse_json_top(std::string_view(reinterpret_cast<const char *>(evidence.data), evidence.size), &evidence_top) ||
!parse_json_top(std::string_view(reinterpret_cast<const char *>(producer.data), producer.size), &producer_top))
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
if (!require_format(detection_top, "L3DCAL_DETECTION_V1") || !require_format(solve_top, "L3DCAL_SOLVE_V1") ||
!require_format(evidence_top, "L3DCAL_EVIDENCE_BUNDLE_V1") || !require_format(producer_top, "L3DCAL_PRODUCER_V1"))
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
std::string detection_decoder, detection_version, evidence_optical, evidence_state;
std::string producer_executable, producer_configuration, producer_session, producer_optical, producer_threads;
if (!scalar(detection_top, "decoder", &detection_decoder) || !scalar(detection_top, "decoder_version", &detection_version) ||
!scalar(evidence_top, "optical_sha256", &evidence_optical) || !scalar(evidence_top, "optical_state", &evidence_state) ||
!scalar(producer_top, "solver_executable_sha256", &producer_executable) ||
!scalar(producer_top, "solver_configuration_sha256", &producer_configuration) ||
!scalar(producer_top, "session_sha256", &producer_session) || !scalar(producer_top, "optical_sha256", &producer_optical) ||
!scalar(producer_top, "threads", &producer_threads))
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
unsigned char evidence_optical_sha[32], producer_optical_sha[32], producer_session_sha[32];
unsigned char executable_sha[32], configuration_sha[32];
if (!parse_sha256(evidence_optical, evidence_optical_sha) || !parse_sha256(producer_optical, producer_optical_sha) ||
!parse_sha256(producer_session, producer_session_sha) || !parse_sha256(producer_executable, executable_sha) ||
!parse_sha256(producer_configuration, configuration_sha) || producer_threads != "1" ||
!nonzero_sha(executable_sha) || !nonzero_sha(configuration_sha))
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
if (detection_decoder != session_identity.decoder || detection_version != session_identity.decoder_version ||
std::memcmp(evidence_optical_sha, session_identity.optical_state_sha256, 32) != 0 ||
evidence_state != session_identity.optical_state_token ||
std::memcmp(producer_optical_sha, session_identity.optical_state_sha256, 32) != 0 ||
std::memcmp(producer_session_sha, session.sha256, 32) != 0 ||
std::memcmp(campaign_state.optical_state_sha256, session_identity.optical_state_sha256, 32) != 0 ||
campaign_state.optical_state_token != session_identity.optical_state_token)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
std::memcpy(boundary->session_sha256, session.sha256, 32);
std::memcpy(boundary->detection_sha256, detection.sha256, 32);
std::memcpy(boundary->solve_sha256, solve.sha256, 32);
std::memcpy(boundary->evidence_sha256, evidence.sha256, 32);
std::memcpy(boundary->producer_sha256, producer.sha256, 32);
std::memcpy(boundary->campaign_state_sha256, campaign.sha256, 32);
std::memcpy(boundary->optical_state_sha256, session_identity.optical_state_sha256, 32);
std::memcpy(boundary->solver_executable_sha256, executable_sha, 32);
std::memcpy(boundary->solver_configuration_sha256, configuration_sha, 32);
boundary->selected_execution_id = campaign_state.execution_id;
boundary->optical_configuration_id = campaign_state.optical_configuration_id;
boundary->capture_count = static_cast<uint32_t>(campaign_state.captures.size());
for (size_t i = 0; i < campaign_state.captures.size(); ++i) boundary->capture_ids[i] = campaign_state.captures[i];
std::memcpy(boundary->optical_state_token, session_identity.optical_state_token.data(), session_identity.optical_state_token.size());
boundary->optical_state_token[session_identity.optical_state_token.size()] = '\0';
return LARDON3D_CALIBRATION_WORKFLOW_OK;
}

View file

@ -0,0 +1,156 @@
#include <lardon3d/calibration_workflow.h>
#include <fcntl.h>
#include <openssl/evp.h>
#include <sys/stat.h>
#include <unistd.h>
#include <array>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#define CHECK(x) do { if (!(x)) { std::fprintf(stderr, "workflow failure %d: %s\n", __LINE__, #x); return false; } } while (0)
namespace {
std::string hex_repeat(char c) { return std::string(64, c); }
bool write_text(const std::filesystem::path& path, const std::string& text) {
std::ofstream out(path, std::ios::binary | std::ios::trunc);
out << text;
return static_cast<bool>(out);
}
std::string sha256_file(const std::filesystem::path& path) {
std::ifstream in(path, std::ios::binary);
std::ostringstream bytes; bytes << in.rdbuf();
std::string data = bytes.str();
unsigned char digest[32]{}; unsigned int size = 0;
if (!in || EVP_Digest(data.data(), data.size(), digest, &size, EVP_sha256(), nullptr) != 1 || size != 32) return {};
static const char digits[] = "0123456789abcdef";
std::string hex(64, '0');
for (size_t i = 0; i < 32; ++i) { hex[2*i] = digits[digest[i] >> 4]; hex[2*i+1] = digits[digest[i] & 15]; }
return hex;
}
struct Fixture {
std::filesystem::path root;
std::filesystem::path session, detection, solve, evidence, producer, campaign;
Lardon3DCalibrationWorkflowInputFiles files{};
};
std::string session_text(const std::string& optical) {
const std::string generator = hex_repeat('2'), planarity = hex_repeat('3'), image = hex_repeat('4');
std::ostringstream s;
s << "L3DCAL_SESSION_V1\n"
<< "target board " << generator << " DICT_5X5_100 9 7 30 21\n"
<< "measurement caliper 0.1 30 30 30 30 30 30 30 30 30 30\n"
<< "white_border 30\n"
<< "planarity PASS " << planarity << "\n"
<< "decoder qualified_decoder 1\n"
<< "optical_state " << optical << " body_objective_zoom_focus_stabilization_format_pipeline\n"
<< "image /nonexistent " << image << " 0\n"
<< "pre_solve " << image << " 0.1\n"
<< "clipping " << image << " 0.0\n"
<< "coordinate " << image << " qualified_decoder 1 0 100 100 20 0 0\n";
for (int i = 0; i < 20; ++i)
s << "coordinate_point " << image << " center " << i << " 0 " << i << " 0\n";
s << "distance " << image << " 0.4 1\n";
return s.str();
}
bool make_valid(Fixture *f) {
char temp[] = "/tmp/lardon3d-calibration-workflow-XXXXXX";
char *root = mkdtemp(temp); if (!root) return false;
f->root = root;
f->session = f->root / "session.l3dcal";
f->detection = f->root / "detection.json";
f->solve = f->root / "solve.json";
f->evidence = f->root / "evidence.json";
f->producer = f->root / "producer.json";
f->campaign = f->root / "campaign.l3dcal";
const std::string optical = hex_repeat('1');
if (!write_text(f->session, session_text(optical))) return false;
const std::string session_sha = sha256_file(f->session); if (session_sha.empty()) return false;
if (!write_text(f->detection,
"{\n\"format\":\"L3DCAL_DETECTION_V1\",\n\"decoder\":\"qualified_decoder\",\n\"decoder_version\":\"1\",\n\"views\":[]\n}\n")) return false;
if (!write_text(f->solve, "{\n\"format\":\"L3DCAL_SOLVE_V1\",\n\"runs\":[],\n\"fit_params\":[]\n}\n")) return false;
if (!write_text(f->evidence,
"{\n\"format\":\"L3DCAL_EVIDENCE_BUNDLE_V1\",\n\"optical_sha256\":\"" + optical +
"\",\n\"optical_state\":\"body_objective_zoom_focus_stabilization_format_pipeline\",\n\"validation_flags\":\"0xf\",\n\"residuals\":[]\n}\n")) return false;
if (!write_text(f->producer,
"{\n\"format\":\"L3DCAL_PRODUCER_V1\",\n\"solver_executable_sha256\":\"" + hex_repeat('a') +
"\",\n\"solver_configuration_sha256\":\"" + hex_repeat('b') +
"\",\n\"session_sha256\":\"" + session_sha +
"\",\n\"opencv_version\":\"5.0.0\",\n\"opencv_build_sha256\":\"" + hex_repeat('c') +
"\",\n\"threads\":1,\n\"rng_seed\":1278432342,\n\"optical_sha256\":\"" + optical + "\"\n}\n")) return false;
if (!write_text(f->campaign,
"L3DCAL_CAMPAIGN_STATE_V1\nexecution 42\noptical_configuration 7\noptical_state " + optical +
" body_objective_zoom_focus_stabilization_format_pipeline\ncapture 0 101\ncapture 1 102\n")) return false;
f->files = {f->session.c_str(), f->detection.c_str(), f->solve.c_str(), f->evidence.c_str(), f->producer.c_str(), f->campaign.c_str()};
return true;
}
bool valid_and_identity() {
Fixture f; CHECK(make_valid(&f));
Lardon3DCalibrationWorkflowInputBoundary boundary{};
CHECK(lardon3d_calibration_workflow_validate_input_boundary(&f.files, &boundary) == LARDON3D_CALIBRATION_WORKFLOW_OK);
CHECK(boundary.selected_execution_id == 42 && boundary.optical_configuration_id == 7 && boundary.capture_count == 2);
CHECK(boundary.capture_ids[0] == 101 && boundary.capture_ids[1] == 102);
CHECK(std::strcmp(boundary.optical_state_token, "body_objective_zoom_focus_stabilization_format_pipeline") == 0);
std::filesystem::remove_all(f.root); return true;
}
bool malformed_and_provenance() {
Fixture f; CHECK(make_valid(&f));
Lardon3DCalibrationWorkflowInputBoundary boundary{};
CHECK(write_text(f.solve, "{\"format\":\"L3DCAL_SOLVE_V1\",}"));
CHECK(lardon3d_calibration_workflow_validate_input_boundary(&f.files, &boundary) == LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE);
CHECK(boundary.selected_execution_id == 0);
CHECK(write_text(f.solve, "{\"format\":\"L3DCAL_SOLVE_V1\",\"runs\":[]}"));
std::string producer; { std::ifstream in(f.producer); std::ostringstream x; x << in.rdbuf(); producer = x.str(); }
const std::string actual = sha256_file(f.session); CHECK(!actual.empty());
const size_t at = producer.find(actual); CHECK(at != std::string::npos); producer.replace(at, 64, hex_repeat('0'));
CHECK(write_text(f.producer, producer));
CHECK(lardon3d_calibration_workflow_validate_input_boundary(&f.files, &boundary) == LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH);
std::filesystem::remove_all(f.root);
CHECK(make_valid(&f));
std::string campaign; { std::ifstream in(f.campaign); std::ostringstream x; x << in.rdbuf(); campaign = x.str(); }
const size_t opt = campaign.find(hex_repeat('1')); CHECK(opt != std::string::npos); campaign.replace(opt, 64, hex_repeat('9'));
CHECK(write_text(f.campaign, campaign));
CHECK(lardon3d_calibration_workflow_validate_input_boundary(&f.files, &boundary) == LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH);
std::filesystem::remove_all(f.root); return true;
}
bool file_boundary() {
Fixture f; CHECK(make_valid(&f));
Lardon3DCalibrationWorkflowInputBoundary boundary{};
const std::filesystem::path fifo = f.root / "fifo";
CHECK(mkfifo(fifo.c_str(), 0600) == 0);
auto saved = f.files.producer_path; f.files.producer_path = fifo.c_str();
CHECK(lardon3d_calibration_workflow_validate_input_boundary(&f.files, &boundary) == LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE);
f.files.producer_path = saved;
const std::filesystem::path link = f.root / "producer-link";
CHECK(symlink(f.producer.c_str(), link.c_str()) == 0); saved = f.files.producer_path; f.files.producer_path = link.c_str();
CHECK(lardon3d_calibration_workflow_validate_input_boundary(&f.files, &boundary) == LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE);
f.files.producer_path = saved;
const std::filesystem::path huge = f.root / "huge";
int fd = open(huge.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0600); CHECK(fd >= 0);
CHECK(ftruncate(fd, static_cast<off_t>(LARDON3D_CALIBRATION_WORKFLOW_MAX_FILE_BYTES) + 1) == 0); CHECK(close(fd) == 0);
saved = f.files.producer_path; f.files.producer_path = huge.c_str();
CHECK(lardon3d_calibration_workflow_validate_input_boundary(&f.files, &boundary) == LARDON3D_CALIBRATION_WORKFLOW_CAPACITY);
f.files.producer_path = saved;
std::filesystem::remove_all(f.root); return true;
}
} // namespace
int main() {
if (!valid_and_identity() || !malformed_and_provenance() || !file_boundary()) return 1;
std::puts("CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS");
return 0;
}