Skip to main content

Crate tianheng

Crate tianheng 

Source
Expand description

天衡 (Tiānhéng) — reactive architectural governance.

Govern by reaction, not instruction. The balance (天衡, 玉衡): this crate is the imperative shell + facade. It composes the 三儀 (圭表 static, 渾儀 semantic, 漏刻 runtime CI face) into one reaction, and exposes a single declared Constitution plus run — the CLI reaction that turns it into a process exit code (0 clean / 1 enforced violation / 2 constitution-or-scan error).

The functional cores (guibiao, hunyi, louke) do the observation and comparison; this crate owns the side effects (argument parsing, filesystem, stdout/stderr) and the composition. The cores must not depend on this shell — a crate-level invariant Tianheng enforces on itself (tests/self_governance.rs).

One declared source, three projections. An adopter writes one Constitution carrying every dimension’s boundaries; the static and semantic dimensions project as a CI exit code, and 漏刻 projects both as a CI exit code (its probe-coverage audit, composed here) and, in the adopter’s binary, as a runtime event (the prod face, consumed directly via louke). The runtime boundaries declared here are the same objects the adopter hands to louke::install at startup — the single source of truth, two faces.

Modules§

prelude
The public facade for declaring a constitution and running the reaction. The projection, baseline, and scanner internals stay in the dimension crates; consumers go through Constitution / run (and check for the pure static core).

Structs§

AsyncExposureBoundary
An async-exposure boundary: a module’s public API must not declare an async fn. The implicit-existential complement of ImplTraitBoundary: an async fn leaks a compiler-inserted impl Future (and commits the seam’s contract to an async model), so where impl-trait forbids a written -> impl Future, this forbids the async fn sugar (observed from syn::Signature.asyncness). Governs public free fns, public inherent methods, and public trait method declarations; trait-impl methods (asyncness dictated by the trait) and private items are excluded. Declarative intent by anchor scoping — “this declared seam is synchronous” (a sync-core/async-edges layering), not a blanket “no async”.
Baseline
A recorded set of accepted violations — a generated observation snapshot, not policy. The gate fails only on violations absent from it.
BaselineEntry
One recorded baseline entry: an accepted violation’s identity plus optional governance-tracking metadata. owner (who owns this accepted debt) and tracker (the external issue tracking its fix) describe how the accepted violation is tracked after acceptance, not the basis of the law — so they are metadata only, never part of the ViolationId match key. There is deliberately no anchor here: the governance anchor already rides the live boundary→violation (Violation::anchor), so a baseline copy would duplicate and drift from it.
Constitution
A declared constitution composing every observation dimension’s boundaries — the single source of truth, in Rust. The static (圭表) boundaries, the semantic (渾儀) bundle, and the runtime (漏刻) boundaries hang off one builder, so adding a dimension is a field, never a new run argument.
CrateBoundary
A boundary attached to one crate target, with a human-readable reason.
CrateTarget
A crate identified by its package name.
DynTraitBoundary
A dyn-trait boundary: a module’s public API must not expose trait-object (dyn) syntax. The type-shape complement of SemanticBoundary (signature-coupling): where that forbids an exposed named type, this forbids an exposed type shape — a dyn node at any depth in the governed public surface. Internal dyn is never a violation — this governs exposure across the declared seam, not internal dynamic dispatch, so it is intent (by anchor scoping), not a lint. Declared in Rust and composed with the other dimensions at the gate.
ForbiddenMarkerBoundary
A forbidden-marker boundary: types defined in a module subtree must not acquire a forbidden trait — by #[derive(T)] or a hand-written impl T for <a subtree type>. Declared in Rust and composed at the gate. The complement to exposure, impl-locality, and visibility; it delivers the “this layer is not T-able” intent.
GnomonConstitution
The governed shape, declared in Rust (the single source of truth).
ImplTraitBoundary
An impl-trait boundary: a module’s public API must not return a written impl Trait (return-position impl Trait / RPIT). The existential complement of DynTraitBoundary: where that forbids the dynamic-dispatch shape (dyn), this forbids the existential shape — an unnameable type the caller cannot name, store without boxing, or rely on beyond its declared bounds. Governs return positions only: argument-position impl Trait (APIT) is universal (a caller-chosen generic), not an existential leak, and is never governed; async fn’s implicit impl Future is a distinct compiler-inserted existential, out of scope. Declared in Rust and composed with the other dimensions at the gate.
ModuleBoundary
A boundary over the intra-crate module import graph — the layering Cargo cannot see. Observed from the target crate’s source use declarations (PROJECT.md).
OriginEntry
An origin registration produced by register_origin! — a TypeId, the observed origin (module_path!() at the registration site), and the type’s name (for findings). Pass these to install.
Report
All violations from one evaluation.
RuntimeBoundary
A runtime boundary: only the declared origins may cross the named seam. Declared in Rust (the single source of truth) and installed once at startup; a probe references the seam by name, so the policy lives in this declaration, not at the call site.
SansIoPure
A sans-I/O purity profile: the two source-observable axes of a pure kernel composed into one declaration — the core reads no ambient clock, and its public API is synchronous. Passed to Constitution::sans_io_pure, it expands into a 圭表 must_not_call_inline boundary and a 渾儀 must_not_expose_async_fn boundary on the module. Both reach the module’s subtree (the clock rule is inherently subtree-wide over the filesystem; the async rule opts in via including_submodules, descending the declared mod-tree), so a pure kernel is sans-I/O throughout, not only at its own seam — though a #[path]-remapped module is the semantic dimension’s stated bound (the async half does not descend one).
SemanticBoundaries
The 渾儀 (semantic) dimension’s boundaries, gathered so the shell takes the dimension as one unit rather than one parameter per capability. Each field is one capability’s boundaries; check_all evaluates them all with a single cargo metadata read.
SemanticBoundary
A semantic boundary: the public API of a module must not expose any forbidden type. Declared in Rust (the single source of truth), alongside — and composed with — the static constitution at the gate. Each dimension owns its own declaration DSL and expresses findings in the shared 璇璣 model; the shell merges them into one reaction.
TraitImplBoundary
A trait-impl-locality boundary: within a target crate, the named trait may be implemented only inside the declared allowed module location(s). An impl <Trait> for <Type> block outside them is a violation. Declared in Rust (the single source of truth) and composed with the other dimensions at the gate. This governs impl locality — the complement of exposure (SemanticBoundary) and of the static import boundary. It governs only the target crate’s own impl sites; it makes no claim about downstream crates (that would be external trait sealing, an essential gap).
UnsafeBoundary
An unsafe-confinement boundary: within a target crate, unsafe (blocks, unsafe fn/impl/ trait, unsafe extern) may appear only under the declared subtree(s); a site outside all of them reacts. Declared in Rust (the single source of truth), composed at the gate.
Violation
One violated boundary. severity is the producing boundary’s severity, so the exit-code decision and the report can treat enforce and warn findings apart. baselined is set when baseline gating records the violation in a baseline; a baselined violation does not fail the reaction.
ViolationId
A violation’s identity for baseline matching: (target, rule, finding). Reason and severity are excluded so editing them does not turn a known violation new.
VisibilityBoundary
A visibility boundary: a governed module must not declare any bare-pub items — a declared-visibility hygiene rule for an internal / impl-detail layer. The rule is syntactic (the pub keyword on the module’s own direct items), not crate- reachability: pub(crate)/pub(super)/pub(in …)/private are allowed, and attribute- derived public surface (#[macro_export], #[no_mangle]) is out of scope (the deferred attribute capability’s domain). Declared in Rust and composed with the other dimensions at the gate.

Enums§

Boundary
One boundary, of either kind. Named Boundary (umbrella) with the crate kind as CrateBoundary, since a module reaction is also a boundary (drift law D2).
BoundaryKind
Which kind of boundary produced a violation — surfaced in the JSON report so a consumer need not reverse-engineer the rule string. Not part of the baseline identity (ViolationId), so adding it does not invalidate existing baselines. #[non_exhaustive]: a further dimension (e.g. runtime) adds its own kind without breaking consumers.
DependencyKind
Which dependency table a crate rule observes. Defaults to Normal. Mirrors cargo’s fixed set (normal / dev / build), so it is intentionally not #[non_exhaustive] — unlike Rule, this enum will not grow.
ModuleRule
What a module boundary forbids.
Outcome
The reaction’s outcome. Exit codes separate architectural drift (1) from a misconfiguration (2), so a mistyped target is not reported as drift.
Polarity
The repair direction a boundary-drift violation points to — a different axis from BoundaryKind, which names which dimension saw it. Derived from the producing rule’s type (known at the reaction site), never observed from code and never declared by the adopter. #[non_exhaustive]: the axis is deliberately two-valued, but a future rung stays additive.
Posture
How a violated boundary reacts in production. Event (the default) emits a structured Violation; Panic additionally aborts — opt-in only, never the default.
Rule
What a crate boundary forbids. Each variant is a reaction with an observation source in cargo metadata; no variant is named for a reaction that does not exist.
Severity
How strongly a boundary reacts. Enforce fails the reaction (exit 1); Warn reports the violation as advisory without failing — the first rung of adoption, so a dirty project can observe a boundary before enforcing it.
SourceKind
A dependency’s declared source kind, classified from cargo metadata’s source field. The vocabulary of the Rule::RestrictDependencySourcesTo allowlist. Like DependencyKind, it mirrors a fixed cargo distinction (a declared source is a registry, a git, or a path), so it is intentionally not #[non_exhaustive]: it will not grow.
VisibilityCeiling
The maximum declared visibility a governed module’s direct items may carry. An item whose declared-visibility rank is strictly above the ceiling reacts; at or below it passes. (Public is deliberately not a ceiling — it would never react.)

Traits§

Tracked
The supertrait a governed trait carries so a probe can recover the concrete type behind a dyn Trait without trait upcasting (rust 1.85). Write trait DomainPort: louke::Tracked; the blanket impl supplies as_any for every 'static type, so no per-type boilerplate is needed. A &dyn Trait over borrowed (non-'static) data cannot be probed — Any requires 'static (a stated bound).

Functions§

audit_probe_coverage
CI face. Audit probe coverage against the declared RuntimeBoundary objects (the authoritative seam set — the constitution, not a source scan for declarations) by scanning the workspace’s src_dirs for assert_boundary! probes. Reacts, with the static dimensions’ exit-code contract, in both directions plus an un-auditable case:
check
Run the constitution’s boundaries against the Cargo workspace at manifest_path.
check_semantic
Run the semantic boundaries against the Cargo workspace at manifest_path.
constitution_markdown
Render a constitution as the human- and agent-readable Markdown summary of its declared law — the same projection list --format markdown prints, returned as a String for library callers (e.g. to generate an agent-context artifact). It composes the same internal projector, so it carries no less than the JSON and never reacts; it adds nothing of its own (no preamble, no trailing newline), so it equals the CLI output byte for byte.
projection_gate
Gate a checked-in projection against the live one — the 潛移 staleness reaction, reusable by adopters. Pair it with super::constitution_markdown: generate a Markdown projection of your constitution into a checked-in file (so your agents read the reacted law in-context, the projection track), keep a short hand-written spine (AGENTS.md-style, the prose track), and gate the projection’s freshness in a cargo test so the file can never drift from the declared law — the same mechanism Tianheng runs on its own AGENTS.self-law.md.
run
Run the unified constitution’s boundaries against a Cargo workspace and return the process exit code. The one Constitution carries every dimension — static (圭表), semantic (渾儀), and the runtime (漏刻) CI probe-coverage audit — which this gate composes into one reaction. A dimension with no declared boundaries contributes nothing. args are the full process arguments (the program name is skipped internally, like a real main). Pass std::env::args() from a binary.
workspace_member_src_dirs
Resolve every workspace member’s source-root directory from the target workspace at manifest_path, so a caller (the 天衡 shell, composing the 漏刻 runtime CI audit) can hand resolved &Paths to a dimension that must stay std-only and never read cargo metadata itself. Each root is the parent of the member’s lib (else proc-macro, else bin) target src_path — the same resolution the semantic dimension uses (both through 星表), not the manifest_dir/src shortcut (which would silently miss a custom layout). An unreadable workspace is a constitution error, never a silent empty set.