api: handlers hold no logic, and one generated contract (plan 2.3, 2.4) - #91
Merged
Conversation
2.4 — choreography the daemon's handlers were carrying moves into
`springtale-runtime::operations`, so a second surface cannot drift from
it. New operations: `authors` (the `trusted-author:` prefix and the
32-byte Ed25519 check in one place), `chat::ingest`, `bot::{status,
memory_summary}`, `sessions::list`, `approvals::{pending, resolve}`
through the runtime `ApprovalGate` trait, and `heartbeat::{get, set,
boot_interval}` — one operation that persists the interval and applies
it to the monitor, with boot reading the key back so a restart keeps the
setting. The events limit clamp moves into `operations::events::list`.
Rule create/update/delete already went through `rules::{create_and_
activate, update_and_reactivate, delete_and_deactivate}`; the desktop
holds no runtime any more, so the duplicates the plan names are gone.
Raw `serde_json::Value` bodies become typed request structs with no
silent defaults: `CreateFormationRequest` (intent and connectors are
required — a formation whose posture nobody chose is not a default),
`CompactMemoryRequest`, `Set/StepAutonomyRequest`, `AddAuthorRequest`,
`SetHeartbeatRequest`, `IncomingMessage`, `ResolveRequest`.
2.3 — 138 handlers carry `#[utoipa::path]` with a unique operation id,
85 response/request types derive `ToSchema`, and `GET /openapi.json`
serves the document unauthenticated (it is a schema, not data).
`springtaled --dump-openapi` prints it before any vault or store exists,
so CI regenerates it and diffs it against the committed copy.
`pnpm build` runs `openapi-typescript` over that copy into
`packages/types/src/api.ts`; 30 wire shapes drop out of the
hand-maintained `dashboard/types.ts`, which shrinks to the provider
contract and view models.
`scripts/check-surface.sh` proves every route has a command-line verb
and a provider method and that nothing under /formations sits outside
the four orchestration verb groups. An empty list is a failure, not a
pass. `springtale --help --json` cannot answer the CLI half — clap emits
no machine-readable help and a verb name does not carry its route — so
the check reads the CLI's own path literals instead.
The CLI's build script emits completions for five shells and a man page,
and every subcommand now honours `--json` through `output::emit`. That
work surfaced a real bug: `springtale safety disguise <bool>` panicked
in any debug build because a positional `bool` derives `SetTrue`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M415coMKAv5V2xJQsaSf8
| permissions: | ||
| contents: read | ||
| steps: | ||
| - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2 |
…e uses The new contract-regeneration job referenced two actions by moving tag, which both workflow scanners reject: unpinned action reference, and mutable action tag. Same digests as every other job in the file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018M415coMKAv5V2xJQsaSf8
| with: | ||
| persist-credentials: false | ||
| - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable | ||
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two plan items in one branch.
Handlers hold no logic (2.4, findings 11, 12, 13). The daemon's handlers held choreography that a second surface would have to repeat. Author handling, chat ingestion, bot status and memory summary, session listing and approval resolution move into runtime operations and are called from the handlers. The heartbeat becomes one operation that persists the interval and applies it to the monitor, with boot reading the stored value. Seven untyped JSON bodies become typed request structs with no silent defaults, and the events limit clamp moves into its operation. The desktop duplicates the plan mentions were already gone with the sidecar work, which was verified rather than assumed.
One generated contract (2.3, findings 6, 7). There was no machine-checkable agreement between the daemon, the command line and the two front ends. Now 138 handlers are annotated and 85 response types carry schemas, the document is served and can also be dumped without booting, and the frontend build generates TypeScript from it. The hand-maintained dashboard types shrink by about 200 lines as thirty wire shapes start coming from the generated schema. The command line emits shell completions and a man page from its build script.
The surface check script is in CI. Two deviations worth stating plainly. The command line has no machine-readable help, so the script reads the route literals out of the command sources rather than asking the binary. And the invariant does not hold yet: on its first run, of 125 routes, 78 had no command-line verb and 57 no provider method. Rather than declaring that green, the known gaps are listed in an exemptions file that works as a ratchet: a new gap fails the build, and so does a stale exemption. Closing those gaps is follow-up work the script now measures.
That first run also caught two routes under formations outside the allowed verb groups and eight duplicate operation identifiers, which is why every path now names its own.
One live bug found and fixed along the way: the safety disguise command took a positional boolean, which made any debug build panic when it was called. A test now asserts the command tree is valid.
Verification: builds and clippy
-D warningsclean across runtime, daemon and command line; 325 tests green;cargo fmtclean; frontend typecheck, lint and build clean; the surface script passes.🤖 Generated with Claude Code
https://claude.ai/code/session_018M415coMKAv5V2xJQsaSf8