Blog › ICP guides
Ada developer on retainer: type constraint violations, task scheduling bugs, SPARK Ada verification, and Ada on monthly retainer
October 2, 2026 · ~13 min read
An Ada developer was maintaining the scheduling subsystem of an avionics flight management system. The scheduler computed the next wake time for a periodic monitoring task by subtracting the current tick count from a pre-calculated target tick. Under normal conditions, the target tick was always in the future, so the delta was always positive. During a hardware-induced clock interrupt that caused the current tick counter to momentarily exceed the target, the delta became negative. The scheduler passed the negative delta to a delay statement.
The variable holding the delta was declared as:
Delta_Ticks : Integer;
The computation was:
Delta_Ticks := Next_Wake_Tick - Current_Tick;
Integer in Ada can hold negative values. When the clock interrupt caused Current_Tick to briefly exceed Next_Wake_Tick, Delta_Ticks became −3. The subsequent delay duration(Delta_Ticks) call raised CONSTRAINT_ERROR because Ada’s Duration type has a non-negative minimum. The scheduler task terminated abnormally. One CONSTRAINT_ERROR in a safety-critical scheduling path. The fix: change Delta_Ticks from Integer to Natural — Natural is the Ada standard subtype of Integer with range 0 .. Integer'Last — and add a guard that handles the clock-ahead case explicitly before the subtraction. With the type changed, any negative assignment to Delta_Ticks raises CONSTRAINT_ERROR at the point of assignment rather than propagating a wrong value into the delay call, exposing the clock-ahead condition at the right location. CONSTRAINT_ERROR events in the delay call: 1 → 0. The work log said “fixed scheduler crash on clock interrupt, 5h.” What the log does not say is that the fundamental issue was using Integer for a semantically non-negative quantity — and that Ada’s type system, if used correctly with Natural, would have surfaced the bug at the assignment rather than propagating it.
Ada overview: the language mandated for safety-critical systems since 1983
Ada was developed under U.S. Department of Defense contract in the late 1970s, with the language design competition won by Jean Ichbiah’s team at CII Honeywell Bull. The first standard, Ada 83, was published in 1983. Ada 95 added object-oriented features. Ada 2005 added interface types and synchronized interfaces. Ada 2012 added contract-based programming (preconditions, postconditions, type invariants). Ada 2022 is the current standard. The DoD originally mandated Ada for all new defense software in 1987; the mandate was lifted in 1997, but Ada remained the dominant language for avionics, defense, and transportation safety-critical systems through adoption inertia, existing codebases, and the fact that no other language offers Ada’s combination of strong typing, tasking primitives, and provability.
Ada’s design philosophy centers on catching errors at compile time that other languages allow at runtime, or on making runtime errors predictable and diagnosable. Strong typing means that assigning a value of one type to a variable of another requires an explicit conversion; implicit coercions that silently change value ranges are not allowed. Range-constrained subtypes mean that the programmer can declare that a variable must be between 0 and 100, and any assignment outside that range raises CONSTRAINT_ERROR immediately, at the assignment, rather than propagating a wrong value through the system. Tasking is part of the core language, not a library: task types, protected types, and rendezvous synchronization are language constructs with well-defined semantics, not framework calls with implementation-dependent behavior.
Ada retainer work today covers: avionics software maintenance (flight management systems, navigation systems, autopilot systems running on certifiable Ada compilers with DO-178C artifacts); defense systems maintenance (command and control software, radar signal processing, weapons system interface software); transportation systems maintenance (railway interlocking and signalling systems, automotive safety systems using Ada or MISRA-C with Ada-like constraints); and SPARK Ada formal verification (SPARK is a formally-analyzable subset of Ada 2012; SPARK programs can be statically proven free of runtime errors using GNATprove; organizations that have invested in SPARK proofs need ongoing proof maintenance as requirements change).
Ada type system: subtypes, range constraints, and CONSTRAINT_ERROR
Ada’s type system is the primary tool for expressing semantic constraints on data. The standard numeric types — Integer, Long_Integer, Float, Long_Float — are the base types. Standard subtypes include Natural (subtype of Integer with range 0 .. Integer'Last) and Positive (subtype of Integer with range 1 .. Integer'Last). A programmer can declare custom subtypes: subtype Altitude_Feet is Integer range 0 .. 65000; any assignment to an Altitude_Feet variable where the value is outside 0 to 65,000 raises CONSTRAINT_ERROR at the assignment point.
The critical property: Ada does not silently accept out-of-range values. When a value is assigned to a constrained subtype and the value falls outside the declared range, Ada raises CONSTRAINT_ERROR immediately, at the assignment, with a traceable exception message naming the package and line number. This is different from C (which wraps or truncates silently), Java (which silently narrows on cast), and Python (which uses arbitrary-precision integers and never overflows). The Ada behavior catches the bug at the earliest possible point, but it requires that the programmer declare the appropriate subtype — using the base Integer type for a variable that is semantically non-negative means that negative values can propagate through the system until they hit a runtime check elsewhere, or until they are passed to a context (like Duration conversion) that raises the error.
The most common Ada type bug in retainer work: a developer who knows the variable should always be non-negative but declares it as Integer because “the calling code never produces a negative value.” The assertion “the calling code never produces a negative value” is correct under normal conditions. Under edge-case conditions — clock rollover, hardware fault injection, unexpected input sequence — the calling code does produce a negative value. If the type were Natural, the CONSTRAINT_ERROR would fire at the assignment and the edge case would be visible. With Integer, the negative value propagates until it hits a context that raises an error elsewhere, producing a misleading stack trace.
The diagnostic process for CONSTRAINT_ERROR bugs: read the exception message to find the package and line number; identify the variable at that line; check its declared type and range; trace back through the call chain to find the assignment that produced the out-of-range value; determine whether the type declaration was too permissive (fix: strengthen the subtype) or whether the logic genuinely produces values outside the intended range under some input (fix: add a guard or handle the edge case). A well-typed Ada program surfaces the error at the earliest possible assignment; a poorly-typed Ada program (using base Integer for every numeric variable) defers the error to unpredictable locations.
Ada tasking, protected objects, and real-time scheduling
Ada’s tasking model is part of the core language. A task type declares a concurrent execution unit. A protected type declares a data object that can be accessed concurrently with mutual exclusion enforced by the language runtime. Rendezvous synchronization allows two tasks to meet at a point (an accept statement in one task matching an entry call from another) and exchange data or synchronize execution. Unlike threads in C or Java — which are library constructs with implementation-dependent behavior — Ada tasks have language-level semantics: priority, scheduling policy, and deadline are part of the task declaration.
The most common Ada tasking bug in retainer work: a protected entry with a guard that can never become True in the current state. A protected entry guard is a Boolean expression evaluated before the entry body executes; if the guard is False, the calling task queues waiting for the guard to become True. If the guard depends on a protected variable that is set by another task, and that other task is itself waiting on the first task for a different reason, the two tasks are in a priority-inversion deadlock or a livelock. The system appears to hang: no crash, no error message, no diagnostic output. The diagnosis requires reading the task dependency graph — which task waits on which entry, which task sets which guard condition — and finding the circular dependency.
The Ada Real-Time Annex (Annex D of the Ada Reference Manual) specifies scheduling policies, priorities, and deadline behavior for real-time systems. The Ravenscar profile is a restricted subset of Ada tasking that permits static analysis of schedulability: under Ravenscar, task interactions are restricted to protected objects (no rendezvous), tasks are created at system start and do not terminate, and dynamic task creation is prohibited. A system that uses Ravenscar can be analyzed by a schedulability analyzer to prove that all task deadlines will be met. Retainer work for systems using the Ravenscar profile requires understanding the schedulability analysis and ensuring that maintenance changes do not violate Ravenscar restrictions.
SPARK Ada formal verification and DO-178C certification maintenance
SPARK is a formally analyzable subset of Ada. SPARK programs are valid Ada programs with additional annotations (preconditions, postconditions, type invariants, global variable flow annotations) that allow GNATprove — the SPARK verification tool — to prove absence of runtime errors without executing the program. GNATprove generates proof obligations for each assertion and discharge them using SMT solvers. A SPARK program for which GNATprove produces no unproved obligations is guaranteed to be free of CONSTRAINT_ERROR, numeric overflow, array out-of-bounds access, and division by zero, for all possible inputs satisfying the preconditions.
SPARK proof maintenance is a significant ongoing retainer activity for organizations that have invested in SPARK verification. Any code change that affects a subprogram with SPARK annotations may invalidate existing proofs. A change that adds a new parameter, modifies a loop bound, or changes a data structure may require updating contracts (preconditions and postconditions) so that GNATprove can re-prove the affected subprograms. A proof failure does not mean the code is wrong — it means the existing contracts no longer provide enough information for the solver to verify the change automatically. Resolving a proof failure requires understanding what the solver needs to know about the new code path and expressing that knowledge as an Ada contract or intermediate assertion.
DO-178C is the software certification standard for airborne systems used by the FAA, EASA, and equivalent aviation authorities. A DO-178C Level A certification (the highest level, for software whose failure would prevent continued safe flight) requires a full set of certification artifacts: software requirements, software design documents, source code, test procedures, test results, and a traceability matrix linking each requirement to the code that implements it and the test that verifies it. Ada systems in avionics often carry DO-178C certifications. Maintenance changes that modify certified software require updating the certification artifacts to reflect the change — a code fix that takes 4 hours to diagnose and implement can require 12 additional hours to document in the certification artifact set.
Typical Ada retainer work and what it looks like in a work log
Type constraint violation diagnosis is the largest category of Ada retainer work that produces no visible artifact proportional to the hours. A developer who adds a one-line type change from Integer to Natural and a two-line guard for the edge case has spent 5 hours tracing the exception, reading the call stack, identifying the semantic constraint that the original type did not enforce, and verifying that the edge case is handled. The fix is three lines. Work log entry: “Scheduler: Delta_Ticks declared as Integer; clock interrupt caused Next_Wake_Tick - Current_Tick to produce -3; passed to Duration() which raised CONSTRAINT_ERROR; semantically Delta_Ticks must be non-negative; fix: (1) changed type to Natural; (2) added guard to handle Current_Tick > Next_Wake_Tick before subtraction; CONSTRAINT_ERROR events in delay path before: 1; after: 0; 5h.”
Task scheduling deadlock diagnosis is the second category. Two tasks waiting on each other’s protected entries hang silently. Work log entry: “Monitoring_Task and Logger_Task deadlocked: Monitoring_Task held Protected_Buffer write lock and called Logger_Task entry Log_Event; Logger_Task was waiting on Protected_Buffer read entry to flush previous event; circular wait; fix: moved Log_Event call outside Protected_Buffer entry body so write lock is released before Logger_Task is notified; deadlock events before: 1; after: 0; 8h.”
SPARK proof failure resolution is the third category. A code change that is logically correct produces a GNATprove proof failure because the solver no longer has enough contract information to verify the change automatically. Work log entry: “Process_Delta subprogram: added parameter Max_Delta : Natural; GNATprove failed proof obligation on line 47: Constraint_Check for Delta_Val; existing precondition only constrained Delta_Val >= 0; did not constrain Delta_Val <= Max_Delta; added postcondition Delta_Val'Result <= Max_Delta to Process_Delta; GNATprove proof obligations: 1 failed → 0 failed; 4h.”
Track Ada developer retainer hours without the status emails
When a 5-hour session traces a scheduler CONSTRAINT_ERROR to a Delta_Ticks : Integer declaration — because using the base Integer type instead of Natural allowed a clock interrupt edge case to propagate a negative value into a Duration conversion — the work log needs to name the type, the edge case, the Ada type system mechanism, and the event count before and after. HourTab gives your Ada retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log that names the Ada mechanism. No client login. No status emails. CSV in, URL out.
How HourTab tracks Ada developer retainer hours
Ada retainer work is invisible by the mechanism that makes Ada safe: the type system catches errors at runtime before they propagate. When the CONSTRAINT_ERROR fires in the scheduler’s delay call rather than at the assignment, the work is finding the delta between where the error fired and where it originated. That delta is invisible in the work log unless the log names the type, the range constraint, the edge case, and the propagation path. A log entry that says “fixed scheduler crash, 5h” does not explain why the fix was a type declaration change and a guard, not a logic rewrite.
The work log needs to name the type system mechanism: which type was declared, what range it allows, what value exceeded it, where the value originated, and what the semantically correct type or constraint is. A log entry that says “changed Delta_Ticks from Integer to Natural; added guard for clock-ahead case; CONSTRAINT_ERROR events in delay path: 1 → 0; 5h” is auditable.
HourTab gives Ada developers a public retainer-hours URL they send to clients — avionics contractors maintaining flight management systems, defense systems integrators maintaining command and control software, railway signalling companies maintaining interlocking systems, and organizations maintaining DO-178C-certified codebases. For Ada retainers, each work log entry should name the Ada mechanism: which type or subtype, which range constraint, which task entry or protected guard, which SPARK proof obligation. Comparative context: Ada retainer work has conceptual overlap with other type-safe or safety-critical environments — Fortran (where array bounds and kind parameters provide analogous explicit precision management for numerical systems); COBOL (where PIC clause decimal alignment provides explicit format control with silent truncation as the failure mode, the inverse of Ada’s explicit runtime checking); and Rust (which uses ownership and lifetimes to eliminate memory safety bugs at compile time in the same spirit that Ada uses subtypes to eliminate range violations). Ada is uniquely positioned as the language for existing safety-critical codebases with DO-178C certifications that cannot be replaced without re-certification from scratch.
FAQ: Ada developer retainers
What does an Ada developer on retainer typically do?
An Ada developer on monthly retainer covers type constraint violation diagnosis (CONSTRAINT_ERROR from out-of-range subtype assignments; Integer vs Natural vs Positive subtype selection; range constraint propagation through arithmetic); Ada task scheduling maintenance (task priority declarations, protected object entry guards, real-time annex scheduling policy, Ravenscar profile compliance); SPARK Ada proof maintenance (updating contracts after code changes, resolving failed GNATprove proof obligations, maintaining flow annotations); GNAT toolchain maintenance (GNAT FSF vs GNAT Pro version management, Alire package updates, cross-compilation target configuration); and DO-178C documentation maintenance (traceability matrix updates, test procedure maintenance).
What Ada work is most commonly underlogged?
Type constraint violation diagnosis is the most underlogged: using Integer where Natural is semantically correct allows a negative value to propagate until it hits a context that raises CONSTRAINT_ERROR, producing a misleading stack trace far from the origin; fix is a type declaration change and an edge-case guard; diagnosis requires tracing the propagation path; 3 to 6 hours invisible. Task scheduling deadlock: two tasks waiting on each other’s protected entries hang silently; no crash, no error; 6 to 12 hours. SPARK proof failure resolution: a logically correct code change invalidates an existing GNATprove proof obligation; the developer must add or strengthen contracts so the solver can re-verify; 4 to 8 hours per failed obligation.
What are typical Ada developer retainer rates?
Entry-level Ada developers with 1 to 2 years covering basic Ada type system, sequential program maintenance, and GNAT toolchain familiarity typically bill at $75 to $130 per hour. Mid-level Ada programmers with 2 to 4 years covering Ada tasking, protected objects, SPARK Ada contracts, and embedded cross-compilation typically bill at $110 to $175 per hour. Senior Ada developers with 4 or more years covering SPARK Ada formal verification, DO-178C certification artifacts, and avionics or defense safety-critical systems typically bill at $150 to $260 per hour. Monthly retainer ranges: $2,000 to $4,500 per month for advisory engagements (12 to 25 hours per month); $4,500 to $12,000 per month for full engagement safety-critical software development.
What should an Ada developer retainer agreement include?
A retainer agreement should specify: Ada standard scope (Ada 83, Ada 95, Ada 2005, Ada 2012, Ada 2022; most active defense and avionics code targets Ada 2012 or earlier); SPARK Ada scope (whether formal verification proof maintenance is included; SPARK is a separately skilled subset of Ada); DO-178C certification scope (whether maintaining certification artifacts is included; artifact maintenance is a major time commitment separate from coding); GNAT toolchain scope (GNAT FSF vs AdaCore GNAT Pro; cross-compilation target; Alire vs manual library management); and hour logging format (the type declaration, the value and operation that produced the issue, the Ada type system mechanism, the fix applied, and the event count before and after).
How should Ada developer retainer hours be logged?
Log each Ada retainer session with: the type or subtype declaration at issue (e.g., Delta_Ticks : Integer in Scheduler package); the value and operation that produced the exception or wrong result (e.g., Delta_Ticks := Next_Wake_Tick - Current_Tick produced -3 when clock interrupt caused Current_Tick to exceed Next_Wake_Tick); the Ada type system mechanism (e.g., Integer allows negative values; Natural subtype range is 0 .. Integer'Last; passing -3 to Duration() raised CONSTRAINT_ERROR); fix applied (e.g., changed Delta_Ticks to Natural; added clock-ahead guard before subtraction); CONSTRAINT_ERROR events before and after. For SPARK: the subprogram, the proof obligation that failed, the contract added, and GNATprove output before and after.