jacquardSnapshot

← snapshot

17086 bytes
//! The demo seed: replays the jac-demo arc against the server's own state,
//! plus one extra live blocked gate so a visitor lands with something to do.

use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use jac_core::{Author, Clock as _, Provenance, SystemClock};
use jac_decision::{Decision, DecisionScope, RationaleFamily, Statement};
use jac_rendezvous::{Publication, PublishedSketch, Registry as _};
use jac_repo::RefName;
use serde_json::{Value, json};

use crate::dto::{self, VerdictDto};
use crate::error::ApiError;
use crate::routes::repos::{
    AgentReq, FileReq, FounderReq, InitRepoRequest, InitialCommitReq, create_repo,
};
use crate::routes::{with_repo, with_repo_mut};
use crate::state::{AppState, SharedState};

const LOGIN_RS: &str =
    "pub fn login(user: &str, token: &str) -> Result<Session, AuthError> { /* ... */ }";
const BACKOFF_RS: &str = "pub fn retry(op: impl Fn() -> Outcome) -> Outcome { /* exponential backoff; 401/403 are terminal */ }";
const SESSION_RS: &str =
    "pub struct SessionCache { /* time-based eviction only; see the governing decision */ }";

/// `POST /api/demo` — seed the walkthrough. Naturally run-once: the second
/// call collides on the slug and returns `409`.
/// Eight modules that all reach the retry helper the same way. The bulk
/// rename below rewrites the call in every one of them, which is the shape a
/// grouped diff exists to collapse.
const FANOUT: [(&str, &str); 8] = [
    ("src/net/fetch.rs", "fetch"),
    ("src/net/push.rs", "push"),
    ("src/net/poll.rs", "poll"),
    ("src/net/stream.rs", "stream"),
    ("src/net/upload.rs", "upload"),
    ("src/net/probe.rs", "probe"),
    ("src/net/resolve.rs", "resolve"),
    ("src/net/handshake.rs", "handshake"),
];

/// The module body before and after the rename. One line differs.
fn fanout_body(op: &str, jittered: bool) -> String {
    let call = if jittered {
        "retry_with_jitter"
    } else {
        "retry"
    };
    format!(
        "pub async fn {op}(req: Request) -> Result<Response, NetError> {{\n             let policy = Policy::default();\n             {call}(policy, || transport::{op}(&req)).await\n         }}\n"
    )
}

pub(crate) async fn seed_route(
    State(state): State<SharedState>,
) -> Result<(StatusCode, Json<Value>), ApiError> {
    let body = seed_inner(&state)?;
    Ok((StatusCode::CREATED, Json(body)))
}

/// Seeds the demo arc, for the binary's `--seed-demo` flag.
///
/// # Errors
///
/// Fails if the demo repos already exist or any engine step refuses.
pub fn seed(state: &AppState) -> anyhow::Result<Value> {
    seed_inner(state).map_err(|e| anyhow::anyhow!("{e}"))
}

/// Seeds Meridian Systems and Halcyon Works, the blocked→attested→admitted
/// arc, the rendezvous, and one still-unsettled decision with a parked
/// branch (`feature/session-cache`) whose gate a visitor can walk.
#[expect(
    clippy::too_many_lines,
    reason = "the demo arc is one linear story; scene order is the structure"
)]
fn seed_inner(state: &AppState) -> Result<Value, ApiError> {
    // -- Scene 1-2: Meridian, Ada, loom-bot, and the first human commit.
    let meridian = create_repo(
        state,
        InitRepoRequest {
            name: "Meridian Systems".to_owned(),
            founder: FounderReq {
                display_name: "Ada".to_owned(),
            },
            default_ref: None,
            agents: vec![AgentReq {
                model: "loom-bot".to_owned(),
            }],
            initial_commit: Some(InitialCommitReq {
                message: "auth: initial login flow".to_owned(),
                files: vec![FileReq {
                    path: "src/auth/login.rs".to_owned(),
                    content: LOGIN_RS.to_owned(),
                }],
                provenance: None,
            }),
            founding_decision: None,
        },
    )?;
    let slug = meridian.slug.clone();
    let ada_id = meridian.founder.id.clone();
    let loom_bot_id = meridian
        .agents
        .first()
        .map(|a| a.id.clone())
        .ok_or_else(|| ApiError::internal("seed lost its agent"))?;

    let main = RefName::parse("main")?;
    let feature = RefName::parse("feature/backoff")?;

    // -- Scene 3: loom-bot branches and adds the retry layer (provenance: agent).
    // -- Scene 4: loom-bot proposes the governing decision.
    let (decision_id, decision_hex) = with_repo_mut(state, &slug, |entry| {
        let base = entry
            .repo
            .head(&main)
            .ok_or_else(|| ApiError::internal("main is unbound after seed commit"))?;
        entry.repo.branch(&feature, base);
        let loom_bot = *entry
            .agents
            .keys()
            .next()
            .ok_or_else(|| ApiError::internal("seed lost its agent"))?;
        entry.repo.commit(
            &feature,
            &[
                ("src/auth/login.rs", LOGIN_RS.as_bytes()),
                ("src/auth/backoff.rs", BACKOFF_RS.as_bytes()),
            ],
            Author::Agent(loom_bot),
            Provenance::Agent,
            "auth: retry with exponential backoff",
        )?;
        let at = entry.repo.now();
        let id = entry.repo.propose_decision(Decision {
            title: "auth retry fails closed on 401/403".to_owned(),
            rationale: "On 401 and 403 responses the retry layer fails closed. Exponential \
                        backoff applies to transient network errors only. Cached credentials \
                        are never reused for an unauthorized request."
                .to_owned(),
            families: vec![
                RationaleFamily::Constraints,
                RationaleFamily::ConfidenceRisk,
            ],
            proposed_by: Author::Agent(loom_bot),
            scope: DecisionScope {
                path_prefixes: vec![dto::parse_repo_path("src/auth")?],
            },
            at,
        });
        Ok((id, id.digest().to_hex().as_str().to_owned()))
    })?;

    // -- Scene 5: promotion blocked. The work parks; nobody is punished.
    let blocked = with_repo_mut(state, &slug, |entry| {
        let verdict = entry.repo.promote(&feature, &main)?;
        Ok(VerdictDto::of(&verdict, false, None))
    })?;

    // -- Scene 6: Ada walks the change and answers in her own words.
    with_repo_mut(state, &slug, |entry| {
        let ada = entry
            .humans
            .values()
            .next()
            .cloned()
            .ok_or_else(|| ApiError::internal("seed lost Ada"))?;
        let statement = Statement::new(
            "I traced the 401 path by hand: the retry loop treats it as terminal and \
             surfaces the error instead of replaying stale credentials. The backoff \
             bound is what I'd have picked. I'd revisit if we ever add token refresh \
             inside the retry.",
        )?;
        entry.repo.attest(decision_id, &ada, statement)?;
        Ok(())
    })?;

    // -- Scene 7: the same promotion, admitted.
    let admitted = with_repo_mut(state, &slug, |entry| {
        let verdict = entry.repo.promote(&feature, &main)?;
        let into_head = entry
            .repo
            .head(&main)
            .map(|id| id.digest().to_hex().as_str().to_owned());
        Ok(VerdictDto::of(&verdict, verdict.is_admitted(), into_head))
    })?;

    // -- Scene 8: Meridian publishes sketches — and only sketches.
    let (meridian_org, content_sketch, decision_sketch) = with_repo(state, &slug, |entry| {
        let head = entry
            .repo
            .head(&main)
            .ok_or_else(|| ApiError::internal("main is unbound after admit"))?;
        Ok((
            entry.org_id,
            PublishedSketch::Content(entry.repo.sketch_snapshot(head)?),
            PublishedSketch::Decision(entry.repo.sketch_decision(decision_id)?),
        ))
    })?;
    {
        let mut registry = state
            .registry
            .lock()
            .map_err(|_| ApiError::internal("registry lock poisoned"))?;
        let at = SystemClock.now();
        registry.publish(Publication {
            org: meridian_org,
            sketch: content_sketch,
            at,
        });
        registry.publish(Publication {
            org: meridian_org,
            sketch: decision_sketch,
            at,
        });
    }

    // -- Scene 9: Halcyon, independently, has the same problem.
    let halcyon = create_repo(
        state,
        InitRepoRequest {
            name: "Halcyon Works".to_owned(),
            founder: FounderReq {
                display_name: "Grace".to_owned(),
            },
            default_ref: None,
            agents: vec![AgentReq {
                model: "shuttle-bot".to_owned(),
            }],
            initial_commit: Some(InitialCommitReq {
                message: "net: retry scaffolding".to_owned(),
                files: vec![FileReq {
                    path: "lib/net/retry/mod.rs".to_owned(),
                    content: "pub mod backoff;".to_owned(),
                }],
                provenance: None,
            }),
            founding_decision: None,
        },
    )?;
    let halcyon_slug = halcyon.slug;
    let (halcyon_org, their_sketch) = with_repo_mut(state, &halcyon_slug, |entry| {
        let bot = *entry
            .agents
            .keys()
            .next()
            .ok_or_else(|| ApiError::internal("seed lost Halcyon's agent"))?;
        let at = entry.repo.now();
        let their_decision = entry.repo.propose_decision(Decision {
            title: "retry layer: authentication errors are terminal".to_owned(),
            rationale: "The retry layer fails closed on 401 and 403 responses. Exponential \
                        backoff is for transient network errors only. A request that was \
                        unauthorized is never replayed with cached credentials."
                .to_owned(),
            families: vec![RationaleFamily::Constraints],
            proposed_by: Author::Agent(bot),
            scope: DecisionScope {
                path_prefixes: vec![dto::parse_repo_path("lib/net/retry")?],
            },
            at,
        });
        Ok((
            entry.org_id,
            PublishedSketch::Decision(entry.repo.sketch_decision(their_decision)?),
        ))
    })?;
    let (matches, introduction) = {
        let mut registry = state
            .registry
            .lock()
            .map_err(|_| ApiError::internal("registry lock poisoned"))?;
        let at = SystemClock.now();
        registry.publish(Publication {
            org: halcyon_org,
            sketch: their_sketch,
            at,
        });
        let matches: Vec<Value> = registry
            .find_similar(
                &their_sketch,
                jac_sketch::Similarity::from_permille(150),
                halcyon_org,
            )
            .iter()
            .map(|m| {
                json!({
                    "org": m.org.to_string(),
                    "similarity_permille": m.similarity.permille(),
                    "similarity_percent": m.similarity.to_string(),
                })
            })
            .collect();
        let introduction = registry.broker(halcyon_org, meridian_org, at);
        (
            matches,
            json!({
                "token": introduction.token.to_string(),
                "parties": [
                    introduction.parties.0.to_string(),
                    introduction.parties.1.to_string(),
                ],
            }),
        )
    };

    // -- The live gate: a fresh unsettled decision and a parked branch, so
    // the visitor experiences a block instead of only reading about one.
    let session_branch = RefName::parse("feature/session-cache")?;
    let live_decision_hex = with_repo_mut(state, &slug, |entry| {
        let base = entry
            .repo
            .head(&main)
            .ok_or_else(|| ApiError::internal("main is unbound"))?;
        entry.repo.branch(&session_branch, base);
        let loom_bot = *entry
            .agents
            .keys()
            .next()
            .ok_or_else(|| ApiError::internal("seed lost its agent"))?;
        entry.repo.commit(
            &session_branch,
            &[
                ("src/auth/login.rs", LOGIN_RS.as_bytes()),
                ("src/auth/backoff.rs", BACKOFF_RS.as_bytes()),
                ("src/auth/session.rs", SESSION_RS.as_bytes()),
            ],
            Author::Agent(loom_bot),
            Provenance::Agent,
            "auth: session cache with time-based eviction",
        )?;
        let at = entry.repo.now();
        let id = entry.repo.propose_decision(Decision {
            title: "session cache: eviction is time-based only".to_owned(),
            rationale: "Entries expire on a fixed clock, never on capacity pressure: a \
                        size-based evictor would let one busy tenant silently log out \
                        another. Capacity is handled by refusing new sessions instead."
                .to_owned(),
            families: vec![
                RationaleFamily::Alternatives,
                RationaleFamily::ConfidenceRisk,
            ],
            proposed_by: Author::Agent(loom_bot),
            scope: DecisionScope {
                path_prefixes: vec![dto::parse_repo_path("src/auth")?],
            },
            at,
        });
        Ok(id.digest().to_hex().as_str().to_owned())
    })?;

    // -- A bulk rename. The transport modules land on `main` first, so the
    // branch's diff is eight identical substitutions rather than eight new
    // files. One of the eight also gains a timeout, which a grouped diff must
    // refuse to fold away.
    let jitter_branch = RefName::parse("feature/retry-jitter")?;
    with_repo_mut(state, &slug, |entry| {
        let loom_bot = *entry
            .agents
            .keys()
            .next()
            .ok_or_else(|| ApiError::internal("seed lost its agent"))?;

        // Commits carry the whole tree in this milestone, so main's existing
        // files travel with the new ones.
        let mut base_tree: Vec<(String, String)> = vec![
            ("src/auth/login.rs".to_owned(), LOGIN_RS.to_owned()),
            ("src/auth/backoff.rs".to_owned(), BACKOFF_RS.to_owned()),
        ];
        base_tree.extend(
            FANOUT
                .iter()
                .map(|(path, op)| ((*path).to_owned(), fanout_body(op, false))),
        );
        let base_refs: Vec<(&str, &[u8])> = base_tree
            .iter()
            .map(|(p, c)| (p.as_str(), c.as_bytes()))
            .collect();
        entry.repo.commit(
            &main,
            &base_refs,
            Author::Agent(loom_bot),
            Provenance::Agent,
            "net: transport modules",
        )?;

        let base = entry
            .repo
            .head(&main)
            .ok_or_else(|| ApiError::internal("main is unbound"))?;
        entry.repo.branch(&jitter_branch, base);

        // The rename everywhere, plus one module that also grows a timeout.
        let mut after: Vec<(String, String)> = vec![
            ("src/auth/login.rs".to_owned(), LOGIN_RS.to_owned()),
            ("src/auth/backoff.rs".to_owned(), BACKOFF_RS.to_owned()),
        ];
        after.extend(
            FANOUT
                .iter()
                .map(|(path, op)| ((*path).to_owned(), fanout_body(op, true))),
        );
        if let Some(odd) = after.iter_mut().find(|(p, _)| p == "src/net/upload.rs") {
            odd.1 = odd.1.replace(
                "let policy = Policy::default();",
                "let policy = Policy::default().timeout(Duration::from_secs(90));",
            );
        }
        let after_refs: Vec<(&str, &[u8])> = after
            .iter()
            .map(|(p, c)| (p.as_str(), c.as_bytes()))
            .collect();
        entry.repo.commit(
            &jitter_branch,
            &after_refs,
            Author::Agent(loom_bot),
            Provenance::Agent,
            "net: jitter every retry to avoid a thundering herd",
        )?;
        Ok(())
    })?;

    state.telemetry.emit(jac_telemetry::Envelope::event(
        "jac.demo.seeded",
        SystemClock.now().as_millis(),
    ));

    Ok(json!({
        "repos": [slug, halcyon_slug],
        "meridian": {
            "slug": "meridian-systems",
            "founder": ada_id,
            "agent": loom_bot_id,
            "decision": decision_hex,
        },
        "arc": {
            "first_promotion": blocked,
            "after_attestation": admitted,
        },
        "rendezvous": {
            "matches": matches,
            "introduction": introduction,
        },
        "bulk": {
            "branch": "feature/retry-jitter",
            "note": "eight modules share one substitution; a ninth also changes its policy",
        },
        "live": {
            "branch": "feature/session-cache",
            "decision": live_decision_hex,
            "note": "this decision is unsettled; the branch parks at the gate until a human attests",
        },
    }))
}