Deploy / deploy (push) Failing after 3s
Replaces the free-string, direct-KV-mutate state model in answers.rs
with a proper event log (events/store.rs, JetStream-backed, CAS via
expected_last_subject_sequence) and three pure state machines
(aggregates/{applicant,subscriber,project}.rs). Closes a real
lost-update race in the old transition_answer (concurrent decisions on
the same item could both win, publishing contradictory events). KV
buckets become best-effort read-model projections, not the source of
truth. Content-declared transition targets are now validated at
load/reload time against the real compiled transition tables, not
accepted as arbitrary strings.
Buckets renamed to describe their content, not their relation to the
app (portal_applicants -> applicants, etc); "inquiry" folded into a
richer "project" concept.
ResourceSpec generalized beyond a single KV bucket: Kv | GiteaStarred |
GiteaOrgRepos | Url sources, with an optional jq filter (via the jaq
crate) to shape live data for the frontend. Url source is SSRF-guarded
(https-only, rejects loopback/private/link-local, real DNS resolve).
New headless question_lint binary (validates content against compiled
transition tables with no NATS/OIDC/server involved) and a one-time
backfill_events binary (dry-run by default) for migrating existing KV
data onto the new event log.
Questions get an optional `responsible` contact plus a lightweight
"report this question" action.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
44 lines
1.3 KiB
Rust
44 lines
1.3 KiB
Rust
//! Project submissions (`record_as: projects` - absorbs what used to be
|
|
//! called "inquiries"). A visitor submits a project idea plus who it's
|
|
//! for (org/contact details, captured as plain payload fields - no
|
|
//! separate Organization aggregate yet, see the event-sourcing plan).
|
|
//! `Open -> Accepted | Declined` - a real b2b-pipeline pair of terminal
|
|
//! states rather than the old generic `Handled`.
|
|
use super::AggregateKind;
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
pub enum State {
|
|
Open,
|
|
Accepted,
|
|
Declined,
|
|
}
|
|
|
|
impl AggregateKind for State {
|
|
const AGGREGATE_TYPE: &'static str = "project";
|
|
const INITIAL_STATE: Self = State::Open;
|
|
|
|
fn from_event_type(event_type: &str) -> Option<Self> {
|
|
match event_type {
|
|
"submitted" => Some(State::Open),
|
|
"accepted" => Some(State::Accepted),
|
|
"declined" => Some(State::Declined),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn event_type(self) -> &'static str {
|
|
match self {
|
|
State::Open => "submitted",
|
|
State::Accepted => "accepted",
|
|
State::Declined => "declined",
|
|
}
|
|
}
|
|
|
|
fn allowed(self) -> &'static [Self] {
|
|
match self {
|
|
State::Open => &[State::Accepted, State::Declined],
|
|
State::Accepted | State::Declined => &[],
|
|
}
|
|
}
|
|
}
|