diff --git a/AGENTS.md b/AGENTS.md index a84fc36..eea1ea6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,13 +81,19 @@ Detailed subcontracts remain defined by their canonical documents. This file must not duplicate every S3 substage, scientific threshold, migration detail, or persistence format. -Project DB v25 is the current additive operational schema. It preserves v24 RAW batch, the v23 +Project DB v25 is an additive operational schema in the retained lineage. It preserves v24 RAW batch, the v23 optical overlay and the v22 scientific/persistence foundation. Its additive purpose is typed durable persistence for `features.extract.batch/1` through `feature_extract_batch_tasks`; it adds no scientific identity and must not reinterpret historical rows. The v25 Feature-batch path has completed its retained real A6000 proof. Historical references to older Project DB versions remain valid where they describe the actual historical contract or migration path. +Project DB v27 is the current additive operational schema. It preserves the v22 scientific/ +persistence foundation, v23 optical overlay, v24 RAW batch, v25 Feature batch and v26 exact +Capture geometric-state/applicability foundation. Its v27 focus-domain rows are generic bounded +exact-token machinery with no historical backfill and no device-specific physical applicability +claim. Future schema changes beyond v27 require explicit human authorization. + The global maintenance implementation, fresh portable/Vulkan/sanitizer/ concurrency validation and independent final review are acquired. Its lifecycle is `GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN`. The review independently passed the @@ -802,7 +808,7 @@ For Project DB: must not reinterpret scientific history; - v25 is the additive Feature-batch migration for `features.extract.batch/1` and must preserve v22/v23/v24 identities, rows and semantics; -- future schema-version changes beyond the current v25 head require +- future schema-version changes beyond the current v27 head require explicit human authorization; - migrations must be additive unless a different migration is explicitly authorized; @@ -1020,7 +1026,7 @@ Every completed ticket report must include: STOP and request a human decision only when resolution requires: - changing a FROZEN scientific contract; -- changing Project DB schema/version beyond the current v25 head without +- changing Project DB schema/version beyond the current v27 head without prior authorization; - introducing a genuinely new subsystem outside authorized scope; - files outside the authorized scope; diff --git a/README.md b/README.md index 2e382c5..df73c97 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ progressive observations and constraints ### Current Project Database -The current Project DB schema is **v26**. +The current Project DB schema is **v27**. The current head is additive: @@ -40,6 +40,7 @@ v23 Generic optical-context overlay v24 raw.develop.batch/1 persistence v25 features.extract.batch/1 persistence v26 Capture geometric state and exact calibration applicability +v27 Generic bounded exact-token focus domains ``` Earlier schema versions remain valid historical contracts where their own documentation says so. @@ -121,7 +122,7 @@ The following major foundations are implemented and validated at their documente - **Calibration Solver Preflight v1 — PASS** - **Calibration Evidence Solver v1 — IMPLEMENTED / VALIDATED** - **Calibration Tooling planarity alignment — PASS / FROZEN** -- **Project DB v24/v25/v26 operational overlays — IMPLEMENTED / VALIDATED** +- **Project DB v24/v25/v26/v27 operational overlays — IMPLEMENTED / VALIDATED** - raw.develop.batch/1 durable selected-execution path - features.extract.batch/1 durable selected-execution path diff --git a/docs/README.md b/docs/README.md index 7689472..3f04a44 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,14 +4,14 @@ ```text DOCUMENTATION_INDEX=CURRENT -CURRENT_PROJECT_DB_SCHEMA=v26 +CURRENT_PROJECT_DB_SCHEMA=v27 CURRENT_PRODUCTION_TASK_KINDS=16 REAL_S21_TRACKS=PASS/FROZEN REAL_A6000_PRE_SFM=PASS/FROZEN PRODUCT_DEFINITION_V1=PASS/FROZEN PROMPT_TREE=CURRENT -CURRENT_NEXT=AUTOFOCUS_V2_FOUNDATION +CURRENT_NEXT=PHYSICAL_AUTOFOCUS_OPTICAL_APPLICABILITY_VALIDATION ``` This index separates current authority, historical evidence and future product-definition work. @@ -35,7 +35,7 @@ quote an exact API, DDL or constant. ## Current repository state ```text -Project DB head v26 +Project DB head v27 Production Task kinds 16 v22 selected scientific execution foundation @@ -43,6 +43,7 @@ v23 generic optical-context overlay v24 raw.develop.batch/1 persistence v25 features.extract.batch/1 persistence v26 Capture geometric state/applicability +v27 generic bounded exact-token focus domains ``` Canonical resource objective: diff --git a/docs/architecture/calibration_science_v2.md b/docs/architecture/calibration_science_v2.md index 9f8edb0..6618a06 100644 --- a/docs/architecture/calibration_science_v2.md +++ b/docs/architecture/calibration_science_v2.md @@ -265,6 +265,36 @@ Historical EXIF/MakerNote values such as Sony `FocusPosition2` or derived `FocusDistance2` may inform experiment design. They are not by themselves physical calibration evidence. +The implemented generic Stage E foundation represents a validated discrete +domain as: + +```text +existing exact calibration applicability ++ positive domain format/version ++ nonzero retained-evidence/provenance SHA-256 ++ exact complete exemplar state for every non-focus geometry field ++ 1..64 explicit opaque observed focus tokens +``` + +The 64-token limit is an operational persistence/API bound, not a scientific +focus range. Domain membership is exact token equality. Tokens have no numeric +distance, order, magnitude, adjacency or EXIF interpretation, and the generic +machinery performs no interpolation or extrapolation. The target configuration +and every non-focus geometric-state and provenance field must exactly match the +exemplar. Unknown/incomplete focus remains `CALIBRATION_REQUIRED`. + +An exact applicability and its attached domain retain one applicability and +calibration identity. Candidate enumeration deduplicates that identity if both +the exact exemplar state and a domain member match. Distinct overlapping +applicabilities remain ambiguous and produce `SELECTION_REQUIRED`. + +This generic representation does not itself validate a real device domain. +In particular: + +```text +A6000_E_PZ_16_50_AF_APPLICABILITY=BLOCKED_BY_PHYSICAL_VALIDATION +``` + ### 9.3 Discrete focus bands If one calibration is not valid across the complete autofocus range, v2 may diff --git a/docs/architecture/project_database.md b/docs/architecture/project_database.md index 8c1c45a..e17b1b3 100644 --- a/docs/architecture/project_database.md +++ b/docs/architecture/project_database.md @@ -7,7 +7,7 @@ span schema versions. Historical sections remain authoritative for the version t present-tense statements in this document use the current schema head. ```text -CURRENT_PROJECT_DB_SCHEMA=v26 +CURRENT_PROJECT_DB_SCHEMA=v27 ``` The current head is additive: @@ -18,10 +18,11 @@ v23 Generic optical-context overlay IMPLEMENTED / VALI v24 raw.develop.batch/1 persistence IMPLEMENTED / VALIDATED v25 features.extract.batch/1 persistence IMPLEMENTED / VALIDATED v26 Capture geometric state and exact calibration applicability PASS / FROZEN +v27 Generic exact-token focus-domain applicability PASS / FROZEN ``` -Project DB v26 preserves the scientific and persistence meaning of every earlier retained row. No -migration from v22 through v26 backfills scientific identity, infers camera/lens identity, invents +Project DB v27 preserves the scientific and persistence meaning of every earlier retained row. No +migration from v22 through v27 backfills scientific identity, infers camera/lens identity, invents calibration, rewrites Capture identity, or changes historical Task payload interpretation. The current real A6000 pre-SfM proof completed the v24 RAW-batch and v25 Feature-batch paths before @@ -233,6 +234,37 @@ The v1 optical-profile and calibration-selection APIs/tables remain unchanged. The v26 tables are a separate additive foundation; they do not attach a Sparse calibration scope or transition a selected execution to `READY`. +### Project DB v27 - generic validated focus domains + +**PASS / FROZEN.** + +The v26 -> v27 migration is transactional, additive and DDL-only. It creates +empty `optical_focus_domains_v2` and `optical_focus_domain_tokens_v2` tables. +Historical focus observations are not physical validation, so migration +creates no inferred domain, token member, applicability, selection or +calibration identity. + +A v27 domain attaches to one existing v26 applicability. It durably retains a +positive version, a nonzero 32-byte retained-evidence/provenance digest and a +bounded set of 1..64 distinct opaque focus tokens. Tokens are stored in +deterministic lexical order, but scientific matching is exact byte-for-byte +token membership; there is no numeric distance/range comparison, EXIF +assumption, interpolation or extrapolation. The operational 64-token capacity +is not a scientific dataset or focus-range limit. + +Resolution preserves the v26 zero/one/many contract while enumerating the +distinct union of exact v26 matches and eligible domain matches. A domain match +requires the exact configuration, exact complete exemplar state for every +non-focus geometry/provenance field, observed target focus and exact token +membership. Exact and domain paths for the same applicability are deduplicated; +different overlapping applicabilities produce `SELECTION_REQUIRED`. The +existing v2 selection API remains immutable/idempotent and accepts a domain +applicability only while it is exactly eligible. + +This schema is generic machinery, not device-specific autofocus evidence. +`A6000_E_PZ_16_50_AF_APPLICABILITY=BLOCKED_BY_PHYSICAL_VALIDATION` remains in +force. + ## Optical TUI workflow The optical TUI uses bounded public APIs and does not write SQLite directly. It can: @@ -893,6 +925,7 @@ v22 -> v23 generic optical-context overlay v23 -> v24 selected RAW-batch Task persistence v24 -> v25 selected Feature-batch Task persistence v25 -> v26 Capture geometric state + exact calibration applicability +v26 -> v27 generic bounded exact-token focus domains ``` Historical version-specific contracts remain valid for the rows and checkpoints they describe. @@ -900,9 +933,9 @@ An older version number is not stale when the text explicitly describes historic ## Opening and migration -An empty Project DB is created and migrated through the complete known chain to v26. +An empty Project DB is created and migrated through the complete known chain to v27. -A supported historical DB is migrated sequentially to v26. Each migration is transactional. A +A supported historical DB is migrated sequentially to v27. Each migration is transactional. A migration failure rolls back both newly created objects and the schema-version marker for that step, leaving the prior version complete and retryable. @@ -913,7 +946,7 @@ The implementation rejects: - impossible version/storage-class combinations; - malformed required durable relationships. -The migration implementation must recognize only the known sequential range through v26. It must not +The migration implementation must recognize only the known sequential range through v27. It must not skip an intermediate contract. Important rollback properties retained from historical tests include: @@ -927,6 +960,7 @@ Important rollback properties retained from historical tests include: - v24 failure leaves no partial RAW-batch table/marker and a true v23; - v25 failure leaves no partial Feature-batch table/marker and a true v24. - v26 failure leaves no partial geometric-state/applicability tables or marker and a true v25. +- v27 failure leaves no partial focus-domain tables or marker and a true v26. Exact executable migration SQL is owned by `src/project_db.c`. Documentation may summarize it, but must not contradict the source or the FROZEN migration tests. @@ -1089,20 +1123,21 @@ must not be silently reinterpreted as a scientific dataset limit. ## Status summary ```text -CURRENT_PROJECT_DB_SCHEMA=v26 +CURRENT_PROJECT_DB_SCHEMA=v27 v22 selected scientific execution foundation PASS/FROZEN v23 generic optical-context overlay IMPLEMENTED/VALIDATED/REVIEWED v24 raw.develop.batch/1 persistence IMPLEMENTED/VALIDATED v25 features.extract.batch/1 persistence IMPLEMENTED/VALIDATED v26 Capture geometric state/applicability PASS/FROZEN +v27 generic exact-token focus domains PASS/FROZEN REAL_S21_TRACKS PASS/FROZEN REAL_A6000_PRE_SFM PASS/FROZEN ``` Current Project DB opens and migrates supported historical databases through the sequential known -chain to v26. +chain to v27. Historical contracts for Candidate Pair, Matcher, Geometric Verification, Tracks, Sparse SfM, Phase H, Capture/Asset Provenance, campaign execution, Photo Quality and selected scientific @@ -1119,4 +1154,4 @@ No current Project DB migration: - creates a generic dependency DAG; - persists active Resource Governor reservations. -Future schema changes beyond v26 require explicit human authorization. +Future schema changes beyond v27 require explicit human authorization. diff --git a/docs/product/product_definition.md b/docs/product/product_definition.md index 34fe6e9..e72dab2 100644 --- a/docs/product/product_definition.md +++ b/docs/product/product_definition.md @@ -111,7 +111,7 @@ This document may freeze a product requirement while its implementation remains The following existing boundaries are consumed as-is: ```text -Project DB head v26 +Project DB head v27 Production Task kinds 16 Capture / Asset Provenance PASS/FROZEN Acquisition / campaign execution PASS/FROZEN diff --git a/docs/roadmap/roadmap.md b/docs/roadmap/roadmap.md index 4ae0241..5a0149d 100644 --- a/docs/roadmap/roadmap.md +++ b/docs/roadmap/roadmap.md @@ -11,7 +11,7 @@ It must not be used to reinterpret FROZEN scientific evidence. ## Current repository state ```text -Project DB current schema v26 +Project DB current schema v27 Production Task kinds 16 GLOBAL_MAINTENANCE_AUDIT PASS/FROZEN REAL_S21_TRACKS PASS/FROZEN @@ -24,7 +24,7 @@ SOURCE_COMMENT_AUDIT PASS PRODUCT_DEFINITION PASS/FROZEN PROMPT_TREE CURRENT USER_FACING_UI_LANGUAGE_NORMALIZATION PASS -CURRENT_NEXT AUTOFOCUS_V2_FOUNDATION +CURRENT_NEXT PHYSICAL_AUTOFOCUS_OPTICAL_APPLICABILITY_VALIDATION ``` The current Project DB head is additive: @@ -35,6 +35,7 @@ v23 generic optical-context overlay v24 raw.develop.batch/1 persistence v25 features.extract.batch/1 persistence v26 Capture geometric state + exact calibration applicability +v27 generic bounded exact-token focus domains ``` Historical references to earlier versions remain valid when they describe the state of their own @@ -539,8 +540,10 @@ additive L3DCALB2/Tooling/Bootstrap v2 now retains independent group-local prove complete exact selected-image coverage, and attaches one existing per-image scope only after complete 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. +dependency is physical autofocus/optical applicability validation and a dedicated calibrated real +campaign. The generic adaptive-settings audit and bounded exact-token autofocus foundation are +PASS/FROZEN. 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. @@ -934,12 +937,16 @@ SOURCE_COMMENT_AUDIT PASS SOURCE_COMMENT_REMEDIATION PASS PRODUCT_DEFINITION_V1 PASS/FROZEN PROMPT_TREE CURRENT -CALIBRATION_EVIDENCE_SOLVER_V1 IMPLEMENTED/VALIDATED -CALIBRATION_TOOLING_ALIGNMENT PASS/FROZEN -CURRENT_NEXT DEDICATED_PHYSICAL_CALIBRATED_REAL_CAMPAIGN +CALIBRATION_SCIENCE_V2 PASS/FROZEN +CALIBRATION_V2_HETEROGENEOUS_OPTICS PASS/FROZEN +CALIBRATION_WORKFLOW_V2 PASS/FROZEN +ADAPTIVE_CAPTURE_SETTINGS_CONTRACT PASS/FROZEN +AUTOFOCUS_V2_FOUNDATION PASS/FROZEN +CURRENT_NEXT PHYSICAL_AUTOFOCUS_OPTICAL_APPLICABILITY_VALIDATION ``` Implementation proceeds only through explicitly human-authorized tranches under `prompt.md` and the -numbered `prompt/` execution contract. The current authorized dependency is the final usable -calibration workflow; its next missing sub-boundary composes the FROZEN Tooling -and Bootstrap importer to reach truthful READY from the validated binding. +numbered `prompt/` execution contract. The current dependency is physical autofocus/optical +applicability validation for real equipment, followed by a dedicated calibrated real campaign. +Generic autofocus applicability machinery is available, but the real +A6000 + E PZ 16-50 autofocus applicability remains blocked until physical evidence validates it. diff --git a/include/lardon3d/optical_profiles.h b/include/lardon3d/optical_profiles.h index 4bad529..b5ed06d 100644 --- a/include/lardon3d/optical_profiles.h +++ b/include/lardon3d/optical_profiles.h @@ -15,6 +15,10 @@ enum { LARDON3D_OPTICAL_TEXT_CAPACITY = 128, LARDON3D_OPTICAL_PROVENANCE_CAPACITY = 256, LARDON3D_OPTICAL_PAGE_MAX = 128, + LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE = 32, + /* This is an operational persistence/API bound, not a scientific focus + * range. Every member remains one opaque exact observation token. */ + LARDON3D_OPTICAL_FOCUS_DOMAIN_TOKEN_MAX = 64, }; typedef enum { @@ -171,6 +175,19 @@ typedef struct { uint64_t exemplar_capture_id; } Lardon3DOpticalCalibrationApplicabilityV2; +typedef struct { + uint64_t focus_domain_id; + /* The v26 applicability retains calibration/configuration identity and its + * exemplar retains the exact complete non-focus geometric tuple. */ + uint64_t applicability_id; + uint64_t calibration_profile_id; + uint64_t optical_configuration_id; + uint64_t exemplar_capture_id; + uint32_t domain_version; + unsigned char evidence_sha256[LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE]; + uint32_t token_count; +} Lardon3DOpticalFocusDomainV2; + typedef struct { Lardon3DOpticalCalibrationResolutionKind kind; uint64_t applicability_id; @@ -333,15 +350,35 @@ Lardon3DProjectDbResult lardon3d_optical_calibration_applicability_v2_create( uint64_t exemplar_capture_id, Lardon3DOpticalCalibrationApplicabilityV2 *output); -/* Resolution counts exact valid applicability rows. NONE is CALIBRATION_REQUIRED, - * ONE is RESOLVED, and MANY is SELECTION_REQUIRED. All are successful outcomes. */ +/* Attach one physically validated discrete focus domain to an existing exact + * v26 applicability. evidence_sha256 must be a nonzero retained-evidence or + * provenance digest. focus_tokens contains 1..FOCUS_DOMAIN_TOKEN_MAX distinct, + * nonempty, NUL-terminated opaque tokens; the call borrows the array and + * strings only for its duration. Members are stored as a deterministic exact + * set: order has no meaning, exact retry is idempotent, and conflicting reuse + * of the applicability is CONSTRAINT. The API performs no numeric conversion, + * EXIF interpretation, interpolation, extrapolation, or physical validation. + * output is mandatory. On OK, it receives the durable focus domain and its + * attached applicability identity; for valid calls it is zeroed on non-OK. */ +Lardon3DProjectDbResult lardon3d_optical_focus_domain_v2_create( + Lardon3DProjectDb *database, uint64_t applicability_id, + uint32_t domain_version, + const unsigned char evidence_sha256[LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE], + const char *const *focus_tokens, size_t token_count, + Lardon3DOpticalFocusDomainV2 *output); + +/* Resolution counts distinct eligible applicability rows: exact v26 state + * matches plus v27 domains whose non-focus geometry is exact and whose focus + * token is an exact member. NONE is CALIBRATION_REQUIRED, ONE is RESOLVED, and + * MANY is SELECTION_REQUIRED. All are successful outcomes. */ Lardon3DProjectDbResult lardon3d_optical_capture_calibration_resolve_v2( Lardon3DProjectDb *database, uint64_t capture_id, Lardon3DOpticalCalibrationResolutionV2 *output); -/* Selection requires a complete observed geometric-state tuple and an exactly - * compatible applicability; UNKNOWN in any geometry-relevant field is a - * CONSTRAINT. The first selection is immutable, exact retry is idempotent, and - * a conflicting applicability is rejected without changing the selection. */ +/* Selection requires a complete observed geometric-state tuple and an + * applicability currently eligible by exact v26 match or exact v27 domain + * membership; UNKNOWN in any geometry-relevant field is a CONSTRAINT. The + * first selection is immutable, exact retry is idempotent, and a conflicting + * applicability is rejected without changing the selection. */ Lardon3DProjectDbResult lardon3d_optical_capture_calibration_select_v2( Lardon3DProjectDb *database, uint64_t capture_id, uint64_t applicability_id); /* Loads the Capture's durable v2 selection without creating or changing it. diff --git a/include/lardon3d/project_db.h b/include/lardon3d/project_db.h index 9b859d2..8b4797e 100644 --- a/include/lardon3d/project_db.h +++ b/include/lardon3d/project_db.h @@ -11,10 +11,10 @@ #include enum { - /* v26 additively stores Capture-owned observed geometric state and exact - * calibration applicability. Migration does not infer state from v23 - * configuration or historical calibration selections. */ - LARDON3D_PROJECT_DB_SCHEMA_VERSION = 26, + /* v27 additively stores validated focus domains as bounded sets of exact + * observed tokens attached to v26 applicability. Migration creates no + * domain or token from historical geometric state. */ + LARDON3D_PROJECT_DB_SCHEMA_VERSION = 27, LARDON3D_PROJECT_DB_ID_CAPACITY = 65, LARDON3D_PROJECT_DB_KIND_CAPACITY = 65, LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096, diff --git a/prompt.md b/prompt.md index 75ae710..f5c1afa 100644 --- a/prompt.md +++ b/prompt.md @@ -79,7 +79,7 @@ Do not return after each file. Do not repeat global A-to-Z audits when unchanged This contract was prepared against the repository state that declares: ```text -CURRENT_PROJECT_DB_SCHEMA=v26 +CURRENT_PROJECT_DB_SCHEMA=v27 PRODUCTION_TASK_KINDS=16 PRODUCT_DEFINITION_V1=PASS/FROZEN PROMPT_TREE=NEXT diff --git a/prompt/02_CURRENT_FROZEN_STATE.md b/prompt/02_CURRENT_FROZEN_STATE.md index 9b6be58..e6edeee 100644 --- a/prompt/02_CURRENT_FROZEN_STATE.md +++ b/prompt/02_CURRENT_FROZEN_STATE.md @@ -3,10 +3,10 @@ ## Status ```text -CURRENT_PROJECT_DB_SCHEMA=v26 +CURRENT_PROJECT_DB_SCHEMA=v27 PRODUCTION_TASK_KINDS=16 USER_FACING_UI_LANGUAGE_NORMALIZATION=PASS -CURRENT_IMPLEMENTATION_CURSOR=ADAPTIVE_CAPTURE_SETTINGS_AND_AUTOFOCUS_V2_FOUNDATION +CURRENT_IMPLEMENTATION_CURSOR=PHYSICAL_AUTOFOCUS_OPTICAL_APPLICABILITY_VALIDATION ``` ## Authority @@ -16,7 +16,7 @@ CURRENT_IMPLEMENTATION_CURSOR=ADAPTIVE_CAPTURE_SETTINGS_AND_AUTOFOCUS_V2_FOUNDAT ## CURRENT ```text -Project DB head v26 +Project DB head v27 Production Task kinds 16 Feature Store IMPLEMENTED Visual Index IMPLEMENTED @@ -39,8 +39,8 @@ 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 +Adaptive capture settings semantics PASS/FROZEN +Autofocus v2 foundation PASS/FROZEN Calibration Tooling planarity alignment PASS/FROZEN ``` diff --git a/prompt/12_CALIBRATION.md b/prompt/12_CALIBRATION.md index 1c40105..cdd5371 100644 --- a/prompt/12_CALIBRATION.md +++ b/prompt/12_CALIBRATION.md @@ -21,9 +21,9 @@ CALIBRATION_SCIENCE_V2=PASS/FROZEN CALIBRATION_V2_HETEROGENEOUS_OPTICS=PASS/FROZEN CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION=PASS/FROZEN CALIBRATION_V2_WORKFLOW_READY=PASS/FROZEN -ADAPTIVE_CAPTURE_SETTINGS_CONTRACT=PLANNED -AUTOFOCUS_V2_FOUNDATION=PLANNED -CURRENT_CALIBRATION_NEXT=AUTOFOCUS_V2_FOUNDATION +ADAPTIVE_CAPTURE_SETTINGS_CONTRACT=PASS/FROZEN +AUTOFOCUS_V2_FOUNDATION=PASS/FROZEN +CURRENT_CALIBRATION_NEXT=PHYSICAL_AUTOFOCUS_OPTICAL_APPLICABILITY_VALIDATION ``` ## Authority diff --git a/prompt/31_IMPLEMENTATION_ORDER.md b/prompt/31_IMPLEMENTATION_ORDER.md index 7f672c0..97df2d2 100644 --- a/prompt/31_IMPLEMENTATION_ORDER.md +++ b/prompt/31_IMPLEMENTATION_ORDER.md @@ -6,7 +6,7 @@ IMPLEMENTATION_ORDER=DEPENDENCY_DRIVEN IMPLEMENTATION_AUTHORIZATION=NO STEP_0_USER_FACING_LANGUAGE_NORMALIZATION=PASS -CURRENT_NEXT=AUTOFOCUS_V2_FOUNDATION +CURRENT_NEXT=PHYSICAL_AUTOFOCUS_OPTICAL_APPLICABILITY_VALIDATION ``` ## Authority @@ -25,8 +25,8 @@ Default dependency order: 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 — PASS/FROZEN; -6. Adaptive capture settings / generic autofocus foundation — CURRENT; -7. physical autofocus/optical applicability validation and dedicated calibrated real campaign; +6. Adaptive capture settings / generic autofocus foundation — PASS/FROZEN; +7. physical autofocus/optical applicability validation and dedicated calibrated real campaign — CURRENT; 8. real Sparse SfM proof; 9. durable Dense/OpenMVS orchestration; 10. mesh / refinement / texturing / export; diff --git a/src/optical_profiles.c b/src/optical_profiles.c index 5d7881e..238e2bd 100644 --- a/src/optical_profiles.c +++ b/src/optical_profiles.c @@ -2338,6 +2338,21 @@ static const char exact_state_predicate[] = "t.decoded_geometry_state=e.decoded_geometry_state AND " "t.decoded_width=e.decoded_width AND t.decoded_height=e.decoded_height"; +/* A discrete focus domain varies only the exact opaque focus token. Every + other geometry and observation-provenance field remains exemplar-exact. */ +static const char exact_nonfocus_state_predicate[] = + "t.optical_configuration_id=e.optical_configuration_id AND " + "t.state_version=e.state_version AND t.provenance=e.provenance AND " + "t.aperture_state=e.aperture_state AND t.aperture_x1000=e.aperture_x1000 " + "AND t.stabilization=e.stabilization AND t.crop_state=e.crop_state AND " + "t.crop_observation=e.crop_observation AND " + "t.pipeline_state=e.pipeline_state AND " + "t.pipeline_observation=e.pipeline_observation AND " + "t.representation_state=e.representation_state AND " + "t.representation_observation=e.representation_observation AND " + "t.decoded_geometry_state=e.decoded_geometry_state AND " + "t.decoded_width=e.decoded_width AND t.decoded_height=e.decoded_height"; + Lardon3DProjectDbResult lardon3d_optical_calibration_applicability_v2_create( Lardon3DProjectDb *database, uint64_t calibration_profile_id, uint64_t exemplar_capture_id, @@ -2429,11 +2444,233 @@ Lardon3DProjectDbResult lardon3d_optical_calibration_applicability_v2_create( return result; } +static bool focus_domain_digest_valid( + const unsigned char digest[LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE]) { + if (!digest) + return false; + for (size_t index = 0; index < LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE; + ++index) + if (digest[index] != 0) + return true; + return false; +} + +static bool focus_domain_tokens_valid(const char *const *tokens, size_t count) { + if (!tokens || count == 0 || + count > LARDON3D_OPTICAL_FOCUS_DOMAIN_TOKEN_MAX) + return false; + for (size_t index = 0; index < count; ++index) { + if (!optical_text(tokens[index], LARDON3D_OPTICAL_TEXT_CAPACITY, false)) + return false; + for (size_t previous = 0; previous < index; ++previous) + if (strcmp(tokens[index], tokens[previous]) == 0) + return false; + } + return true; +} + +Lardon3DProjectDbResult lardon3d_optical_focus_domain_v2_create( + Lardon3DProjectDb *database, uint64_t applicability_id, + uint32_t domain_version, + const unsigned char evidence_sha256[LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE], + const char *const *focus_tokens, size_t token_count, + Lardon3DOpticalFocusDomainV2 *output) { + if (output) + memset(output, 0, sizeof(*output)); + if (!database || !optical_id(applicability_id) || domain_version == 0 || + !focus_domain_digest_valid(evidence_sha256) || + !focus_domain_tokens_valid(focus_tokens, token_count) || !output) + return LARDON3D_PROJECT_DB_INVALID_ARGUMENT; + + /* Canonical lexical order makes a domain's durable representation + independent of caller ordering while retaining tokens byte-for-byte. */ + const char *ordered[LARDON3D_OPTICAL_FOCUS_DOMAIN_TOKEN_MAX]; + for (size_t index = 0; index < token_count; ++index) { + size_t position = index; + while (position > 0 && strcmp(focus_tokens[index], ordered[position - 1]) < 0) { + ordered[position] = ordered[position - 1]; + --position; + } + ordered[position] = focus_tokens[index]; + } + + (void)pthread_mutex_lock(&database->mutex); + Lardon3DProjectDbResult result = execute( + database, "BEGIN IMMEDIATE", "begin optical focus domain v2"); + sqlite3_stmt *statement = NULL; + uint64_t calibration_profile_id = 0; + uint64_t optical_configuration_id = 0; + uint64_t exemplar_capture_id = 0; + if (result == LARDON3D_PROJECT_DB_OK) + result = prepare( + database, + "SELECT calibration_profile_id,optical_configuration_id," + "exemplar_capture_id FROM optical_calibration_applicabilities_v2 " + "WHERE applicability_id=?1", + &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)applicability_id); + int code = sqlite3_step(statement); + if (code == SQLITE_DONE) + result = LARDON3D_PROJECT_DB_NOT_FOUND; + else if (code != SQLITE_ROW) + result = sqlite_result(database, code, "load focus domain applicability"); + else if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER || + sqlite3_column_type(statement, 1) != SQLITE_INTEGER || + sqlite3_column_type(statement, 2) != SQLITE_INTEGER || + sqlite3_column_int64(statement, 0) <= 0 || + sqlite3_column_int64(statement, 1) <= 0 || + sqlite3_column_int64(statement, 2) <= 0) + result = LARDON3D_PROJECT_DB_CORRUPT; + else { + calibration_profile_id = (uint64_t)sqlite3_column_int64(statement, 0); + optical_configuration_id = (uint64_t)sqlite3_column_int64(statement, 1); + exemplar_capture_id = (uint64_t)sqlite3_column_int64(statement, 2); + if (sqlite3_step(statement) != SQLITE_DONE) + result = LARDON3D_PROJECT_DB_CORRUPT; + } + } + (void)sqlite3_finalize(statement); + statement = NULL; + + if (result == LARDON3D_PROJECT_DB_OK) + result = prepare( + database, + "INSERT OR IGNORE INTO optical_focus_domains_v2(applicability_id," + "domain_version,evidence_sha256,token_count) VALUES(?1,?2,?3,?4)", + &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)applicability_id); + sqlite3_bind_int64(statement, 2, (sqlite3_int64)domain_version); + sqlite3_bind_blob(statement, 3, evidence_sha256, + LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE, + SQLITE_TRANSIENT); + sqlite3_bind_int64(statement, 4, (sqlite3_int64)token_count); + result = step_done(database, statement, "insert optical focus domain v2"); + statement = NULL; + } + + uint64_t focus_domain_id = 0; + if (result == LARDON3D_PROJECT_DB_OK) + result = prepare( + database, + "SELECT focus_domain_id,domain_version,evidence_sha256,token_count FROM " + "optical_focus_domains_v2 WHERE applicability_id=?1", + &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)applicability_id); + int code = sqlite3_step(statement); + if (code != SQLITE_ROW) + result = code == SQLITE_DONE + ? LARDON3D_PROJECT_DB_CORRUPT + : sqlite_result(database, code, "load optical focus domain v2"); + else { + sqlite3_int64 stored_id = sqlite3_column_int64(statement, 0); + sqlite3_int64 stored_version = sqlite3_column_int64(statement, 1); + sqlite3_int64 stored_count = sqlite3_column_int64(statement, 3); + const unsigned char *stored_digest = sqlite3_column_blob(statement, 2); + if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER || + sqlite3_column_type(statement, 1) != SQLITE_INTEGER || + sqlite3_column_type(statement, 2) != SQLITE_BLOB || + sqlite3_column_type(statement, 3) != SQLITE_INTEGER || stored_id <= 0 || + stored_version <= 0 || stored_version > UINT32_MAX || + sqlite3_column_bytes(statement, 2) != + LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE || + !focus_domain_digest_valid(stored_digest) || stored_count <= 0 || + stored_count > LARDON3D_OPTICAL_FOCUS_DOMAIN_TOKEN_MAX) { + result = LARDON3D_PROJECT_DB_CORRUPT; + } else if (stored_version != (sqlite3_int64)domain_version || + memcmp(stored_digest, evidence_sha256, + LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE) != 0 || + stored_count != (sqlite3_int64)token_count) { + result = LARDON3D_PROJECT_DB_CONSTRAINT; + } else { + focus_domain_id = (uint64_t)stored_id; + if (sqlite3_step(statement) != SQLITE_DONE) + result = LARDON3D_PROJECT_DB_CORRUPT; + } + } + } + (void)sqlite3_finalize(statement); + statement = NULL; + + for (size_t index = 0; index < token_count && + result == LARDON3D_PROJECT_DB_OK; + ++index) { + result = prepare( + database, + "INSERT OR IGNORE INTO optical_focus_domain_tokens_v2 " + "VALUES(?1,?2,?3)", + &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)focus_domain_id); + sqlite3_bind_int64(statement, 2, (sqlite3_int64)index); + sqlite3_bind_text(statement, 3, ordered[index], -1, SQLITE_TRANSIENT); + result = step_done(database, statement, "insert focus domain token v2"); + statement = NULL; + } + } + if (result == LARDON3D_PROJECT_DB_OK) + result = prepare( + database, + "SELECT token_ordinal,focus_token FROM optical_focus_domain_tokens_v2 " + "WHERE focus_domain_id=?1 ORDER BY token_ordinal", + &statement); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(statement, 1, (sqlite3_int64)focus_domain_id); + for (size_t index = 0; index < token_count; ++index) { + int code = sqlite3_step(statement); + if (code != SQLITE_ROW) { + result = code == SQLITE_DONE ? LARDON3D_PROJECT_DB_CORRUPT + : sqlite_result(database, code, + "load focus domain token v2"); + break; + } + if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER || + sqlite3_column_int64(statement, 0) != (sqlite3_int64)index || + sqlite3_column_type(statement, 1) != SQLITE_TEXT || + sqlite3_column_bytes(statement, 1) <= 0 || + sqlite3_column_bytes(statement, 1) >= LARDON3D_OPTICAL_TEXT_CAPACITY) { + result = LARDON3D_PROJECT_DB_CORRUPT; + break; + } + if (!optical_column_equals(statement, 1, ordered[index])) { + result = LARDON3D_PROJECT_DB_CONSTRAINT; + break; + } + } + if (result == LARDON3D_PROJECT_DB_OK && + sqlite3_step(statement) != SQLITE_DONE) + result = LARDON3D_PROJECT_DB_CORRUPT; + } + (void)sqlite3_finalize(statement); + + result = optical_commit_or_rollback(database, result, + "commit optical focus domain v2", + "rollback optical focus domain v2"); + (void)pthread_mutex_unlock(&database->mutex); + if (result == LARDON3D_PROJECT_DB_OK) { + output->focus_domain_id = focus_domain_id; + output->applicability_id = applicability_id; + output->calibration_profile_id = calibration_profile_id; + output->optical_configuration_id = optical_configuration_id; + output->exemplar_capture_id = exemplar_capture_id; + output->domain_version = domain_version; + memcpy(output->evidence_sha256, evidence_sha256, + LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE); + output->token_count = (uint32_t)token_count; + } else { + memset(output, 0, sizeof(*output)); + } + return result; +} + static Lardon3DProjectDbResult -exact_candidates_locked(Lardon3DProjectDb *database, uint64_t capture_id, - uint64_t required_applicability, - Lardon3DOpticalCalibrationResolutionV2 *output, - size_t *count) { +applicability_candidates_locked(Lardon3DProjectDb *database, + uint64_t capture_id, + uint64_t required_applicability, + Lardon3DOpticalCalibrationResolutionV2 *output, + size_t *count) { /* A broken v2 dependency is corruption, not evidence that calibration is required. Validate the target configuration before exact-state filtering. */ sqlite3_stmt *validation = NULL; @@ -2461,13 +2698,58 @@ exact_candidates_locked(Lardon3DProjectDb *database, uint64_t capture_id, (void)sqlite3_finalize(validation); if (result != LARDON3D_PROJECT_DB_OK) return result; + validation = NULL; - char query[1800]; + /* Domain rows are retained scientific evidence. Broken dependencies, + incomplete exemplars or token-set corruption must fail closed before + enumeration; a domain never supplies evidence missing from its exemplar. */ + result = prepare( + database, + "SELECT d.focus_domain_id FROM optical_focus_domains_v2 d LEFT JOIN " + "optical_calibration_applicabilities_v2 a ON " + "a.applicability_id=d.applicability_id LEFT JOIN capture_geometric_states e " + "ON e.capture_id=a.exemplar_capture_id AND " + "e.optical_configuration_id=a.optical_configuration_id WHERE " + "a.applicability_id IS NULL OR (a.optical_configuration_id=(SELECT " + "optical_configuration_id FROM capture_geometric_states WHERE capture_id=?1) " + "AND (e.capture_id IS NULL OR e.focus_state!=2 OR e.aperture_state!=2 OR " + "e.stabilization=0 OR e.crop_state!=2 OR e.pipeline_state!=2 OR " + "e.representation_state!=2 OR e.decoded_geometry_state!=2 OR " + "typeof(d.domain_version)!='integer' OR d.domain_version<=0 OR " + "typeof(d.evidence_sha256)!='blob' OR length(d.evidence_sha256)!=32 OR " + "d.evidence_sha256=zeroblob(32) OR typeof(d.token_count)!='integer' OR " + "d.token_count<=0 OR d.token_count>64 OR d.token_count!=(SELECT COUNT(*) " + "FROM optical_focus_domain_tokens_v2 k WHERE " + "k.focus_domain_id=d.focus_domain_id) OR (SELECT MIN(k.token_ordinal) FROM " + "optical_focus_domain_tokens_v2 k WHERE k.focus_domain_id=d.focus_domain_id)" + "!=0 OR (SELECT MAX(k.token_ordinal) FROM optical_focus_domain_tokens_v2 k " + "WHERE k.focus_domain_id=d.focus_domain_id)!=d.token_count-1 OR EXISTS(" + "SELECT 1 FROM optical_focus_domain_tokens_v2 k WHERE " + "k.focus_domain_id=d.focus_domain_id AND (typeof(k.token_ordinal)!='integer' " + "OR k.token_ordinal<0 OR k.token_ordinal>=64 OR " + "typeof(k.focus_token)!='text' OR length(k.focus_token)=0 OR " + "length(k.focus_token)>=128)))) LIMIT 1", + &validation); + if (result == LARDON3D_PROJECT_DB_OK) { + sqlite3_bind_int64(validation, 1, (sqlite3_int64)capture_id); + int code = sqlite3_step(validation); + if (code == SQLITE_ROW) + result = LARDON3D_PROJECT_DB_CORRUPT; + else if (code != SQLITE_DONE) + result = sqlite_result(database, code, "validate optical focus domain v2"); + } + (void)sqlite3_finalize(validation); + if (result != LARDON3D_PROJECT_DB_OK) + return result; + + char query[4096]; int length = snprintf( query, sizeof(query), - "SELECT " - "a.applicability_id,a.calibration_profile_id,p.sparse_calibration_id " - "FROM capture_geometric_states t JOIN " + "SELECT applicability_id,calibration_profile_id,sparse_calibration_id " + "FROM (SELECT a.applicability_id AS applicability_id," + "a.calibration_profile_id AS calibration_profile_id," + "p.sparse_calibration_id AS sparse_calibration_id FROM " + "capture_geometric_states t JOIN " "optical_calibration_applicabilities_v2 a ON " "a.optical_configuration_id=t.optical_configuration_id JOIN " "capture_geometric_states e ON e.capture_id=a.exemplar_capture_id JOIN " @@ -2475,17 +2757,29 @@ exact_candidates_locked(Lardon3DProjectDb *database, uint64_t capture_id, "p.calibration_profile_id=a.calibration_profile_id AND " "p.optical_configuration_id=a.optical_configuration_id JOIN " "sparse_calibrations s ON s.calibration_id=p.sparse_calibration_id WHERE " - "t.capture_id=?1 AND %s%s ORDER BY a.applicability_id LIMIT 2", - exact_state_predicate, - required_applicability ? " AND a.applicability_id=?2" : ""); + "t.capture_id=?1 AND %s UNION SELECT a.applicability_id," + "a.calibration_profile_id,p.sparse_calibration_id FROM " + "capture_geometric_states t JOIN optical_focus_domains_v2 d ON " + "1=1 JOIN optical_calibration_applicabilities_v2 a ON " + "a.applicability_id=d.applicability_id AND " + "a.optical_configuration_id=t.optical_configuration_id JOIN " + "capture_geometric_states e ON e.capture_id=a.exemplar_capture_id JOIN " + "optical_focus_domain_tokens_v2 k ON " + "k.focus_domain_id=d.focus_domain_id AND " + "k.focus_token=t.focus_observation JOIN optical_calibration_profiles p ON " + "p.calibration_profile_id=a.calibration_profile_id AND " + "p.optical_configuration_id=a.optical_configuration_id JOIN " + "sparse_calibrations s ON s.calibration_id=p.sparse_calibration_id WHERE " + "t.capture_id=?1 AND t.focus_state=2 AND %s) candidates WHERE " + "(?2=0 OR applicability_id=?2) ORDER BY applicability_id LIMIT 2", + exact_state_predicate, exact_nonfocus_state_predicate); sqlite3_stmt *statement = NULL; result = length < 0 || (size_t)length >= sizeof(query) ? LARDON3D_PROJECT_DB_IO_ERROR : prepare(database, query, &statement); if (result == LARDON3D_PROJECT_DB_OK) { sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id); - if (required_applicability) - sqlite3_bind_int64(statement, 2, (sqlite3_int64)required_applicability); + sqlite3_bind_int64(statement, 2, (sqlite3_int64)required_applicability); int code; *count = 0; while (*count < 2 && (code = sqlite3_step(statement)) == SQLITE_ROW) { @@ -2556,7 +2850,8 @@ Lardon3DProjectDbResult lardon3d_optical_capture_calibration_resolve_v2( return result; (void)pthread_mutex_lock(&database->mutex); size_t count = 0; - result = exact_candidates_locked(database, capture_id, 0, output, &count); + result = applicability_candidates_locked(database, capture_id, 0, output, + &count); (void)pthread_mutex_unlock(&database->mutex); if (result != LARDON3D_PROJECT_DB_OK) { memset(output, 0, sizeof(*output)); @@ -2585,8 +2880,9 @@ lardon3d_optical_capture_calibration_select_v2(Lardon3DProjectDb *database, Lardon3DProjectDbResult result = execute( database, "BEGIN IMMEDIATE", "begin Capture calibration selection v2"); if (result == LARDON3D_PROJECT_DB_OK) - result = exact_candidates_locked(database, capture_id, applicability_id, - &candidate, &count); + result = applicability_candidates_locked(database, capture_id, + applicability_id, &candidate, + &count); if (result == LARDON3D_PROJECT_DB_OK && count != 1) result = LARDON3D_PROJECT_DB_CONSTRAINT; Lardon3DOpticalCaptureGeometricState state = {0}; @@ -2688,7 +2984,7 @@ Lardon3DProjectDbResult lardon3d_optical_capture_calibration_selection_load_v2( if (result == LARDON3D_PROJECT_DB_OK) { Lardon3DOpticalCalibrationResolutionV2 candidate = {0}; size_t count = 0; - result = exact_candidates_locked( + result = applicability_candidates_locked( database, capture_id, output->applicability_id, &candidate, &count); if (result == LARDON3D_PROJECT_DB_OK && (count != 1 || diff --git a/src/project_db.c b/src/project_db.c index b84c253..8ef9bce 100644 --- a/src/project_db.c +++ b/src/project_db.c @@ -960,6 +960,29 @@ static const char schema_optical_geometric_state_v26[] = "optical_calibration_applicabilities_v2(applicability_id,calibration_" "profile_id,optical_configuration_id));"; +/* PERSISTENCE CONTRACT v27: a focus domain is evidence attached to one v26 + applicability, not a numeric focus interval or a new calibration identity. + Token order is canonical insertion order only; matching is exact text + equality. DDL-only migration cannot infer a domain from historical tokens. */ +static const char schema_optical_focus_domains_v27[] = + "CREATE TABLE optical_focus_domains_v2(" + "focus_domain_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(focus_domain_id>0)," + "applicability_id INTEGER NOT NULL UNIQUE REFERENCES " + "optical_calibration_applicabilities_v2(applicability_id)," + "domain_version INTEGER NOT NULL CHECK(domain_version>0 " + "AND domain_version<=4294967295),evidence_sha256 BLOB NOT NULL " + "CHECK(typeof(evidence_sha256)='blob' AND length(evidence_sha256)=32 AND " + "evidence_sha256!=zeroblob(32)),token_count INTEGER NOT NULL " + "CHECK(token_count>0 AND token_count<=64));" + "CREATE TABLE optical_focus_domain_tokens_v2(focus_domain_id INTEGER NOT NULL " + "REFERENCES optical_focus_domains_v2(focus_domain_id) ON DELETE CASCADE," + "token_ordinal INTEGER NOT NULL CHECK(token_ordinal>=0 AND token_ordinal<64)," + "focus_token TEXT NOT NULL CHECK(typeof(focus_token)='text' AND " + "length(focus_token)>0 AND length(focus_token)<128),PRIMARY KEY(focus_domain_id," + "token_ordinal),UNIQUE(focus_domain_id,focus_token));" + "CREATE INDEX optical_focus_domain_tokens_v2_token_idx ON " + "optical_focus_domain_tokens_v2(focus_token,focus_domain_id);"; + static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) { if (destination) { (void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : ""); @@ -1053,7 +1076,8 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int from_version != 12 && from_version != 13 && from_version != 14 && from_version != 15 && from_version != 16 && from_version != 17 && from_version != 18 && from_version != 19 && from_version != 20 && from_version != 21 && from_version != 22 && - from_version != 23 && from_version != 24 && from_version != 25) { + from_version != 23 && from_version != 24 && from_version != 25 && + from_version != 26) { return LARDON3D_PROJECT_DB_CORRUPT; } Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration"); @@ -1579,6 +1603,27 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int result = LARDON3D_PROJECT_DB_CORRUPT; } } + if (result == LARDON3D_PROJECT_DB_OK && from_version < 27) { + /* A v26 focus token is only an observation. It is never sufficient + physical evidence to manufacture an applicability domain. */ + result = execute(database, schema_optical_focus_domains_v27, + "migrate optical focus domains v26 to v27"); +#ifdef LARDON3D_PROJECT_DB_TESTING + if (result == LARDON3D_PROJECT_DB_OK && + getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V27")) + result = execute(database, "INSERT INTO missing_v27_test_table VALUES(1)", + "forced migration v27 failure"); +#endif + if (result == LARDON3D_PROJECT_DB_OK) { + result = execute(database, + "UPDATE metadata SET value=27 WHERE " + "key='schema_version' AND value=26", + "finish schema v27 migration"); + if (result == LARDON3D_PROJECT_DB_OK && + sqlite3_changes(database->connection) != 1) + result = LARDON3D_PROJECT_DB_CORRUPT; + } + } if (result == LARDON3D_PROJECT_DB_OK) { result = execute(database, "COMMIT", "commit migration"); } @@ -1736,7 +1781,9 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje "capture_calibration_selections"}; const char *required_v26[] = {"capture_geometric_states", "optical_calibration_applicabilities_v2", - "capture_calibration_selections_v2"}; + "capture_calibration_selections_v2", + "optical_focus_domains_v2", + "optical_focus_domain_tokens_v2"}; for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) && result == LARDON3D_PROJECT_DB_OK; ++index) { @@ -1751,7 +1798,7 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje ++index) { if (!table_exists(database->connection, required_v26[index])) { copy_error(database->error, - "Current Project DB v26 schema is incomplete."); + "Current Project DB v27 optical schema is incomplete."); result = LARDON3D_PROJECT_DB_CORRUPT; } } diff --git a/tests/test_geometric_verification.c b/tests/test_geometric_verification.c index 1a7d7eb..806ca53 100644 --- a/tests/test_geometric_verification.c +++ b/tests/test_geometric_verification.c @@ -172,6 +172,11 @@ static bool create_v11_database(const char *path) { lardon3d_project_db_close(database); return execute_sql(path, "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" + "DROP TABLE IF EXISTS optical_focus_domain_tokens_v2;" + "DROP TABLE IF EXISTS optical_focus_domains_v2;" + "DROP TABLE IF EXISTS capture_calibration_selections_v2;" + "DROP TABLE IF EXISTS optical_calibration_applicabilities_v2;" + "DROP TABLE IF EXISTS capture_geometric_states;" "DROP TABLE IF EXISTS feature_extract_batch_tasks;" "DROP TABLE IF EXISTS raw_development_batch_tasks;" "DROP TABLE IF EXISTS capture_calibration_selections;" diff --git a/tests/test_geometric_verifier_task.c b/tests/test_geometric_verifier_task.c index 03f91a7..fcd5aa2 100644 --- a/tests/test_geometric_verifier_task.c +++ b/tests/test_geometric_verifier_task.c @@ -878,6 +878,11 @@ static bool run_task_test(void) { CHECK(exec_sql( database_path, "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" + "DROP TABLE IF EXISTS optical_focus_domain_tokens_v2;" + "DROP TABLE IF EXISTS optical_focus_domains_v2;" + "DROP TABLE IF EXISTS capture_calibration_selections_v2;" + "DROP TABLE IF EXISTS optical_calibration_applicabilities_v2;" + "DROP TABLE IF EXISTS capture_geometric_states;" "DROP TABLE IF EXISTS feature_extract_batch_tasks;" "DROP TABLE IF EXISTS raw_development_batch_tasks;" "DROP TABLE IF EXISTS capture_calibration_selections;" diff --git a/tests/test_match_result.c b/tests/test_match_result.c index 6cc2ad4..61861dc 100644 --- a/tests/test_match_result.c +++ b/tests/test_match_result.c @@ -54,7 +54,12 @@ static bool create_v9_database(const char *path) { static const char sql[] = "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" /* This database is a true v9 fixture, not a current database whose - metadata alone was relabelled while additive v20-v25 objects survived. */ + metadata alone was relabelled while later additive objects survived. */ + "DROP TABLE IF EXISTS optical_focus_domain_tokens_v2;" + "DROP TABLE IF EXISTS optical_focus_domains_v2;" + "DROP TABLE IF EXISTS capture_calibration_selections_v2;" + "DROP TABLE IF EXISTS optical_calibration_applicabilities_v2;" + "DROP TABLE IF EXISTS capture_geometric_states;" "DROP TABLE IF EXISTS feature_extract_batch_tasks;" "DROP TABLE IF EXISTS raw_development_batch_tasks;" "DROP TABLE IF EXISTS capture_calibration_selections;" diff --git a/tests/test_matcher_task.c b/tests/test_matcher_task.c index 381f7e8..b97007f 100644 --- a/tests/test_matcher_task.c +++ b/tests/test_matcher_task.c @@ -360,6 +360,11 @@ static bool downgrade_project_to_historical_v10(const char *database_path) { } static const char sql[] = "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" + "DROP TABLE IF EXISTS optical_focus_domain_tokens_v2;" + "DROP TABLE IF EXISTS optical_focus_domains_v2;" + "DROP TABLE IF EXISTS capture_calibration_selections_v2;" + "DROP TABLE IF EXISTS optical_calibration_applicabilities_v2;" + "DROP TABLE IF EXISTS capture_geometric_states;" "DROP TABLE IF EXISTS feature_extract_batch_tasks;" "DROP TABLE IF EXISTS raw_development_batch_tasks;" "DROP TABLE IF EXISTS selected_execution_items;" diff --git a/tests/test_optical_profiles.c b/tests/test_optical_profiles.c index 6c06fb2..c418642 100644 --- a/tests/test_optical_profiles.c +++ b/tests/test_optical_profiles.c @@ -1147,6 +1147,8 @@ static bool downgrade_to_v22_fixture(const char *path) { return raw_sql( path, "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" + "DROP TABLE IF EXISTS optical_focus_domain_tokens_v2;" + "DROP TABLE IF EXISTS optical_focus_domains_v2;" "DROP TABLE IF EXISTS capture_calibration_selections_v2;" "DROP TABLE IF EXISTS optical_calibration_applicabilities_v2;" "DROP TABLE IF EXISTS capture_geometric_states;" @@ -1407,6 +1409,8 @@ static bool test_v26_exact_geometric_applicability(void) { */ CHECK(raw_sql( path, "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" + "DROP TABLE optical_focus_domain_tokens_v2;" + "DROP TABLE optical_focus_domains_v2;" "DROP TABLE capture_calibration_selections_v2;" "DROP TABLE optical_calibration_applicabilities_v2;" "DROP TABLE capture_geometric_states;" @@ -1432,6 +1436,296 @@ static bool test_v26_exact_geometric_applicability(void) { return true; } +static Lardon3DOpticalCaptureGeometricState complete_focus_state( + uint64_t capture_id, uint64_t configuration_id, const char *focus_token) { + Lardon3DOpticalCaptureGeometricState state = { + .capture_id = capture_id, + .optical_configuration_id = configuration_id, + .state_version = 1, + .provenance = LARDON3D_OPTICAL_GEOMETRIC_STATE_METADATA, + .focus_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED, + .aperture_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED, + .aperture_x1000 = 5600, + .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 = 6000, + .decoded_height = 4000, + }; + (void)snprintf(state.focus_observation, sizeof(state.focus_observation), + "%s", focus_token); + memcpy(state.crop_observation, "full-sensor", sizeof("full-sensor")); + memcpy(state.pipeline_observation, "raw-policy-v1", + sizeof("raw-policy-v1")); + memcpy(state.representation_observation, "L3DRAWD1-png", + sizeof("L3DRAWD1-png")); + return state; +} + +static bool test_v27_discrete_focus_domains(void) { + char directory[64]; + char path[256]; + CHECK(make_database_path(directory, path)); + Lardon3DProjectDb *database = NULL; + char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; + CHECK(lardon3d_project_db_open(path, &database, error) == + LARDON3D_PROJECT_DB_OK); + RepairOpticalFixture fixture; + CHECK(seed_repair_optical_fixture(database, &fixture)); + + Lardon3DOpticalConfiguration base_configuration; + CHECK(lardon3d_optical_configuration_load( + database, fixture.configuration_id, &base_configuration) == + LARDON3D_PROJECT_DB_OK); + Lardon3DOpticalCameraBodyProfile other_body_input = {0}; + memcpy(other_body_input.manufacturer, "Generic", sizeof("Generic")); + memcpy(other_body_input.model, "Other body", sizeof("Other body")); + memcpy(other_body_input.name, "Other body profile", + sizeof("Other body profile")); + Lardon3DOpticalCameraBodyProfile other_body; + CHECK(lardon3d_optical_camera_body_create(database, &other_body_input, + &other_body) == + LARDON3D_PROJECT_DB_OK); + Lardon3DOpticalLensProfile other_lens_input = { + .interface_kind = LARDON3D_OPTICAL_LENS_ELECTRONIC, + .focal_range_kind = LARDON3D_OPTICAL_FOCAL_RANGE_PRIME, + .minimum_focal_um = 16000, + .maximum_focal_um = 16000, + }; + memcpy(other_lens_input.manufacturer, "Generic", sizeof("Generic")); + memcpy(other_lens_input.model, "Other lens", sizeof("Other lens")); + memcpy(other_lens_input.name, "Other lens profile", + sizeof("Other lens profile")); + Lardon3DOpticalLensProfile other_lens; + CHECK(lardon3d_optical_lens_create(database, &other_lens_input, &other_lens) == + LARDON3D_PROJECT_DB_OK); + Lardon3DOpticalConfiguration configuration_input = { + .camera_body_profile_id = other_body.camera_body_profile_id, + .lens_profile_id = base_configuration.lens_profile_id, + .has_focal_length = true, + .focal_length_um = 16000, + }; + Lardon3DOpticalConfiguration other_body_configuration; + CHECK(lardon3d_optical_configuration_create( + database, &configuration_input, &other_body_configuration) == + LARDON3D_PROJECT_DB_OK); + configuration_input.camera_body_profile_id = + base_configuration.camera_body_profile_id; + configuration_input.lens_profile_id = other_lens.lens_profile_id; + Lardon3DOpticalConfiguration other_lens_configuration; + CHECK(lardon3d_optical_configuration_create( + database, &configuration_input, &other_lens_configuration) == + LARDON3D_PROJECT_DB_OK); + + enum { TARGET_COUNT = 5 }; + Lardon3DProjectDbCapture targets[TARGET_COUNT]; + for (size_t index = 0; index < TARGET_COUNT; ++index) + CHECK(lardon3d_project_db_create_capture(database, fixture.scanset_id, + (uint32_t)(index + 2), + &targets[index]) == + LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_optical_capture_assign_explicit( + database, fixture.capture_id, fixture.configuration_id) == + LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_optical_capture_assign_explicit( + database, targets[0].capture_id, fixture.configuration_id) == + LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_optical_capture_assign_explicit( + database, targets[1].capture_id, fixture.configuration_id) == + LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_optical_capture_assign_explicit( + database, targets[2].capture_id, + fixture.alternate_configuration_id) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_optical_capture_assign_explicit( + database, targets[3].capture_id, + other_body_configuration.optical_configuration_id) == + LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_optical_capture_assign_explicit( + database, targets[4].capture_id, + other_lens_configuration.optical_configuration_id) == + LARDON3D_PROJECT_DB_OK); + + Lardon3DOpticalCaptureGeometricState state = complete_focus_state( + fixture.capture_id, fixture.configuration_id, "synthetic-focus-a"); + Lardon3DOpticalCaptureGeometricState stored; + CHECK(lardon3d_optical_capture_geometric_state_create( + database, &state, &stored) == LARDON3D_PROJECT_DB_OK); + state = complete_focus_state( + targets[3].capture_id, + other_body_configuration.optical_configuration_id, "synthetic-focus-b"); + CHECK(lardon3d_optical_capture_geometric_state_create( + database, &state, &stored) == LARDON3D_PROJECT_DB_OK); + state = complete_focus_state( + targets[4].capture_id, + other_lens_configuration.optical_configuration_id, "synthetic-focus-b"); + CHECK(lardon3d_optical_capture_geometric_state_create( + database, &state, &stored) == LARDON3D_PROJECT_DB_OK); + state = complete_focus_state(targets[0].capture_id, fixture.configuration_id, + "synthetic-focus-b"); + CHECK(lardon3d_optical_capture_geometric_state_create( + database, &state, &stored) == LARDON3D_PROJECT_DB_OK); + state = complete_focus_state(targets[1].capture_id, fixture.configuration_id, + "synthetic-focus-outside"); + CHECK(lardon3d_optical_capture_geometric_state_create( + database, &state, &stored) == LARDON3D_PROJECT_DB_OK); + /* The identical focus token at another focal configuration must not cross + the exact body/lens/focal applicability boundary. */ + state = complete_focus_state(targets[2].capture_id, + fixture.alternate_configuration_id, + "synthetic-focus-b"); + CHECK(lardon3d_optical_capture_geometric_state_create( + database, &state, &stored) == LARDON3D_PROJECT_DB_OK); + + Lardon3DSparseCalibration calibration; + CHECK(create_sparse_calibration(database, 0xd1, 6000, 4000, &calibration)); + Lardon3DOpticalCalibrationProfile profile_input = { + .optical_configuration_id = fixture.configuration_id, + .sparse_calibration_id = calibration.calibration_id, + .profile_version = 2, + .applicability = LARDON3D_OPTICAL_CALIBRATION_EXACT_CONFIGURATION, + .created_at = 200, + }; + memcpy(profile_input.name, "focus domain A", sizeof("focus domain A")); + memcpy(profile_input.provenance, "synthetic physical evidence", + sizeof("synthetic physical evidence")); + Lardon3DOpticalCalibrationProfile profile_a; + CHECK(lardon3d_optical_calibration_profile_create( + database, &profile_input, &profile_a) == LARDON3D_PROJECT_DB_OK); + Lardon3DOpticalCalibrationApplicabilityV2 applicability_a; + CHECK(lardon3d_optical_calibration_applicability_v2_create( + database, profile_a.calibration_profile_id, fixture.capture_id, + &applicability_a) == LARDON3D_PROJECT_DB_OK); + + const char *tokens[] = {"synthetic-focus-b", "synthetic-focus-a"}; + unsigned char evidence[LARDON3D_OPTICAL_FOCUS_DOMAIN_DIGEST_SIZE] = {0}; + evidence[0] = 0x71; + Lardon3DOpticalFocusDomainV2 domain_a; + CHECK(lardon3d_optical_focus_domain_v2_create( + database, applicability_a.applicability_id, 1, evidence, tokens, + 2, &domain_a) == LARDON3D_PROJECT_DB_OK && + domain_a.token_count == 2); + const char *retry_tokens[] = {"synthetic-focus-a", "synthetic-focus-b"}; + Lardon3DOpticalFocusDomainV2 domain_retry; + CHECK(lardon3d_optical_focus_domain_v2_create( + database, applicability_a.applicability_id, 1, evidence, + retry_tokens, 2, &domain_retry) == LARDON3D_PROJECT_DB_OK && + domain_retry.focus_domain_id == domain_a.focus_domain_id); + evidence[1] = 1; + CHECK(lardon3d_optical_focus_domain_v2_create( + database, applicability_a.applicability_id, 1, evidence, + retry_tokens, 2, &domain_retry) == LARDON3D_PROJECT_DB_CONSTRAINT && + domain_retry.focus_domain_id == 0); + evidence[1] = 0; + + Lardon3DOpticalCalibrationResolutionV2 resolution; + CHECK(lardon3d_optical_capture_calibration_resolve_v2( + database, targets[0].capture_id, &resolution) == + LARDON3D_PROJECT_DB_OK && + resolution.kind == LARDON3D_OPTICAL_CALIBRATION_RESOLVED && + resolution.applicability_id == applicability_a.applicability_id); + CHECK(lardon3d_optical_capture_calibration_resolve_v2( + database, targets[1].capture_id, &resolution) == + LARDON3D_PROJECT_DB_OK && + resolution.kind == LARDON3D_OPTICAL_CALIBRATION_REQUIRED); + CHECK(lardon3d_optical_capture_calibration_resolve_v2( + database, targets[2].capture_id, &resolution) == + LARDON3D_PROJECT_DB_OK && + resolution.kind == LARDON3D_OPTICAL_CALIBRATION_REQUIRED); + CHECK(lardon3d_optical_capture_calibration_resolve_v2( + database, targets[3].capture_id, &resolution) == + LARDON3D_PROJECT_DB_OK && + resolution.kind == LARDON3D_OPTICAL_CALIBRATION_REQUIRED); + CHECK(lardon3d_optical_capture_calibration_resolve_v2( + database, targets[4].capture_id, &resolution) == + LARDON3D_PROJECT_DB_OK && + resolution.kind == LARDON3D_OPTICAL_CALIBRATION_REQUIRED); + + /* A domain cannot repair incomplete exemplar evidence. Corrupting the + schema-valid focus state must fail closed before candidate enumeration. */ + lardon3d_project_db_close(database); + database = NULL; + char corruption[512]; + int corruption_bytes = snprintf( + corruption, sizeof(corruption), + "UPDATE capture_geometric_states SET focus_state=1," + "focus_observation='' WHERE capture_id=%llu;", + (unsigned long long)fixture.capture_id); + CHECK(corruption_bytes > 0 && + (size_t)corruption_bytes < sizeof(corruption) && + raw_sql(path, corruption)); + CHECK(lardon3d_project_db_open(path, &database, error) == + LARDON3D_PROJECT_DB_OK); + memset(&resolution, 0x7f, sizeof(resolution)); + CHECK(lardon3d_optical_capture_calibration_resolve_v2( + database, targets[0].capture_id, &resolution) == + LARDON3D_PROJECT_DB_CORRUPT && + resolution.kind == 0 && + resolution.applicability_id == 0); + lardon3d_project_db_close(database); + database = NULL; + corruption_bytes = snprintf( + corruption, sizeof(corruption), + "UPDATE capture_geometric_states SET focus_state=2," + "focus_observation='synthetic-focus-a' WHERE capture_id=%llu;", + (unsigned long long)fixture.capture_id); + CHECK(corruption_bytes > 0 && + (size_t)corruption_bytes < sizeof(corruption) && + raw_sql(path, corruption)); + CHECK(lardon3d_project_db_open(path, &database, error) == + LARDON3D_PROJECT_DB_OK); + + memcpy(profile_input.name, "focus domain B", sizeof("focus domain B")); + Lardon3DOpticalCalibrationProfile profile_b; + CHECK(lardon3d_optical_calibration_profile_create( + database, &profile_input, &profile_b) == LARDON3D_PROJECT_DB_OK); + Lardon3DOpticalCalibrationApplicabilityV2 applicability_b; + CHECK(lardon3d_optical_calibration_applicability_v2_create( + database, profile_b.calibration_profile_id, fixture.capture_id, + &applicability_b) == LARDON3D_PROJECT_DB_OK); + evidence[0] = 0x72; + const char *overlap_tokens[] = {"synthetic-focus-b"}; + Lardon3DOpticalFocusDomainV2 domain_b; + CHECK(lardon3d_optical_focus_domain_v2_create( + database, applicability_b.applicability_id, 1, evidence, + overlap_tokens, 1, &domain_b) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_optical_capture_calibration_resolve_v2( + database, targets[0].capture_id, &resolution) == + LARDON3D_PROJECT_DB_OK && + resolution.kind == LARDON3D_OPTICAL_CALIBRATION_SELECTION_REQUIRED); + CHECK(lardon3d_optical_capture_calibration_select_v2( + database, targets[0].capture_id, + applicability_a.applicability_id) == LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_optical_capture_calibration_select_v2( + database, targets[0].capture_id, + applicability_a.applicability_id) == LARDON3D_PROJECT_DB_OK); + + lardon3d_project_db_close(database); + /* v26 contains observations but no retained physical-domain evidence. The + additive migration must recreate empty domain tables without inference. */ + CHECK(raw_sql(path, "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" + "DROP TABLE optical_focus_domain_tokens_v2;" + "DROP TABLE optical_focus_domains_v2;" + "UPDATE metadata SET value=26 WHERE key='schema_version';" + "COMMIT;")); + database = NULL; + CHECK(lardon3d_project_db_open(path, &database, error) == + LARDON3D_PROJECT_DB_OK); + lardon3d_project_db_close(database); + sqlite3_int64 count = -1; + CHECK(raw_integer(path, "SELECT COUNT(*) FROM optical_focus_domains_v2", + &count) && + count == 0); + CHECK(raw_integer(path, + "SELECT COUNT(*) FROM optical_focus_domain_tokens_v2", + &count) && + count == 0); + CHECK(unlink(path) == 0); + CHECK(rmdir(directory) == 0); + return true; +} + static bool test_migration_rollback_retry_and_equivalence(void) { char migrated_directory[64]; char migrated_path[256]; @@ -1507,6 +1801,8 @@ static bool test_migration_rollback_retry_and_equivalence(void) { "capture_geometric_states", "optical_calibration_applicabilities_v2", "capture_calibration_selections_v2", + "optical_focus_domains_v2", + "optical_focus_domain_tokens_v2", }; char query[256]; for (size_t index = 0; index < sizeof(empty_tables) / sizeof(empty_tables[0]); @@ -1543,6 +1839,9 @@ static bool test_migration_rollback_retry_and_equivalence(void) { "optical_calibration_applicabilities_v2", "optical_calibration_applicabilities_v2_config_idx", "capture_calibration_selections_v2", + "optical_focus_domains_v2", + "optical_focus_domain_tokens_v2", + "optical_focus_domain_tokens_v2_token_idx", }; char migrated_sql[8192]; char fresh_sql[8192]; @@ -1564,6 +1863,7 @@ static bool test_migration_rollback_retry_and_equivalence(void) { int main(void) { return test_profiles_assignments_and_calibrations() && test_v26_exact_geometric_applicability() && + test_v27_discrete_focus_domains() && test_calibration_conflict_result_contract() && test_calibration_dependency_corruption_precedence() && test_campaign_request_corruption_prevents_optics_mutation() && diff --git a/tests/test_project_db.c b/tests/test_project_db.c index 2d5cfb2..7b4be64 100644 --- a/tests/test_project_db.c +++ b/tests/test_project_db.c @@ -22,9 +22,14 @@ } while (0) /* Historical migration fixtures are produced from a temporary current DB. - * Remove every additive v23/v24/v25 object first so the fixture really is historical - * rather than a lower version number with future tables left behind. */ + * Remove every additive v23..v27 object first so the fixture really is + * historical rather than a lower version number with future tables left behind. */ #define DROP_OPTICAL_V23_SQL \ + "DROP TABLE IF EXISTS optical_focus_domain_tokens_v2;" \ + "DROP TABLE IF EXISTS optical_focus_domains_v2;" \ + "DROP TABLE IF EXISTS capture_calibration_selections_v2;" \ + "DROP TABLE IF EXISTS optical_calibration_applicabilities_v2;" \ + "DROP TABLE IF EXISTS capture_geometric_states;" \ "DROP TABLE IF EXISTS feature_extract_batch_tasks;" \ "DROP TABLE IF EXISTS raw_development_batch_tasks;" \ "DROP TABLE IF EXISTS capture_calibration_selections;" \ @@ -129,12 +134,12 @@ static bool create_future_database(const char *path) { if (sqlite3_open(path, &connection) != SQLITE_OK) { return false; } - bool ok = - sqlite3_exec( - connection, - "CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);" - "INSERT INTO metadata VALUES('schema_version',26);", - NULL, NULL, NULL) == SQLITE_OK; + char *sql = sqlite3_mprintf( + "CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);" + "INSERT INTO metadata VALUES('schema_version',%u);", + LARDON3D_PROJECT_DB_SCHEMA_VERSION + 1U); + bool ok = sql && sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK; + sqlite3_free(sql); return sqlite3_close(connection) == SQLITE_OK && ok; } @@ -963,12 +968,18 @@ static bool run_test(void) { char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; Lardon3DProjectDb *database = NULL; - /* v25 is DDL-only: an injected failure rolls back table and marker, and a - * retry creates no fabricated association or historical cursor. */ + /* Remove later empty overlays to form a true v24 fixture. The v25 injected + * failure must roll back its table/marker, and retry through current head + * creates no fabricated association, observation, domain, or cursor. */ CHECK(lardon3d_project_db_open(v24_path, &database, error) == LARDON3D_PROJECT_DB_OK); lardon3d_project_db_close(database); database = NULL; CHECK(execute_test_sql(v24_path, + "DROP TABLE optical_focus_domain_tokens_v2;" + "DROP TABLE optical_focus_domains_v2;" + "DROP TABLE capture_calibration_selections_v2;" + "DROP TABLE optical_calibration_applicabilities_v2;" + "DROP TABLE capture_geometric_states;" "DROP TABLE feature_extract_batch_tasks;" "UPDATE metadata SET value=24 WHERE key='schema_version';")); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V25", "1", 1) == 0); @@ -987,6 +998,26 @@ static bool run_test(void) { lardon3d_project_db_close(database); database = NULL; CHECK(query_integer(v24_path, "SELECT count(*) FROM feature_extract_batch_tasks", 0)); + CHECK(execute_test_sql(v24_path, + "DROP TABLE optical_focus_domain_tokens_v2;" + "DROP TABLE optical_focus_domains_v2;" + "UPDATE metadata SET value=26 WHERE key='schema_version';")); + CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V27", "1", 1) == 0); + CHECK(lardon3d_project_db_open(v24_path, &database, error) != + LARDON3D_PROJECT_DB_OK); + CHECK(database == NULL); + CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V27") == 0); + CHECK(query_integer(v24_path, + "SELECT value FROM metadata WHERE key='schema_version'", 26)); + CHECK(query_integer(v24_path, + "SELECT count(*) FROM sqlite_master WHERE type='table' AND name IN " + "('optical_focus_domains_v2','optical_focus_domain_tokens_v2')", 0)); + CHECK(lardon3d_project_db_open(v24_path, &database, error) == + LARDON3D_PROJECT_DB_OK); + CHECK(lardon3d_project_db_schema_version(database) == + LARDON3D_PROJECT_DB_SCHEMA_VERSION); + lardon3d_project_db_close(database); + database = NULL; CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK); CHECK(database && lardon3d_project_db_schema_version(database) == diff --git a/tests/test_selected_execution.c b/tests/test_selected_execution.c index 90cb0a5..635fa92 100644 --- a/tests/test_selected_execution.c +++ b/tests/test_selected_execution.c @@ -64,6 +64,11 @@ static bool seed_v21(const char *path) { "INSERT INTO sparse_calibration_scope_images VALUES(1,1,1);" /* This fixture is a true v21 database; future additive objects must not remain merely because it was generated from a temporary current DB. */ + "DROP TABLE optical_focus_domain_tokens_v2;" + "DROP TABLE optical_focus_domains_v2;" + "DROP TABLE capture_calibration_selections_v2;" + "DROP TABLE optical_calibration_applicabilities_v2;" + "DROP TABLE capture_geometric_states;" "DROP TABLE feature_extract_batch_tasks;" "DROP TABLE raw_development_batch_tasks;" "DROP TABLE capture_calibration_selections;" @@ -110,7 +115,12 @@ static bool run(void) { CHECK(sql(path, "SELECT 1 FROM raw_development_batch_tasks WHERE 0;")); lardon3d_project_db_close(database); database = NULL; - CHECK(sql(path, "DROP TABLE feature_extract_batch_tasks;" + CHECK(sql(path, "DROP TABLE optical_focus_domain_tokens_v2;" + "DROP TABLE optical_focus_domains_v2;" + "DROP TABLE capture_calibration_selections_v2;" + "DROP TABLE optical_calibration_applicabilities_v2;" + "DROP TABLE capture_geometric_states;" + "DROP TABLE feature_extract_batch_tasks;" "DROP TABLE raw_development_batch_tasks;" "UPDATE metadata SET value=23 WHERE key='schema_version';")); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V24", "1", 1) == 0);