Reliability · CP23 evidence

Battle tested, with the boundaries visible.

Trestle treats hardening as an attempt to falsify explicit guarantees. This page records what each campaign attacked, what passed, and what remains outside the evidence.

Evidence, not perfection

A green campaign establishes behavior for the workloads and failure classes exercised. It is not a security certification and does not manufacture years of field exposure.

Evidence status

CampaignStatusEvidence
23.1 Contracts and threat modelCompleteTrust boundaries, proxy spoof tests, redacted diagnostics, six release targets, checksum-verified update and rollback.
23.2 Input fuzzingComplete348,376 generated executions across filters, rules, webhook URLs and Lambda targets without panic or invariant failure.
23.3 Concurrency and failure injectionComplete64 contended jobs executed once; expired leases recovered; 20 race-enabled repetitions passed across queue, store and record packages.
23.4 Migration and upgrade matrixCompleteAll 14 schema starting points upgraded five times with preserved data, complete history and SQLite integrity; injected failure rolled back.
23.5 Recovery drillsCompleteFive database/file round trips plus corrupt, truncated, incomplete, future-schema, traversal and occupied-target failures; no partial target published.
23.6 Authorization abuseCompleteCross-owner and token/scope confusion matrix repeated ten times plus race suites; one individual row-rule bypass found and repaired.
23.7 Load and resource boundsComplete4,800 concurrent reads, explicit page/batch rejection and ten TCP slow-header trials; bounded responses and no retained goroutines.
23.8 Browser qualityCompleteRetained accessibility, keyboard, responsive, strict-CSP and embedded asset-budget checks.
23.9 Contract truthCompleteProduction configuration, module checksums, stale claims and all 80 generated documentation pages audited.
23.10 Release candidateCompleteComplete retained matrix, six verified archives, install, update, rollback and clean quickstart.

23.1 - Baseline contracts

The initial campaign mapped public HTTP, administrator sessions, bearer identities, SQLite, object storage, automation egress, release downloads and backup archives as separate trust boundaries. Tests prove that untrusted forwarded headers do not become trusted client identity, support bundles exclude secrets, and release replacement verifies the selected archive checksum before atomic installation.

The updater preserves one previous binary for explicit rollback and does not read or write Trestle instance data. This establishes release-file behavior, not database downgrade compatibility; restoration and rollback drills belong to 23.5.

23.2 - Adversarial input mutation

Four retained Go fuzz targets attack the typed filter parser/compiler, access-rule validator/evaluator, webhook target URLs and Lambda target validation. The campaign included valid seeds, injection-shaped values, private and credential-bearing URLs, malformed ARNs, oversized expressions and generated mutations.

Across 348,376 executions, accepted filters retained one SQL placeholder per bound argument, invalid rules never authorized, accepted webhook URLs retained the HTTPS/no-credentials contract, and target validators returned without panic. This is scoped mutation evidence, not proof over every input and not an authorization penetration test.

23.3 - Concurrency, interruption and restart

Eight workers contended for 64 durable SQLite jobs. Every job reached succeeded with exactly one recorded attempt and one executor call. A separately seeded running job with an expired lease modeled process interruption; the next worker returned it to pending, claimed it and completed its second attempt.

The queue, store and full record workflow then ran 20 times under Go's race detector. This attacks duplicate claims, shared handler state, SQLite serialization, optimistic mutations and restart recovery under the schedulers exercised here. It cannot enumerate every interleaving, kernel, filesystem or power-loss boundary.

23.4 - Every retained schema boundary

The migration matrix constructs a database at every retained schema state from version 0 through version 13, seeds durable data wherever the system table exists, and reopens it through Trestle's production initializer. Each path must reach the current version with all 13 migration records, the probe unchanged and PRAGMA integrity_check reporting ok.

All 14 paths passed five repetitions. A deliberately invalid version-7 migration starting from version 6 created a table and then failed inside one transaction; the table disappeared, the database remained at version 6 and prior data survived. A version-999 database remains explicitly refused. These are schema-fixture results; final release-binary and cross-platform upgrade compatibility remains part of the complete CP23 matrix.

23.5 - Restore what the backup claims to protect

A real authenticated backup containing a seeded SQLite row and local file object was restored into a new owner-only data directory five times. Trestle reopened the restored database through its production initializer, verified SQLite integrity, and recovered both probes byte-for-byte.

The restore boundary then received traversal and backslash paths, a missing database, a corrupt database, a future schema, a truncated ZIP and an already occupied target. Every case failed before the staged directory was atomically published. The release updater's update -> rollback drill separately restored the prior executable without opening instance data. Local objects are covered; S3 recovery remains the operator's provider-level backup/versioning responsibility.

23.6 - Attack authority across interfaces

The composed abuse matrix creates two application users, scoped service identities, independent owner rows and per-operation collection rules. It attacks filtered lists, direct record lookup, cross-owner update/delete, forged ownership on create, refresh-token use as an access token, read-scope mutation and browser-versus-bearer mutation semantics.

The first run found a real authorization defect: an individual owner rule was evaluated but a false result did not stop view/update/delete when the record ID was known. The shared decision point now returns the same 404 as an absent record while list rules retain SQL filtering. Ten repaired matrix repetitions and the race-enabled identity, rules, records, files and server suites passed. This is an engineering abuse corpus, not an independent penetration test.

23.7 - Bounded work under pressure

The load corpus seeded 100 typed records and issued 1,200 list requests from 24 concurrent workers. Three ordinary runs and one race-enabled run completed 4,800 requests with bounded response bodies, no failures and no retained goroutine growth. Oversized page and 1,001-record batch requests failed at their documented boundaries before expanding into database work.

A real TCP client then sent an incomplete HTTP header more slowly than the configured header timeout. Ten repetitions were disconnected before the request reached an application handler. These measurements establish behavior for this local machine and workload; they are not a universal requests-per-second or capacity promise.

23.8 - Browser quality is a contract

The embedded Nift dashboard is checked for document language, named navigation, route focus, current-page semantics, announced connection state, visible keyboard focus, Escape-to-close mobile navigation, reduced-motion handling and long-value containment. Trestle also emits a strict content security policy with no inline script or style dependency.

Performance remains explicit: generated HTML must stay below 24 KiB, CSS below 96 KiB and the complete three-file embedded dashboard below 320 KiB. These structural checks do not replace testing by people using assistive technology, but they prevent common regressions from silently shipping.

23.9 - Documentation must match the executable

The retained contract audit extracts every production TRESTLE_* configuration key from source and requires it to appear in the README or public documentation. It rejects stale hardening status, prohibited comparison wording and em dashes, verifies the recorded Go module graph, and resolves local links across all 80 generated HTML pages and 84 public files.

The audit corrected stale pre-hardening status and upgrade guidance before this checkpoint closed. It proves consistency with this commit, not that a future implementation change can skip review; the script exists so those changes fail visibly.

23.10 - One release-candidate gate

The final gate ran normal and race-enabled Go tests, vet, browser and contract audits, migration and recovery drills, authorization abuse, bounded load, concurrency stress, four fuzz targets, six cross-platform packages, checksum and archive verification, user installation, executable update and rollback, and a clean local launch.

Every available gate passed. Docker was unavailable in the execution environment, so trusted-proxy unit tests ran ten times instead of repeating the containerized Caddy/nginx smoke test from CP22. The CP23 matrix produced the v0.1.0 stable release; it is not an independent audit or a substitute for field history.

PostgreSQL campaign evidence

The PostgreSQL parity campaign is separate from the CP23 release-candidate evidence. SQLite and PostgreSQL are both first-class database options. This section records only what has been exercised against a real server.

  • Real providers tested: PostgreSQL 18.6 on disposable non-superuser databases locally, and PostgreSQL 16, 17 and 18 services in the CI matrix running the normal and race suites.
  • PG04 identity parity: competing first-admin setup creates exactly one administrator; administrator and application-user sessions, refresh/access tokens, service accounts and personal tokens run the same provider-parameterized suite on SQLite and PostgreSQL.
  • PG05 schema parity: all nine field types map to reviewed provider DDL; rename and additive changes preserve stable columns; incompatible schema changes fail closed with old metadata and rows intact; JSON is validated at the database level on both providers.
  • PG06 record parity: create/get/update/delete, 1,000-record atomic batches, idempotency, projections, optimistic concurrency and boolean filters behave identically; concurrent updates and idempotency races have exactly one winner.
  • PG07 query parity: null, number, datetime and case-insensitive contains semantics, sort direction, filter+cursor pagination and typed error mapping match on both providers.
  • PG08 access-rule and file-metadata parity: the authorization abuse matrix (row filtering, non-leaking denial, scope enforcement, service and personal credentials) and the file lifecycle suite (upload, range, quota, symlink rejection, orphan cleanup) run unchanged on SQLite and PostgreSQL; rule storage and administrator simulation match on both providers.
  • PG09 events, audit, jobs and integration parity: durable event sequencing and gap-tolerant replay, authorized realtime visibility, audit facts with redaction and filtering, and the job engine with deliberate per-provider claiming all run the same provider-parameterized suites; eight concurrent workers claim 64 jobs with exactly one execution each on SQLite and PostgreSQL, and rolled-back mutations publish no event, audit fact, webhook job or Lambda job.
  • PG10 backup, restore and operations parity: the portable logical archive round-trips in all four directions (SQLite to/from PostgreSQL), native SQLite snapshots are retained, logical archives restore into empty destinations with all-or-nothing publication, and the operations summary reports provider size facts; failures leave the target untouched on both providers.
  • PG11 migration, parity matrix and Incident Desk: offline migration round-trips both directions (SQLite to/from PostgreSQL) with read-only sources, explicit confirmation, dry-run, semantic destination verification and a checksum; the provider-parameterized product matrix runs on both engines; Incident Desk runs unchanged against both SQLite and PostgreSQL 18.6 (auth, records, files and realtime smoke passes on each).
  • Composed gate and availability: the CI workflow runs format, vet, frontend checks, all six cross-builds, release packaging/installer/update checks, and real Caddy and nginx TLS smokes against the same committed state; a completely green composed run closes the availability gate. PostgreSQL remains one external API contract with provider-specific internals; managed/serverless topologies and deeper provider metrics remain recorded limitations.

Service transaction evidence

trestle service install is covered by a stateful layered fake-systemd transaction model rather than a live systemd instance. The model tracks persistent enablement, runtime enablement, persistent mask, runtime mask and active state separately, and is-enabled is derived from those layers. Evidence includes prior unit bytes preserved and restored on rollback; prior enablement and activity inspected before mutation with only exactly-recreatable states accepted (enabled, enabled-runtime, disabled × active, inactive); masked, static, linked, generated, transient, failed, reloading and other non-restorable states refused before any mutation; persistent/runtime enablement normalization so runtime-only enablement is restored without a persistent link; failure injection after each lifecycle step with exact unit and raw-state restoration assertions; fresh-install rollback that stops and disables the unit while it is still loaded, then removes it and reloads systemd; a genuine no-op for a byte-identical unit already enabled and active; and tests under umask 0022 and umask 0000, race tests for the changed service package, and the six-target compilation contract.

This evidence is model-based and has not been proven through destructive live-service failure injection or against every systemd release. Ordinary-user dogfooding and validation on an actual host reboot remains a recommended release check.

Application registration policy evidence

Application-user registration policy (open, invite, approval, closed) is exercised by the normal and race suites on SQLite, with the real-PostgreSQL provider legs run through the disposable scripts/test-postgres-gate.sh instance. Evidence includes: the migration seeds the policy from the durable pre-migration schema version (a new database starts closed, an existing pre-v15 deployment starts open, never from row counts); registration serializes against policy changes on a dedicated singleton policy row (SQLite BEGIN IMMEDIATE, PostgreSQL SELECT ... FOR UPDATE) with bounded retry and 503 on lock exhaustion; the policy change winning the lock makes a later registration fail with no user row; invitation creation/list/revoke/accept, atomic single-use under concurrency, hash-only token storage, and revocation; email-bound acceptance with no email oracle; access-request submission is enumeration-resistant and rate-limited, pending requests expire after 14 days and rejected/expired rows are purged after 30 days; decisions remain available after leaving approval; reissue revokes older activation tokens; first-run setup commits the administrator and the policy atomically; backup/restore restores the saved policy by archive schema version, restores open for pre-policy archives, and revokes genuinely outstanding invitations while preserving used, already-revoked and expired states. This evidence is model- and regression-based and has not been proven through destructive live-system attacks; ordinary-user dogfooding remains recommended.

Application registration audit and one-time-token contract

The audit actor/target contract for application-user creation is explicit and tested: open registration and self-register invitation acceptance emit app_registration.user.create with actorKind system and actorID public-registration, administrator activation acceptance emits actorID activation, and in both cases the audit target is the created application-user ID so the durable record is identifiable; the creation path (register, self_register, activate) stays in bounded details and passwords or raw tokens are never included. Audit is a mandatory dependency of account creation: a handler without the audit handler wired fails closed (the account is not created) rather than silently committing a user without the promised fact. The password salt and every session/access/token secret come from a checked randomness helper that rejects a failing or partially filled entropy source on open registration, self-register acceptance and administrator activation acceptance, with tests proving no user row, an unused invitation, no user-creation audit fact and no misleading 201.

The one-time activation-token lifecycle is deliberate: the token is shown once with an explicit Dismiss action and is preserved across every in-view registration action (refresh, invitation revocation, access-request rejection, activation-base-URL save and application-user disable, which refresh only the relevant list). Route navigation flows through a single authoritative guarded path used by route links, restored routes, browser back/forward and collection navigation alike: every Trestle-owned history entry carries a position in history.state equal to its stack distance from the initial entry (derived from the rendered position, so it stays contiguous with the real history indexes even after a new route truncates a forward branch); ordinary route navigation pushes a position-carrying entry so the in-app history matches the rendered views; while an undisposed token is visible, navigation asks first, a rejected confirmation aborts entirely (no render, no active-route change, no token-state change, no history entry added, replaced, duplicated or truncated), and for a rejected browser traversal the browser returns to the rendered entry via history.go(-delta) with exactly the restorative popstate suppressed; a confirmed navigation deliberately clears the token state and panel before rendering the destination exactly once with no later phantom warning. Rejected Back or Forward traversals, including repeated and alternating rejections after branching, restore the complete entry array and original index unchanged and never request an out-of-range restorative history.go. Accepted Back and Forward traversals preserve the entry array while moving the index to the selected entry. Pushing a new route after Back deliberately truncates the old forward branch, with the remaining positions kept contiguous with their actual history indexes. Token-minting actions (provision, approval, reissue) are blocked with explanatory text while a token is pending, so a second secret cannot silently overwrite an undisposed first one; copying alone does not dismiss, dismissal is always explicit, and a failed logout retains the token and its navigation protection. Provisioning failures are surfaced distinctly in the view-error area without touching any visible secret. The activation base URL is trimmed and canonicalized before storage and the server returns that normalized value, so the very next issued token uses the configured link (built with the URL API, never raw concatenation), and the value survives server restart. Tokens are cleared on confirmed navigation or successful logout and are never written to persistent browser storage. The behavioural frontend regression drives the real event handlers and the guarded navigation state machine - including exact restoration of rejected Back and Forward traversals, entry-array preservation with the expected index movement for accepted traversals, and branch truncation with contiguous positions, plus collection navigation, logout success and failure, mint blocking, provisioning failure, normalized base-URL set/replace/clear and explicit dismissal - rather than source-text checks.

Release verification evidence

Current release: v0.1.5 is the current stable public-preview release. The entries below retain the detailed release-verification identifiers captured for earlier releases rather than rewriting historical evidence.

Stable v0.1.2 was released from reviewed commit 91c6441: release workflow run 33818618083 packaged the six archives plus SHA256SUMS, verified every checksum, attested build provenance, and created a normal non-draft non-prerelease release with stable-public-preview wording. The permanent read-only release-verification workflow (33818989370) then confirmed the exact seven-asset set, verified every SHA256SUMS entry, ran constrained gh attestation verify provenance on every archive against the pinned release workflow, and confirmed the release-body wording - read-only, with no edits.

Stable v0.1.1 was the prior patch release from reviewed commit 745fd0a (release workflow run 33482843753, read-only verification 33483410282); its compiled artifacts passed functional, packaging, checksum and provenance verification. An honest CI note from that release: normal CI at the tagged commit 745fd0a failed only because two Go files (cmd/trestle/envfile_owner_unix.go and cmd/trestle/envfile_owner_windows.go) lacked their final newline. That is a non-semantic formatting difference and did not affect the compiled v0.1.1 artifacts. The formatting was repaired forward on main at commit c6b24f9, where the complete normal CI workflow (Format/vet/builds/proxy gate and the PostgreSQL 16/17/18 suites) passed on run 33485974266. v0.1.0 established the initial public compatibility contract and was verified through its own release and read-only verification runs; its evidence remains part of this ledger.

How to read this page

Trestle separates input, concurrency, migration, recovery, security, load, interface and field evidence. A result in one category does not silently become a claim in another. Findings become retained regression tests, and this page changes only when the corresponding evidence has run.