The corelib workspace exposes hundreds of public items across packages/foundation, packages/runtime, packages/console, packages/concurrency, and packages/compiler-sdk. Some items (Collections.Array.Len, System.Output.Write) are battle-tested and already part of the prelude; others (Collections.List.Get, System.FS.WriteAllText) ship as shape-only stubs while the runtime catches up. Without a tier classification, downstream consumers cannot tell which APIs are safe to depend on and which may break in the next minor release.
Examples
Platform spec article
Examples
Spec standingStandard
-
Public corelib items classify as Tier 1 (Standard), Tier 2 (Supported), or Tier 3 (Unstable); missing directives default to `supported`.
Context
Decision
Rule Detail Tier vocabulary Tier 1 — standard, Tier 2 —supported, Tier 3 —unstableAuthoring directive /// @tier(<value>)on the declaring doc comment of any public itemAliases Tier1/Standard/standard,Tier2/Supported/supported,Tier3/Unstable/unstable(case-insensitive)Default Public items without a directive resolve to supportedCascade Item-site → parent → package default → workspace default Serialization Lowercase string under camelCase tierfield inApiDocItem; omitted when unresolvedConsequences
- Downstream tooling can badge every documented item by tier without consulting an external manifest.
- Authors get a one-line annotation surface; existing items default to a meaningful tier (
supported) without a sweeping refactor. - The default of
supportedis the safest "no commitment, no claim of unstable" choice. Future tightening to require explicit directives is possible without a compatibility break.
Verification anchors
compiler/crates/beskid_analysis/src/doc/api_tier.rs(resolver + tests)compiler/crates/beskid_analysis/src/doc/api_snapshot.rs(tierfield)compiler/crates/beskid_cli/src/commands/doc.rs::execute(CLI emission)compiler/crates/beskid_tests/src/projects/corelib/layout.rs::checked_in_corelib_tier_metadata_round_trips_through_api_json
-
The aggregate corelib prelude `Prelude.bd` must re-export Tier-1 modules only; Tier 2 / Tier 3 require explicit `use` imports.
Context
Every Beskid project gets the corelib prelude injected by default (see Corelib injection and resolution). Adding a module to the prelude makes it transitively visible to all downstream code. Including Tier 2 / Tier 3 modules in the prelude would expose unstable surfaces by default and violate the tier compatibility table.
Decision
Rule Detail Membership rule compiler/corelib/beskid_corelib/src/Prelude.bdmust re-export only modules whose declarations resolve tostandardEnforcement compiler/crates/beskid_tests/src/projects/corelib/layout.rs::corelib_prelude_only_re_exports_tier1_modulescross-checks everypub mod ...;line inPrelude.bdagainst the resolved per-module tierPromotion path Promoting a Tier 2 module to Tier 1 requires (a) updating the directive to @tier(standard)on the module's package source and (b) adding a row to the hub's## Decisionssection pointing at this ADR or a new follow-up ADRDemotion path Demoting a Tier 1 prelude item requires a normative ADR under adr/and a compatibility alias for at least one minor releaseConsequences
- The prelude stays small and predictable: only a vetted surface ships transitively.
- Tier 2 / Tier 3 modules remain reachable via explicit
use System.FS;(etc.); no expressivity lost. - CI catches accidental prelude growth before merge, keeping the v0.3 → v1.0 compatibility budget intact.
Verification anchors
compiler/corelib/beskid_corelib/src/Prelude.bdcompiler/crates/beskid_tests/src/projects/corelib/layout.rs::corelib_prelude_only_re_exports_tier1_modulescompiler/crates/beskid_tests/src/projects/corelib/compile.rs::checked_in_corelib_prelude_exports_mvp_modules
-
The corelib API-shape tier classification is serialized as a `tier` field on every `ApiDocItem` row; tooling must not consult a parallel JSON / TOML file for tiers.
Context
Tier classification needs to flow from corelib sources to consumers (pckg dashboard, IDE tooling, conformance fixtures). Two paths were considered: (a) emit a sibling
tiers.jsonnext toapi.json, or (b) attach the tier directly to each item row inapi.json. Path (a) creates a second source of truth and risks drift when items rename or move; path (b) keeps the contract in one place but requires aapi.jsonschema bump.Decision
Rule Detail Field placement tier: Option<String>onApiDocItemincompiler/crates/beskid_analysis/src/doc/api_snapshot.rsSerialization camelCase tier; lowercase value; omitted when unresolvedSchema version API_JSON_SCHEMA_VERSIONstays at4; thetierfield is additive and consumers that ignore unknown fields are unaffectedAnti-pattern Tooling must not consult a parallel manifest file ( tiers.json,Tier.toml, …) for tier classificationConsequences
- One source of truth: every tooling consumer reads from
api.jsonand gets tier alongside signature, doc markdown, and module path. - Future schema changes (e.g., adding
tierReasonorsince) can layer onto the same row without splitting the contract. - Authors discover tier drift the moment
beskid docruns locally; there is no second file to forget to update.
Verification anchors
compiler/crates/beskid_analysis/src/doc/api_snapshot.rs::ApiDocItemcompiler/crates/beskid_analysis/src/doc/api_tier.rscompiler/crates/beskid_cli/src/commands/doc.rs::executecompiler/crates/beskid_tests/src/projects/corelib/layout.rs::api_doc_root_advertises_v4_schema_for_tier_metadata
- One source of truth: every tooling consumer reads from
- Contracts and edge cases Normative MUST/SHOULD/MAY rules for corelib API tiering and the edge cases authors and tools must handle deterministically.
- Design model Conceptual model for the corelib three-tier API-shape classification and how it propagates from sources to consumers.
- Examples Canonical Tier 1 / Tier 2 / Tier 3 annotations on `Collections.Array.Len`, `Collections.Map.Count`, and `Collections.List.Get`, plus the resulting `api.json` rows.
- FAQ and troubleshooting Common questions about `@tier(...)` directives, prelude exposure, and how to debug tier drift between sources and `api.json`.
- Flow and algorithm How a `@tier(...)` doc directive becomes a `tier` field on every `api.json` row consumed by IDEs, lints, and the registry.
- Verification and traceability Matrix mapping each API-shape contract to the Rust tests, Beskid test targets, and CLI commands that pin it down.
0 revisions (git unavailable at build; counts may be empty)
No commits recorded for this path.
| Section id | Required | Found |
|---|---|---|
what-this-feature-specifies | yes | yes |
implementation-anchors | yes | yes |
Full tree: run pnpm verify:platform-spec-layout (writes src/generated/platform-spec-layout-report.json).
Example 1: Tier 1 — Collections.Array.Len
Section titled “Example 1: Tier 1 — Collections.Array.Len”Source (compiler/corelib/packages/foundation/src/Collections/Array.bd):
/// Returns array length (element count)./// @tier(standard)/// @par(T) Element type of the slice (length is independent of `T` in v1)./// @arg(values) Slice-like array handle./// @returns Element count from the runtime header.pub i64 Len<T>(T[] values) { return __array_len(values);}Resulting api.json row (truncated):
{ "qualifiedName": "Collections::Array::Len", "kind": "function", "visibility": "public", "tier": "standard", "signature": "fn Len<T>(values: T[]) -> i64"}Example 2: Tier 2 — Collections.Map.Count
Section titled “Example 2: Tier 2 — Collections.Map.Count”Source (compiler/corelib/packages/foundation/src/Collections/Map.bd):
/// Counter for the logical map size./// @tier(supported)/// @arg(map) Map handle (storage still wired through Tier 3 helpers)./// @returns Number of key-value pairs.pub i64 Count<K, V>(Map<K, V> map) { return map.count;}Resulting api.json row:
{ "qualifiedName": "Collections::Map::Count", "kind": "function", "visibility": "public", "tier": "supported", "signature": "fn Count<K, V>(map: Map<K, V>) -> i64"}Example 3: Tier 3 — Collections.List.Get
Section titled “Example 3: Tier 3 — Collections.List.Get”Source (compiler/corelib/packages/foundation/src/Collections/List.bd):
/// Returns the element at `index`. Tier 3 until List storage is wired into the runtime./// @tier(unstable)/// @par(T) Element type stored in the list./// @arg(list) List handle./// @arg(index) Zero-based index./// @returns Element at `index`; behaviour for out-of-range indices is undefined in v1.pub T Get<T>(List<T> list, i64 index) { return list.head;}Resulting api.json row:
{ "qualifiedName": "Collections::List::Get", "kind": "function", "visibility": "public", "tier": "unstable"}Example 4: Cascade inheritance
Section titled “Example 4: Cascade inheritance”When a module declares a tier on its first item, the resolver applies that tier to every member that omits its own directive. Collections.Map’s module-level @tier(supported) covers Count, IsEmpty, Insert, and Remove; only ContainsKey and Get carry an explicit @tier(unstable) override.
Example 5: Omission round-trip
Section titled “Example 5: Omission round-trip”Items that flow through the cascade without a match (rare in corelib because every module annotates itself) emit no tier field at all:
{ "qualifiedName": "ThirdParty::Pkg::Helper", "kind": "function", "visibility": "public"}Consumers MUST treat this as supported so external packages that have not yet adopted tiering interop cleanly with the corelib pipeline.