---
contract-id: hackylens.feature-app-sdk
owner: platform-architecture
version: 0.2.0
stability: experimental
compatibility-app-runtime: >=0.2.0,<0.3.0
compatibility-app-manifest: >=0.1.0,<0.2.0
compatibility-capability-api: >=0.1.0,<0.2.0
---
This document describes the SDK exposed by sdk/include/. Public interface
changes follow the change process.
The Feature App SDK is the public C entry surface for lifecycle-v2 native apps.
All SDK-owned public headers live under sdk/include. An app includes only SDK
headers and the public capability types intentionally exposed or re-exported by
them.
The SDK MAY depend on public Capability API headers under
firmware/include/hackylens/capability/. That is the only permitted SDK to
Capability dependency direction. The SDK and apps MUST NOT include capability
implementation/provider/private headers, portable-service private headers,
storage internals, drivers, board/BSP headers, platform/HAL headers, the K210
SDK, runtime-private headers, or generated-registry private headers.
The SDK does not replace public capability types with parallel wrappers.
Lights uses runtime-owned channel sessions, obtained with
hk_app_context_lights(ctx, channels, &session) and retired by runtime teardown.
The binding is immutable; a channel session must not be copied or moved.
Display similarly uses hk_app_context_display(ctx, plane, &session), returning
a stable runtime-owned plane session. Draw calls take that pointer without an
owner argument. Runtime retires both planes on teardown, including failure.
External Link uses hk_app_context_external_link(ctx, mode_features, &session)
and runtime-owned connector storage. Operation tokens remain separate and
retain their generations across session reopen. A new wrapper type requires a concrete ABI, ownership, or
language-boundary reason recorded in the contract; naming convenience is not
sufficient.
The canonical umbrella is sdk/include/hackylens/app.h. It publishes the
Feature App SDK 0.2.0 version, the accepted App Runtime range [0.2.0, 0.3.0),
Capability API range [0.1.0, 0.2.0), and Native App Manifest schema major 1
as numeric compile-time metadata. sdk/include/hackylens/app/runtime.h owns the
public lifecycle callback typedefs and immutable hk_app_v2_entry_t definition;
the firmware runtime consumes that definition and MUST NOT maintain a private
parallel lifecycle ABI.
SDK 0.2.x defines or re-exports only:
- lifecycle callback types
start,event,render, andstop, plus stop reasons from App Runtime0.2.x; - the borrowed, generation-checked
hk_app_context_tinterface; - ordered app events, including Timer and Runtime Close;
- the bounded view/surface interface backed by a runtime-owned Display session;
- typed Time/Input bindings and scoped Lights/Display/External Link sessions;
- fixed-capacity private app-state access;
hk_app_context_request_closeas the portable close-request seam;- result, version, deadline, cancellation, buffer, and capability types from
Capability API
0.1.x.
The context exposes no mutable descriptor, registry table, provider vtable, raw filesystem block access, unrestricted platform path, raw camera/DVP object, peripheral instance, route, board ID inference, HAL object, or driver pointer. Absence and optional fallback are explicit results, never guessed from hardware identity.
The public definition is sdk/include/hackylens/app/context.h. It carries ABI
size/version, app identity, context generation and immutable Time/Input binding
pointers. It contains no generic owner, grant table or runtime inventory.
Lifecycle callbacks receive const hk_app_context_t *; writable app state is
available through hk_app_context_state.
During start through stop, accessors validate the current callback context.
Time/Input access returns the board-lifetime binding or
HK_ERR_CAPABILITY_ABSENT. Lights, Display and External Link accessors return
stable runtime-owned sessions. Their providers enforce actual channel, plane
and connector conflicts. New sessions cannot be opened during teardown.
Build-time required-service checks exclude incompatible apps before firmware
compilation; runtime does not repeat manifest version/feature negotiation.
The event, stop-reason, wakeup-token, and render-surface definitions are in
sdk/include/hackylens/app/runtime.h. hk_app_event_t is a bounded
size/versioned union for Input, SD/media, Timer, Runtime Close, and Wakeup. Its
runtime sequence is strictly increasing within one generation. Input embeds
the public Capability API hk_input_event_t; no parallel input or button type
is introduced. BACK is delivered as an ordinary Input event. An app that should
leave on BACK calls hk_app_context_request_close; the runtime does not
intercept BACK as navigation.
hk_app_context_wakeup_token creates a fixed slot/context-generation/epoch
token with one app-private value. Holding the token creates no work and grants
no authority by itself. A producer may return it only through a runtime-owned
bounded completion path; stale tokens are rejected before app dispatch.
hk_app_context_request_render records a full or rectangular invalidation.
The fixed limit is eight rectangles. hk_app_surface_t is opaque and borrowed
only for the current render callback. Its API permits display information,
invalidation, clear, rectangle, text, blit, and lock of the runtime-owned BASE
backing store; it deliberately has
no present, batch, plane-ownership, framebuffer-ownership, LCD, driver, HAL, or
platform operation. hk_app_surface_lock and command-batch drawing are
mutually exclusive for one render pass. Retaining a surface pointer after callback return is an SDK
contract violation and every use outside that borrow returns a stale-handle
error when observable by the runtime.
An invalidation requested from inside render applies to a later render pass;
it is not consumed by the pass already in progress. Runtime schedules that
pending pass immediately rather than waiting for the next manifest tick.
Tick and render intervals/budgets come only from the immutable generated
descriptor. Apps cannot refresh them or create a catch-up loop. Timer events
run synchronously on the existing firmware loop; there is no public tick
callback. Render runs only for a pending invalidation and runtime owns Display
begin/present/abort.
The SDK declares:
hk_result_t hk_app_context_teardown_deadline(
const hk_app_context_t *ctx,
hk_deadline_t *deadline);During stop, this accessor returns the one finite absolute monotonic deadline
stored by App Runtime when teardown began. It returns HK_ERR_INVALID_STATE
outside teardown. Repeated calls return the same hk_deadline_t; the accessor
MUST NOT refresh, extend, partition, or derive a per-stage or per-provider
deadline.
Stop code passes this value unchanged to deadline-aware capability and service
release operations. It does not call a raw clock, choose a hardware timer, or
acquire a second Time implementation. App Runtime creates the value exactly once
through public hk_time_deadline_after_us, the immutable Time service binding,
and a runtime-controlled finite policy
budget; the manifest and app cannot configure or extend it. An already-expired
value remains the required value for later scoped service cleanup.
Contexts, handles, surfaces, events, and state views are borrowed for the
lifetimes specified by App Runtime and the corresponding capability. Apps MUST
NOT retain a callback-scoped context or surface after the callback returns.
Scoped service sessions remain valid during stop; runtime then attempts
retirement of every session before invalidating the context and reusing state.
Sessions must not be copied or moved: provider claims use their stable address.
Copying a context does not extend its lifetime or authorize a later generation.
Immutable Time/Input bindings have board lifetime; context and asynchronous
operation generations remain where they protect real lifetimes.
Session storage and cleanup authority remain private to runtime. Cleanup never trusts fields read back from a callback's public context snapshot.
SDK functions are bounded and allocation-free. They do not create tasks,
queues, cores, general background work, or hidden heap storage. Large buffers
remain explicit borrows. App state is descriptor-sized fixed storage and is
reused only after the normative runtime teardown and generation invalidation.
The app entry supplies state storage and its size; the descriptor uses the fixed
HK_APP_STATE_ALIGNMENT ABI policy; alignment is not a runtime request or an
app-manifest tuning field.
The SDK contract is platform-independent. Host tests compile the production app runtime core with typed Time, Input, and Display doubles rather than a second lifecycle engine. Test support may create descriptors, provide clock and capability/service operations, call the production runtime API, and record traces or inject faults at those seams. It MUST NOT implement its own lifecycle states, transitions, unwind, teardown ordering, or generation model.
Public lifecycle callbacks are start, event, render, and stop(ctx).
hk_app_context_request_render and hk_app_context_request_close remain legal
only in RUNNING, never while start is executing. Lifecycle HK_PENDING is
normalized to HK_ERR_INVALID_STATE by production Runtime. Teardown creates
one finite absolute monotonic deadline at teardown start and uses that same
deadline for stop and scoped service provider cleanup.
Typed service operations preserve their bounded behavior. Every Input event reader has an
independent caller-owned sequence cursor and reports HK_ERR_OVERFLOW with the latest
stable state and exact dropped count before resynchronizing without replay.
Time rejects durations above HK_TIME_MAX_SLEEP_US and addition overflow with
HK_ERR_LIMIT. Ordinary close rejects UINT64_MAX as an invalid absolute
deadline; forced retirement still invalidates the session and quarantines an
unsafe resource on failure. Display treats zero-area rectangles as
successful no-ops, accepts set_clip(NULL) as the full display, requires the
normative batch/surface state for commands, present, and abort, and preserves
staged state after validation failure.
Standalone CMake consumers use add_subdirectory(<hackylens>/sdk) and link
HackyLens::AppSDK. Make consumers include sdk/hackylens-app-sdk.mk and use
its public include flags. App sources compile from SDK headers plus their own
private headers; they do not include firmware runtime, generated registry,
board, platform, HAL, driver, or provider headers. Host tests may compile
production runtime sources privately as test support. That is not a public host
runtime framework and MUST NOT be installed or exported as an SDK target.
The SDK conformance gate recursively checks public header closure, compiles C11 and C++17 consumers, and builds/runs one minimal lifecycle-v2 app through both CMake and Make against the production runtime test support. The architecture guard rejects SDK dependencies on repository-private layers. Bundled apps are checked for architecture layering, cross-feature dependencies and raw platform access. Their existing portable firmware services are not exported by the SDK. Standalone fixtures still reject private and undeclared third-party headers; C++ syntax does not waive resource ownership rules.
A native app is portable only when it builds against this entry surface and its declared public capabilities. Successful compilation against SEN0305 private headers is not SDK conformance.
SDK 0.2.x accepts App Runtime 0.2.x, Native App Manifest 0.1.x schema major
1, and Capability API 0.1.x. Runtime and SDK consumers request
[0.2.0, 0.3.0); Capability API remains [0.1.0, 0.2.0). An experimental
breaking change increments MINOR. Publishing this SDK does not change Firmware
0.4.0, HMPY 1.1.0, Board Port 0.1.0, or
MicroPython API 1.0.0.
All twelve bundled apps use the same runtime entry. No legacy adapter or manifest lifecycle selector remains. Menu, BACK, autostart, debug forced exit, safe-mode fallback, and rapid switching use one close/unwind decision.
Bundled apps may call the existing portable firmware services under architecture layer and feature-boundary guards. That does not make those services part of the standalone SDK. Standalone SDK fixtures and external apps still require strict public-header closure; migrating a lifecycle alone does not prove standalone portability. Diagnostic handlers run only for an explicitly matched command. Resource cleanup polls pending KPU/core1 completion, not inactive apps.