Skip to content

Commit 11aa1fb

Browse files
committed
v2.5.0: the public API matches the implementation (ADR-0014)
MINOR bump per ADR-0006: the public header surface changed. Problem: include/retrace/retrace.h declared ~28 RETRACE_API functions; an nm -g audit of the built library showed only the two added in v2.4.0 actually existed (retrace_attach_process, retrace_list_backends) -- even retrace_version() had no definition. The header claimed "ABI-stable from v2.0.0" while consumers who wrote against it compiled fine and failed at link time -- the worst failure mode for a public interface. Fixed: - Removed the never-implemented declarations (engine lifecycle, script builder, action params, config parsing, introspection, error reporting). No working program can regress: the symbols never existed in any linkable build. - Implemented retrace_version() and retrace_version_info() for real in a new src/core/public_api.c. The shipped surface is now exactly 8 exported retrace_* symbols (6 pre-existing registry/backend + the 2 new version functions... plus the v2.4.0 pair), verified by nm. - test/unit/test_public_api.c -- the surface guard: dlsyms every function the header declares from the running image and fails the build if any is missing. A phantom declaration can never ship again. Also pins the version contracts and a behavior smoke for attach/list-backends. - docs/adr/0014-public-api-matches-implementation.md records the decision, the alternatives considered (implement-all-now rejected as it needs the engine-object redesign; keep-with- comment rejected because the failure mode remains), and the implementation-first re-introduction path. ADR index updated (0013 was also missing from it). Bumps: version.h, retrace_cli.c banner, nix/debian/fedora packaging, CHANGELOG.md. ctest --test-dir build: 57/57 pass (was 56).
1 parent 1c8556d commit 11aa1fb

13 files changed

Lines changed: 349 additions & 159 deletions

File tree

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,34 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
77
(see `docs/adr/0006-semantic-versioning.md`).
88

9+
## [2.5.0] - 2026-08-17
10+
11+
MINOR bump per ADR-0006: the public header surface changed.
12+
13+
### Changed
14+
- **The public API now matches the implementation** (ADR-0014).
15+
An audit (`nm -g` on the built library) showed that of ~28
16+
functions declared in `<retrace/retrace.h>`, only the two added
17+
in v2.4.0 actually existed — even `retrace_version()` had no
18+
definition. Consumers compiled against the header and failed at
19+
link time. The never-implemented declarations (engine
20+
lifecycle, script builder, action params, config parsing,
21+
introspection, error reporting) are removed; the re-
22+
introduction path is documented in the ADR and gated on real
23+
implementations. No working program can regress: the removed
24+
symbols never existed in any linkable build.
25+
26+
### Added
27+
- `retrace_version()` and `retrace_version_info()` — implemented
28+
for real in a new `src/core/public_api.c` (previously declared,
29+
never defined).
30+
- `test/unit/test_public_api.c` — the surface guard: dlsyms every
31+
function the header declares from the running image and fails
32+
the build if any is missing. A declared-but-unlinked symbol can
33+
never ship again. Also pins the version contracts and the
34+
attach/list-backends behavior smoke.
35+
- `docs/adr/0014-public-api-matches-implementation.md`.
36+
937
## [2.4.6] - 2026-08-17
1038

1139
### Changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# ADR-0014: The public API matches the implementation
2+
3+
- Status: Accepted
4+
- Date: 2026-08-17
5+
- Deciders: retrace maintainers
6+
7+
## Context
8+
9+
`include/retrace/retrace.h` has declared a rich public API since
10+
the v2.0.0 modernization plan: an engine lifecycle
11+
(`retrace_engine_create`/`destroy`), a programmatic script
12+
builder (`retrace_script_*`, `retrace_action_params_*`), config
13+
parsing entry points (`retrace_config_*`), introspection
14+
(`retrace_engine_list_prototypes`/`describe_*`), and error
15+
reporting (`retrace_last_error*`).
16+
17+
An audit before v2.5.0 (`nm -g` on the built library) showed that
18+
of the ~28 declared `RETRACE_API` functions, **only the two added
19+
in v2.4.0 actually existed** (`retrace_attach_process`,
20+
`retrace_list_backends`) — and even `retrace_version()` had no
21+
definition. The rest were scaffold that never landed.
22+
23+
The header also claimed "ABI-stable from v2.0.0". In practice a
24+
consumer who wrote against the header compiled successfully and
25+
failed at link time — the worst failure mode for a public
26+
interface: it lies at exactly the point where the lie is most
27+
expensive to discover.
28+
29+
## Decision
30+
31+
1. **The public header declares only implemented symbols.** The
32+
never-implemented declarations are removed. The shipped
33+
surface is: the status codes, the version macros
34+
(`include/retrace/version.h`), `retrace_version()`,
35+
`retrace_version_info()` (newly implemented), and the v2.4.0
36+
attach/list-backends pair.
37+
2. **A guard test enforces the contract.**
38+
`test/unit/test_public_api.c` dlsyms every symbol the header
39+
declares and fails the build if any is missing. Adding a
40+
declaration without a definition can no longer ship silently.
41+
3. **New API lands implementation-first.** When the engine-object
42+
and script-builder APIs are designed, each function is added
43+
to the header in the same change that defines it — the guard
44+
test makes anything else a build failure.
45+
46+
## Consequences
47+
48+
- Consumers compiling against the old header see compile errors
49+
on the removed names instead of link errors later. Since the
50+
symbols never existed in any linkable build, no working program
51+
can regress; this is a defect fix, not a breaking change.
52+
- The header shrinks from ~260 lines of partly-fictional API to a
53+
small, fully-real surface — the honest core of what the library
54+
guarantees today.
55+
- The re-introduction path for the richer API is unchanged
56+
(engine objects, script builder, config sources per the
57+
original plan) but is now gated on real implementations, and
58+
each addition will carry its own tests at the public boundary.
59+
60+
## Alternatives considered
61+
62+
- **Implement the full declared surface now.** Requires the
63+
engine-object redesign (the engine is process-global today, not
64+
a handle) plus script/params/config object lifetimes — a large
65+
design effort that should not block making the header truthful.
66+
- **Keep the declarations with a "not yet implemented" note.**
67+
Rejected: the failure mode (compile-then-link-fail) remains,
68+
and tooling that reads headers (language bindings, SWIG, IDEs)
69+
cannot see the comment.

docs/adr/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ via a new ADR that references the old.
1919
| [0009](0009-from-scratch-windows-trampoline.md) | From-scratch Windows trampoline (no MinHook, no Detours) | accepted |
2020
| [0010](0010-aarch64-float-params-from-day-one.md) | AArch64 port supports float parameters from day one | accepted |
2121
| [0011](0011-v1-removal-at-v2.1.0.md) | Remove v1 source at v2.1.0 | accepted |
22+
| [0013](0013-engine-mece-split.md) | Engine MECE split | accepted |
23+
| [0014](0014-public-api-matches-implementation.md) | Public API matches the implementation | accepted |

include/retrace/retrace.h

Lines changed: 20 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,37 @@
2424
*/
2525

2626
/*
27-
* retrace public API — see the modernization plan.md
27+
* retrace public API.
28+
*
29+
* Every function declared here is implemented and exported by the
30+
* library. test/unit/test_public_api.c enforces that contract at
31+
* build time (it dlsyms each symbol and fails the build if any is
32+
* missing).
33+
*
34+
* A larger engine/script-builder/config scaffold was declared here
35+
* from the v2.0.0 modernization plan but never implemented; the
36+
* declarations were removed in v2.5.0 because a header that
37+
* declares unlinked symbols breaks consumers at link time. See
38+
* docs/adr/0014-public-api-matches-implementation.md for the
39+
* decision and the re-introduction path.
2840
*
2941
* Design constraints (see docs/adr/0008-opaque-public-types-for-abi.md):
3042
* - All types are opaque handles. Struct definitions are internal.
3143
* - All functions return int (0 on success, negative on error).
32-
* - All output is caller-allocated with a size parameter, or returned as
33-
* a `const char *` owned by the engine.
34-
* - ABI-stable from v2.0.0.
44+
* - All output is caller-allocated with a size parameter, or returned
45+
* as a `const char *` owned by the engine.
3546
*/
3647

3748
#ifndef RETRACE_RETRACE_H
3849
#define RETRACE_RETRACE_H
3950

4051
#include <stddef.h>
41-
#include <stdint.h>
42-
43-
#include <retrace/version.h>
4452

4553
#ifdef __cplusplus
4654
extern "C" {
4755
#endif
4856

49-
/*
50-
* Visibility annotation. Public symbols are tagged RETRACE_API; everything
51-
* else is hidden by default (set via CMAKE_C_VISIBILITY_PRESET=hidden).
52-
*/
57+
/* Export/visibility. */
5358
#if defined(_WIN32) && defined(RETRACE_SHARED)
5459
# define RETRACE_API __declspec(dllexport)
5560
#elif defined(_WIN32) && defined(RETRACE_STATIC)
@@ -62,20 +67,6 @@ extern "C" {
6267
# define RETRACE_INTERNAL
6368
#endif
6469

65-
/* Opaque handles — definitions live in src/core/internal/. */
66-
typedef struct retrace_engine retrace_engine_t;
67-
typedef struct retrace_script retrace_script_t;
68-
typedef struct retrace_intercept_rule retrace_intercept_rule_t;
69-
typedef struct retrace_action_params retrace_action_params_t;
70-
71-
/* Result of an action callback. Drives engine dispatch. */
72-
typedef enum {
73-
RETRACE_ACTION_OK = 0,
74-
RETRACE_ACTION_SKIP_CALL = 1,
75-
RETRACE_ACTION_HANDLED = 2,
76-
RETRACE_ACTION_ERROR = -1,
77-
} retrace_action_result_t;
78-
7970
/* Status codes for public APIs. */
8071
typedef enum {
8172
RETRACE_OK = 0,
@@ -91,47 +82,14 @@ typedef enum {
9182

9283
/* ----------------------------------------------------------------- *
9384
* Version
85+
*
86+
* Compile-time: the RETRACE_VERSION_* macros (include/retrace/version.h)
87+
* and RETRACE_VERSION_ATLEAST(maj, min, pat). Runtime: the two
88+
* functions below, both returning library-owned static strings.
9489
*/
9590
RETRACE_API const char *retrace_version(void);
9691
RETRACE_API const char *retrace_version_info(void);
9792

98-
/* ----------------------------------------------------------------- *
99-
* Engine lifecycle
100-
*
101-
* The engine is the top-level owner of: the prototype registry, the action
102-
* registry, the backend handle, the active script, per-thread invocation
103-
* state, and the real-impl table (libc function pointers used internally
104-
* to avoid reentrancy).
105-
*
106-
* One engine per process is typical. Multi-engine is supported but each
107-
* engine has independent registries; prototypes are global (linker-section
108-
* scanned) so they are shared.
109-
*/
110-
RETRACE_API retrace_status_t retrace_engine_create(retrace_engine_t **out);
111-
RETRACE_API retrace_status_t retrace_engine_destroy(retrace_engine_t *eng);
112-
113-
RETRACE_API retrace_status_t retrace_engine_set_script(
114-
retrace_engine_t *eng, retrace_script_t *script);
115-
RETRACE_API retrace_status_t retrace_engine_get_script(
116-
retrace_engine_t *eng, const retrace_script_t **out);
117-
118-
RETRACE_API retrace_status_t retrace_engine_set_option(
119-
retrace_engine_t *eng,
120-
const char *key,
121-
const char *value);
122-
123-
/* ----------------------------------------------------------------- *
124-
* Backend selection
125-
*
126-
* Backends self-register at constructor time. The engine selects one by
127-
* probing each registered backend; `retrace_engine_select_backend` forces
128-
* a specific one. See include/retrace/backend.h (the modernization plan/03).
129-
*/
130-
RETRACE_API const char *const *retrace_engine_list_backends(
131-
retrace_engine_t *eng, size_t *count);
132-
RETRACE_API retrace_status_t retrace_engine_select_backend(
133-
retrace_engine_t *eng, const char *name);
134-
13593
/* ----------------------------------------------------------------- *
13694
* Process attach
13795
*
@@ -164,95 +122,6 @@ RETRACE_API retrace_status_t retrace_attach_process(int pid);
164122
RETRACE_API retrace_status_t retrace_list_backends(
165123
const char *const **out_names, size_t *out_count);
166124

167-
/* ----------------------------------------------------------------- *
168-
* Programmatic script builder
169-
*
170-
* Build a script in C without parsing a file. Equivalent to the JSON path
171-
* but with no serialization in between.
172-
*/
173-
RETRACE_API retrace_script_t *retrace_script_new(retrace_engine_t *eng);
174-
RETRACE_API void retrace_script_free(retrace_script_t *script);
175-
RETRACE_API retrace_status_t retrace_script_validate(
176-
retrace_script_t *script, char *err_buf, size_t err_len);
177-
178-
RETRACE_API retrace_status_t retrace_script_add_intercept(
179-
retrace_script_t *script,
180-
const char *func_glob,
181-
retrace_intercept_rule_t **out);
182-
183-
RETRACE_API retrace_action_params_t *retrace_action_params_new(void);
184-
RETRACE_API void retrace_action_params_free(retrace_action_params_t *params);
185-
186-
RETRACE_API retrace_status_t retrace_action_params_set_int(
187-
retrace_action_params_t *params,
188-
const char *name, long long value);
189-
RETRACE_API retrace_status_t retrace_action_params_set_double(
190-
retrace_action_params_t *params,
191-
const char *name, double value);
192-
RETRACE_API retrace_status_t retrace_action_params_set_string(
193-
retrace_action_params_t *params,
194-
const char *name, const char *value);
195-
196-
RETRACE_API retrace_status_t retrace_intercept_rule_add_action(
197-
retrace_intercept_rule_t *rule,
198-
const char *action_name,
199-
retrace_action_params_t *params);
200-
201-
/* ----------------------------------------------------------------- *
202-
* Config parsing
203-
*
204-
* Delegates to the named config source (typically "json" or "text").
205-
* Sources self-register at constructor time. See the modernization plan/04.
206-
*/
207-
RETRACE_API retrace_status_t retrace_config_parse_file(
208-
retrace_engine_t *eng,
209-
const char *source_name,
210-
const char *path,
211-
retrace_script_t **out);
212-
213-
RETRACE_API retrace_status_t retrace_config_parse_buffer(
214-
retrace_engine_t *eng,
215-
const char *source_name,
216-
const char *buf, size_t len,
217-
retrace_script_t **out);
218-
219-
RETRACE_API const char *const *retrace_config_list_sources(
220-
retrace_engine_t *eng, size_t *count);
221-
222-
/* ----------------------------------------------------------------- *
223-
* Inspection
224-
*
225-
* Used by the CLI (`retrace prototypes list`, etc.) and by tooling that
226-
* wants to introspect the engine. Output arrays are NULL-terminated and
227-
* owned by the engine until the next call to the same function.
228-
*/
229-
RETRACE_API const char *const *retrace_engine_list_prototypes(
230-
retrace_engine_t *eng, size_t *count);
231-
RETRACE_API const char *const *retrace_engine_list_actions(
232-
retrace_engine_t *eng, size_t *count);
233-
234-
RETRACE_API retrace_status_t retrace_engine_describe_prototype(
235-
retrace_engine_t *eng,
236-
const char *func_name,
237-
char **out_desc); /* caller frees with retrace_free() */
238-
239-
RETRACE_API retrace_status_t retrace_engine_describe_action(
240-
retrace_engine_t *eng,
241-
const char *action_name,
242-
char **out_desc);
243-
244-
RETRACE_API void retrace_free(void *ptr);
245-
246-
/* ----------------------------------------------------------------- *
247-
* Error reporting
248-
*
249-
* The most recent error for the calling thread is held in thread-local
250-
* storage. `retrace_last_error` returns the message (owned by the engine
251-
* until the next call on the same thread).
252-
*/
253-
RETRACE_API const char *retrace_last_error(retrace_engine_t *eng);
254-
RETRACE_API int retrace_last_error_code(retrace_engine_t *eng);
255-
256125
#ifdef __cplusplus
257126
}
258127
#endif

include/retrace/version.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@
3737
#define RETRACE_VERSION_H
3838

3939
#define RETRACE_VERSION_MAJOR 2
40-
#define RETRACE_VERSION_MINOR 4
41-
#define RETRACE_VERSION_PATCH 6
40+
#define RETRACE_VERSION_MINOR 5
41+
#define RETRACE_VERSION_PATCH 0
4242

43-
#define RETRACE_VERSION_STRING "2.4.6"
43+
#define RETRACE_VERSION_STRING "2.5.0"
4444

4545
#define RETRACE_VERSION_ATLEAST(maj, min, pat) \
4646
(RETRACE_VERSION_MAJOR > (maj) || \

packaging/debian/changelog

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
retrace (2.5.0-1) unstable; urgency=medium
2+
3+
* Public API now matches the implementation (ADR-0014): removed
4+
~26 never-implemented declarations from retrace.h, implemented
5+
retrace_version()/retrace_version_info(), added a dlsym
6+
surface-guard test.
7+
8+
-- Ribose Inc <opensource@ribose.com> Mon, 17 Aug 2026 00:00:00 +0000
9+
110
retrace (2.4.6-1) unstable; urgency=medium
211

312
* docs: architecture/development/faq refreshed for v2.3.x-v2.4.x

packaging/fedora/retrace.spec

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
#
33
# Fedora RPM spec for retrace (TODO.complete/38).
44
# Build: rpmbuild -ba retrace.spec
5-
# Install: dnf install retrace-2.4.6-1.*.rpm
5+
# Install: dnf install retrace-2.5.0-1.*.rpm
66

77
Name: retrace
8-
Version: 2.4.6
8+
Version: 2.5.0
99
Release: 1%{?dist}
1010
Summary: Userspace libc interceptor for security discovery
1111

@@ -50,6 +50,9 @@ action, OTLP/JSON export, and a Python config builder.
5050
%{_includedir}/retrace/
5151

5252
%changelog
53+
* Mon Aug 17 2026 Ribose Inc <opensource@ribose.com> - 2.5.0-1
54+
- Public API matches implementation (ADR-0014): trim phantom decls, implement version fns, surface-guard test
55+
5356
* Sun Aug 16 2026 Ribose Inc <opensource@ribose.com> - 2.4.6-1
5457
- architecture/development/faq docs refreshed for the v2.3.x-v2.4.x era
5558

packaging/nix/default.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
stdenv.mkDerivation rec {
1010
pname = "retrace";
11-
version = "2.4.6";
11+
version = "2.5.0";
1212

1313
src = ./.;
1414

src/cli/retrace_cli.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ typedef int retrace_status_t;
6363
static void usage(FILE *out)
6464
{
6565
fprintf(out,
66-
"retrace v2.4.6 -- userspace libc interceptor\n"
66+
"retrace v2.5.0 -- userspace libc interceptor\n"
6767
"\n"
6868
"Usage:\n"
6969
" retrace run [OPTIONS] -- <command> [args...]\n"

0 commit comments

Comments
 (0)