Skip to main content

gen_types/
ecosystem.rs

1//! Cross-ecosystem trait surface.
2//!
3//! Three traits every package-manager adapter implements to fit the
4//! pleme-io substrate's universal intake pattern. The contract is
5//! documented in `theory/ECOSYSTEM-INTAKE.md`; this module IS the
6//! Rust binding of that contract.
7//!
8//! Stack:
9//! - [`Spec`] — the typed shape of an adapter's emitted build spec
10//! - [`QuirkRegistry`] — the typed shape of known upstream-bug quirks
11//! - [`Invariants`] — the typed shape of well-formedness checks
12//!
13//! Every concrete adapter (gen-cargo today; gen-npm + gen-bundler +
14//! gen-pip + gen-helm + gen-gomod as they ship) implements all three
15//! on its own typed shapes. Substrate / cse-lint / gen confirm
16//! consume these traits without caring which ecosystem produced
17//! them.
18
19use serde::{de::DeserializeOwned, Serialize};
20
21/// The typed shape an adapter's build spec exposes to substrate +
22/// tooling consumers. Every adapter's `BuildSpec` struct implements
23/// this; the trait method surface is universal.
24///
25/// Why the associated types: each ecosystem carries its own
26/// `Args` shape (BuildRustCrateArgs for Cargo, NpmInstallArgs for
27/// npm, …) and its own `Quirk` enum. The trait doesn't constrain
28/// the shapes — substrate dispatches on the serialized JSON. What
29/// the trait DOES constrain is the universal accessor surface
30/// (schema_version / root_key / member_keys / args_for /
31/// quirks_for) so substrate's Nix dispatch is written once.
32pub trait Spec: Serialize + DeserializeOwned {
33    /// Per-package buildRustCrate-equivalent args.
34    type Args: Serialize + DeserializeOwned;
35    /// Per-package quirks variant set (typed by the adapter).
36    type Quirk: Serialize + DeserializeOwned;
37
38    /// Spec schema version. Substrate's lockfile-builder asserts
39    /// this >= a known floor; cse-lint flags stale specs.
40    fn schema_version(&self) -> u32;
41
42    /// The workspace's primary buildable package key.
43    fn root_key(&self) -> &str;
44
45    /// Every package the workspace tracks (root + members).
46    fn member_keys(&self) -> Vec<&str>;
47
48    /// Pre-shaped build args for one package, or None if the package
49    /// isn't in the spec.
50    fn args_for(&self, key: &str) -> Option<&Self::Args>;
51
52    /// Quirks registered for one package — empty when no quirks
53    /// apply, never None.
54    fn quirks_for(&self, key: &str) -> &[Self::Quirk];
55}
56
57/// The typed shape of an adapter's quirk registry. The registry is
58/// the canonical knowledge of "which upstream packages need which
59/// build-time workarounds." Each adapter ships its own typed quirk
60/// enum + a const REGISTRY table.
61///
62/// Implementations should be implementable from a TOML side-table
63/// (declarative) or a Rust constructor function — both are valid.
64/// The `QuirkRegistry` derive macro auto-implements from TOML.
65pub trait QuirkRegistry {
66    /// The ecosystem's typed quirk variant set.
67    type Quirk: Serialize + DeserializeOwned + Clone;
68
69    /// Full registry — every (package_name, quirks) pair the
70    /// adapter knows about. Sorted by package name for stable
71    /// diffs.
72    fn registry() -> Vec<(&'static str, Vec<Self::Quirk>)>;
73
74    /// Lookup quirks for a single package by name. Returns empty
75    /// Vec when no quirks registered (never None).
76    fn for_package(name: &str) -> Vec<Self::Quirk> {
77        for (k, v) in Self::registry() {
78            if k == name {
79                return v;
80            }
81        }
82        Vec::new()
83    }
84
85    /// Every package name with at least one registered quirk. Used
86    /// by invariants to detect drift between the registry and the
87    /// spec's emission.
88    fn registered_names() -> Vec<&'static str> {
89        Self::registry().into_iter().map(|(k, _)| k).collect()
90    }
91}
92
93/// The typed shape of an adapter's invariants pass. Every adapter
94/// ships a `check(spec) -> Vec<Violation>` function that verifies
95/// the spec is well-formed against its own typed rules.
96///
97/// `gen confirm` invokes this per-adapter. cse-lint can invoke it
98/// fleet-wide.
99pub trait Invariants {
100    /// The adapter's spec type — pinned to the same `Spec` impl as
101    /// the adapter's `build()` returns.
102    type Spec: Spec;
103    /// The adapter's typed violation enum. Always serializable so
104    /// `gen confirm` / cse-lint / IDE tooling can consume the
105    /// payload uniformly.
106    type Violation: Serialize + DeserializeOwned + Clone;
107
108    /// Run every invariant against the spec. Returns the violation
109    /// list (empty when the spec is valid). Pure function, no I/O,
110    /// deterministic output.
111    fn check(spec: &Self::Spec) -> Vec<Self::Violation>;
112}