Files
PaperClipAI/doc/sleep-qualification.md
T
2026-11-03 17:41:15 -06:00

245 lines
15 KiB
Markdown

# Sleep configuration qualification
This opt-in harness boots the actual server against an explicitly supplied fresh
test database. It measures local SQL, connection and network quiet. A local pass
does not establish Railway or Neon suspension. Keep a separate verdict for each
layer: quiet, eligibility, database suspension, host suspension, backup persistence,
inbound wake, timed wake and return to idle.
## Local configured services
Use a reviewed integrated source containing the incoming-wake and domain-deadline
contracts. Provision and migrate a fresh dedicated PostgreSQL database ending in
`_test` through the normal test operator. The suite does not allocate a database,
start embedded PostgreSQL or delete a database. It preserves its fixture rows.
Never supply a live tenant database.
The configured exporter needs the exact optional peer versions already declared
in `server/package.json`. With Node 24 and npm available, explicitly install the
fixture peers into a new temporary prefix and link only missing packages in this
checkout:
```sh
node scripts/sleep-qualification/setup-otel.mjs
```
The setup leaves application manifests and lockfiles unchanged. Retain the printed
setup receipt and temporary package lock. A version mismatch fails without
replacing an installed package. An opt-in CI job must run the same setup command;
setting an OTLP endpoint alone does not qualify an exporter.
The helper records its exact symlink targets in
`server/node_modules/.paperclip-sleep-otel-links.json`. If an owned temporary prefix
disappears, rerunning repairs only those matching recorded symlinks. Existing
matching packages are left alone. An unrecorded dangling link or other entry is
preserved with a recovery error; its pathname is not evidence of ownership.
`node scripts/sleep-qualification/setup-otel.mjs --cleanup` unlinks only matching
owned links. It preserves foreign replacements and keeps temporary install
prefixes for setup evidence. Remove those owned prefixes separately when their
receipts are no longer needed.
Set `PAPERCLIP_SLEEP_CONFIGURED_ACCEPTANCE=1`,
`PAPERCLIP_SLEEP_ACCEPTANCE_TEST_DATABASE_URL` to the owned migrated fixture and
`PAPERCLIP_SLEEP_CLOUD_SHA` to the exact reviewed Cloud source SHA. Set
`PAPERCLIP_SLEEP_CLOUD_CHECKOUT` to that built private checkout; the suite verifies
its HEAD and imports its production broker adapter without copying private source.
Retain artifacts
with `PAPERCLIP_MAINTENANCE_KEEP_ARTIFACTS=1`. Run the two scoped Vitest files with
one worker and no file parallelism:
```sh
pnpm exec vitest run server/src/__tests__/sleep-otel-export.test.ts \
server/src/__tests__/sleep-configuration-acceptance.test.ts \
--maxWorkers=1 --no-file-parallelism
```
The first case requires a successful named span at the real local OTLP collector
and no metric or log export during 90 real seconds, beyond the SDK's default
60-second metric cadence. It fails if the production SDK falls back to no-op.
The assembled case keeps
normal automatic backups, announcements, first-party telemetry and OTel enabled.
It creates an agent, useful completed process execution, API credential and future
routine through normal APIs. It waits the real ten-minute authenticated-use
cooldown, then measures at least 90 real seconds without tenant HTTP/SQL observer
polling. A passive listener on the actual app HTTP server counts incoming requests
and WebSocket upgrades, including direct clients outside the harness API helper.
It requires the app's existing request and upgrade handlers before installing;
it cannot become the sole upgrade listener and change unsupported-upgrade handling.
IPC snapshots record its capture identity, installation/validity, cumulative safe-integer
counts and observation times. Both snapshots must enclose the complete quiet interval,
remain valid and yield an exact zero delta. No URL, headers or body are recorded;
snapshot reads themselves make no HTTP requests or SQL queries. Missing or detached
capture, malformed counts and the legacy empty `observerRequests` list cannot prove
quiet. The passive protocol proxy separately measures database effects. It obtains
two owned signed safety reports after the window and preserves fixture history.
After ordinary API reactivation it waits another full real ten-minute cooldown,
measures another 90 seconds of quiet and requests fresh owned eligibility proof.
The helper keeps its legacy defaults for existing maintenance tests.
Announcements are enabled and the actual route/feed service validates and caches
the known fixture. An owned test-child module wrapper injects the existing feed
transport seam for the exact default HTTPS URL; unmatched requests fail. Startup
fails if the wrapper did not intercept the actual app constructor. The receipt
requires one named fetch and no subsequent calls during quiet. Browser mode also
requires the fixture copy to render. The production DNS/HTTPS address guard stays
unchanged and is not exercised in this combined case; its owning tests cover that
boundary separately. Active user activity can legitimately keep an instance awake.
The assembled scenario includes real tool approval rejection/delivery retirement
and signed chat-webhook completion followed by ordinary endpoint removal. That
removed-endpoint subcase is separate from the retained enabled GitHub connection:
the latter uses actual encrypted user credentials, refresh/continuity owners,
signed broker bindings and the production event delivery worker. The host wake
capability comes from the production connector store. Local in-memory broker
storage and synthetic GitHub HTTP responses remain explicit external boundaries;
this does not establish deployed broker durability or host suspension. These
assembled paths require a successful run against the reviewed integration before
their receipts can qualify them; source presence alone is not evidence.
Unknown plugins, persistent provider sessions, unfinished delivery/accounting,
uncertain writes and cleanup remain real blockers. Every inventory family needs
an explicit disposition before claiming all configurations qualified.
## Evaluate a receipt
```sh
pnpm exec vitest run server/scripts/sleep-qualification.test.mjs --maxWorkers=1
node scripts/sleep-qualification/evidence.mjs receipt.json expected.json report.json
```
`expected.json` binds `appSha`, `cloudSha`, `target` and a SHA-256 `configDigest`.
The digest uses `logicalSleepConfiguration(configuration)`: explicit backup interval
and checkpoint policy, announcements, telemetry activation, OTLP activation/protocol,
and UI activation in a fixed order. It excludes ephemeral loopback ports. Compute
the expected logical contract independently before starting the fixture; the
evaluator recomputes the receipt digest. Keep actual `collectorOrigins.telemetry`
and `.otel` separately, matching the actual configuration and optional expected
origin bindings.
Configured coverage must also have recorded observations. Announcements require
actual interception, a request for the expected feed, and no unmatched URL.
`signalObservation` records telemetry requests, trace requests, actual OTLP span
records and the named qualification span separately. Metric, log, malformed-trace
and unexpected request counts must be explicitly zero. The assembled case freezes
these observations after each quiet window; feature booleans are derived from
them and the observed backup checkpoint. Missing or malformed counts fail local
qualification even when a feature flag claims success.
Before the normal cooldown, a signed diagnostic-only inventory saves both raw
reports. It explicitly expects the active API cooldown aggregate and the skipped
backup diagnostic; every other reported blocker must be absent. Aggregate local
probes cannot identify which probe is present, so this early inventory never
proves eligibility. Its hold is released before the full unchanged cooldown.
After quiet, raw ordinary reports are saved before assertions; a refusal also saves
a diagnostic report under that hold.
Automatic backup coverage requires a new archive from the normal idle checkpoint,
not an enabled flag. After quiet counters freeze, the harness reads every new
owned gzip through its trailer, hashes compressed bytes, checks nonempty SQL for
the fixture identifier without retaining contents, and observes the wake marker
and absence of backup failure markers. This local evidence carries no provider
volume or restore claim. The owning backup suite separately qualifies restoration.
Archive provenance uses a positive finite opened-file birthtime, falling back to
positive finite content mtime only when the filesystem reports birthtime as zero.
The selected timestamp and its source are recorded. Existing names, old positive
birthtimes, malformed timestamps and future chronology remain disqualifying.
Original receipts are retained as produced; the new evidence fields are not
retroactively added to earlier runs.
The historical Full Y at `487e450b` and short composition at `57744f4e` wrote an
unmeasured empty observer-request list. Their recorded SQL, connection, outbound,
browser and producer observations remain pinned to those runs, but that list does
not meet the corrected inbound-observation contract. A new combined qualification
requires both real cooldowns and quiet windows with the installed incoming counter;
a short injected-poll regression cannot retroactively establish observer absence.
Receipt finalization follows all observations and runs the same evaluator before
writing the final receipt. A return-to-idle claim also needs explicit in-run quiet
start/end timestamps spanning at least 90 real seconds after the latest wake;
a duration field alone cannot prove it.
The evaluator rejects mismatched identities and chronology. Missing evidence stays
`INCONCLUSIVE`; a measured failed condition is `NOT VERIFIED`, including an
identity-verified active Neon endpoint or exact Railway service with running deployments. It does not produce
provider proof from local counters, fake time, a registry sleep flag or a successful
control response. Preserve original failures and their raw logs after a retry.
## Deployed boundary
No deployment target is authorized by this document. The private Cloud companion
provides a gated read-only provider observer. An operator must first approve one
exact owned disposable staging target, revisions, configuration, provider refs,
fixture operations and finite observation deadline. A named connector or shared
QA workspace is not that authorization.
Capture app/Cloud identity and effective policy while awake. Use only provider and
central control-plane metadata during sleep; tenant health/API/SQL probes can wake
the target. Confirm the exact Neon endpoint is idle and all owned Railway services
have stopped deployments. Observe ordinary authorized API, broker and deadline
wake cycles separately. Verify useful persisted results and return to idle.
Automatic backup persistence additionally requires an exact attached volume
covering the configured home/backup directory and a fsynced sentinel/archive
continuity receipt across the owned stop/wake. A writable local fallback directory
is insufficient. Missing live access leaves these layers `INCONCLUSIVE`.
For actual foreground/background browser coverage, first build the shipped UI into
`server/ui-dist` in the same reviewed checkout. Install the normal Playwright
Chromium runtime and run on a display-capable worker. Set
`PAPERCLIP_SLEEP_BROWSER_ACCEPTANCE=1` for the configured suite. The harness loads
the actual UI, records an ordinary active reload, opens another real foreground
tab and requires Chromium to report the app tab hidden. It observes 90 real
seconds without app API traffic, then closes the browser before the server quiet
window. Failure to establish real visibility fails that mode; it never substitutes
a fake visibility event or frozen page. Without this explicit mode, the receipt
claims closed-browser coverage only.
The browser uses a fresh owned Chromium profile and loopback debugging endpoint.
It attaches with `connectOverCDP({ noDefaults: true })` to the native default
context. Normal Playwright contexts install focus emulation that kept real
background tabs visible in the measured failure. This path avoids that override.
The receipt includes native window IDs, initial/final visibility and passive
visibility events for the entire 90-second interval. An intervening activation
fails the observation. Cleanup disconnects, terminates only the owned process and
removes its temporary profile. It never attaches to a user's existing browser.
## Managed fixture preflight
Configured qualification uses the supported managed authenticated profile. The
actual app runs on loopback, with a lifetime-stable fixture vault key and the
normal managed configuration. A fixture Cloud front door supplies trusted tenant
headers to invoke the real primary-company/user/membership provisioning path;
ordinary company creation remains forbidden. Bearer-key and signed-control
requests omit tenant headers. Native browser headers can reach only the owned
app origin; foreign HTTP/WebSocket transports are blocked and fail qualification.
Managed application storage requires S3. A bounded loopback protocol fixture
supplies the remote object service for actual SDK requests; unsupported operations,
framing and conditional requests fail closed. This establishes neither provider
IAM nor durable S3 storage. The instance home and database archives remain real
local filesystem paths. Normal managed migration, historical cleanup and routine
owners remain enabled. The logical digest binds managed activation, auth mode and
storage provider; actual ephemeral transport origins are recorded separately.
Before another long campaign, run the opt-in `sleep-managed-preflight.test.ts` on
its own root-leased fresh external database using
`PAPERCLIP_SLEEP_MANAGED_PREFLIGHT=1` and an explicit dedicated
`PAPERCLIP_SLEEP_MANAGED_PREFLIGHT_TEST_DATABASE_URL`. Retain artifacts with
`PAPERCLIP_MAINTENANCE_KEEP_ARTIFACTS=1`. It uses the same profile and negotiated
broker receiver, provisions real authority, admits and completes one process run,
and proves real key activity through signed diagnostics. It performs no browser,
ten-minute wait or quiet-window claim. No embedded allocation or automatic drop.
Loopback execution requires the host tool's explicit network/runtime permission.
Setup and successful browser receipts are written immediately before later
assertions. Early diagnostics first use the existing owner-settled contract; they
reject durable blockers immediately and retry only observed local in-flight /
generation inspection incompleteness, up to three rounds. Every attempt is saved.
Both accepted reports must be strict, complete, structurally matching and under
the exact live hold, with each internal `inspection.generation=none` and no active
work before/after. Real HTTP authentication itself changes the global work
counter, so equality across deliberate observer requests is not asserted.
A fresh successful opaque-key request anchors the later full real cooldown.
Neither setup stabilization nor the fast preflight substitutes for both real
600100ms cooldowns, both untouched 90000ms windows and useful reactivation. S3
observations are saved before and after owned app cleanup, including failures;
cleanup preserves the original error. Full provider sleep remains a separate
unqualified layer until an exact deployed target is authorized and observed.