Semantic-Model Packs
Semantic-model packs are Bifrost’s versioned interchange format for API facts that do not come from workspace source, declarative facts emitted by framework or generator behavior, and reviewed external procedure behavior. A producer can construct the public Rust model directly or load reviewed YAML or JSON. Both paths compile through the same validation and canonicalization pipeline.
Bifrost-packs is the home for open semantic packs and policy rules, including authoring contributions, generation recipes, and independently versioned releases. The engine retains compilation, installation, and activation tooling.
Compilation alone does not install, store, match, or activate a pack; those operations belong to the catalog and generation-scoped runtime described below.
This page explains how packs work and how to use them. The
Semantic Pack Reference lists every pack field,
every .bifrost/packs.json field, the shipped packs, and every pack command.
Current runtime boundary: Bifrost can compile, defensively decode, install, strictly activate, and match semantic-model packs for one analyzer generation. A successful runtime acquisition also publishes an immutable declaration overlay for normal navigation and query result surfaces. Procedure-summary payloads can contribute exact external-call transfers to value-flow and taint when an embedding supplies the catalog and activation request to the production policy runtime. Workspace source bodies take precedence; missing, conflicting, incompatible, or incomplete summaries stay visible in the analysis outcome.
Together, the catalog and runtime can install, select, activate, account for, quarantine, and garbage-collect packs while keeping matching generation-local.
Packs do not contain executable code, arbitrary templates, fake source, or unbounded evaluator inputs. Generator expressions are bounded trees of literals, declared captures, ordered concatenation, named ASCII case transforms, and conditionals that compare two expressions for equality or a prefix. Procedure summaries are bounded typed records, not executable models or source-text matching rules.
Native format compatibility
Section titled “Native format compatibility”New native packs use schema 9. Their compatibility object contains
toolchains and omits bifrost; reader support for the native schema determines
format admission. An engine-version field in a schema-8 or schema-9 pack is
rejected. Producer versions still identify the generator and remain part of
provenance.
Historical native schemas 2 through 7 retain their required, valid
compatibility.bifrost range and enforce it during selection and activation.
Missing, malformed, null or mixed compatibility forms are rejected. Existing
pack bytes and content digests are preserved when the catalog is upgraded.
For every schema, toolchain requirements, artifact identity, selectors and integrity checks continue to apply. Reading the format does not establish complete model coverage or support for every semantic capability; partial results retain their completeness diagnostics.
Workspace pack activation
Section titled “Workspace pack activation”CLI policy runs and every SearchToolsService session (the MCP server,
one-shot --tool and --query-file runs, and the Rust and Python libraries)
share one workspace activation contract: .bifrost/packs.json. The document is optional. When present, it
must use schema version 1 and must list ecosystems:
{ "schema_version": 1, "catalog": ".bifrost/semantic-pack-catalog", "ecosystems": ["jvm", "python"], "enable": ["org.example.jdk-21"]}ecosystems names the dependency ecosystems to discover. It is required. An
empty array explicitly disables ambient dependency-pack activation. An unknown
or duplicate label is an error. When the document is absent, the shared host
path selects every ecosystem that serves a language present in the workspace.
An explicit enable entry can satisfy a pack’s review_required gate; it
cannot bypass compatibility checks. Activating a pack is the decision to use
its models: no separate review receipt exists.
A retained source-to-sink path may establish a definite finding when every
summary it relies on comes from an active pack, is complete, and binds exactly
to the call’s target and arguments. A partial, misbound or conflicting summary
cannot. The finding’s proof reasons retain the authored model dependencies
(model identity, content digest and contract version), so a reader can see
that the result rests on a model. Raw authored transfer proof stays authored,
and a conclusive run relying on those summaries reports proven_by_summary.
Genuine gaps and truncated evidence remain separate limits.
The
workspace pack document reference lists every
field, the nine ecosystem labels, the size limits, and the optional
python_runtime_environments bindings.
catalog is a workspace-relative catalog directory. Omitting it stores locally
generated packs in the repository’s versioned .bifrost/cache catalog, shared
with linked worktrees and relocated by the normal Bifrost cache environment
overrides. An analyzer explicitly built with ephemeral storage instead uses a
delete-on-drop catalog, so a scoped or test session does not create .bifrost
inside the workspace. Supplying catalog or a cache environment override is an
explicit opt-in to persistent reuse. Default persistent catalogs also reuse
exact generated productions through the user’s machine cache. Only the requested
generated pack and its extraction accounting are copied into a workspace’s
catalog; workspace models and activation settings remain local. Host reuse
refreshes retention, and publishing a new production runs bounded cleanup of
generated packs unused for at least seven days. Active transfers hold leases.
If the optional host cache cannot accept a publication, the verified local copy
remains usable and a later hit retries publication. Set
BIFROST_SEMANTIC_PACK_CACHE_ROOT to choose an explicitly shared catalog instead.
A cold cache still generates each pack from the exact local artifact; later
sessions reuse that verified production. Shard validation is cached by exact
content, validator version and decode limits; every load still checks content
integrity. On Unix, unchanged JDK source ZIPs also avoid rereading the archive
using file identity and change timestamps. Other platforms retain exact reads.
The analysis catalog and activation APIs do not download packs or dependencies.
Hosts acquire compatible open content into their persistent cache; see
Cached and offline installation.
Java rules that use reviewed JDK return-value obligations also require an
explicit source-tree binding in .bifrost/jvm-toolchains.json:
{ "schema_version": 1, "source_toolchains": [ {"source_root": ".", "jdk_home": "/opt/jdk-21"}, {"source_root": "preview-module", "jdk_home": "/opt/jdk-22"} ]}Source roots are workspace-relative; . binds the entire workspace. JDK homes
may be absolute or relative to the workspace. The deepest matching source root
wins. Equally specific bindings to different homes remain ambiguous. Every
host reads these bindings when it builds the workspace, and an invalid document
makes the build fail. Restart a running host after changing them. Missing or
ambiguous bindings leave reviewed result obligations incomplete. The
reference lists the fields and limits. Discovering a JDK through JAVA_HOME alone
does not select it for these rules.
The selected installation must provide supported declaration artifacts such as
lib/src.zip. Bifrost joins the exact artifact used to resolve the call with
the reviewed model’s compatible activation evidence. The initial JDK 21 pack
covers String.trim() as a pure transformation and
Files.deleteIfExists(Path) as a status result. For the latter, false means
the file did not exist; I/O exceptions are separate. A discarded-status note
does not establish that the caller intended to handle that status.
The same transaction mints workspace evidence the reviewed runtime packs need.
It mints one intrinsic language row per present language. The row’s ecosystem
is fixed per language, for example maven for Java, Scala, and Kotlin, and
pypi for Python; the reference lists all of them. A workspace that contains
Python also gets one row that carries the reviewed Python process-input
profile digest as its configuration. That row activates
bifrost.python-process-inputs (os.environ and sys.argv) without a version
pin, because the Python standard library defines these inputs for every
conforming implementation.
A workspace whose root package.json (engines.node or volta.node),
.nvmrc, or .node-version pins the reviewed Node revision 22.11.0 gets one
Node artifact row per present JavaScript/TypeScript language. The row carries
the reviewed distribution-archive digest and profile configuration that the
shipped runtime-values packs require. A workspace that pins another revision,
or pins nothing, mints no such row; the pack stays incompatible and the activation report’s reason names the
artifact coordinate and the reviewed predicates it requires. Only a declaration
of the exact reviewed revision counts — a range such as >=22 or a moving
label such as lts/* names no reviewed artifact.
Cached and offline installation
Section titled “Cached and offline installation”Open content is published by
Bifrost-packs in separate
packs/vX.Y.Z and rules/vX.Y.Z streams. Engine vX.Y.Z releases retain
installer tooling but no longer generate or publish the semantic-pack bundle.
Pack versions advance independently of the engine version.
Each release’s pack-release.json records its immutable source commit,
required schemas and semantic capabilities, integrity and behavior evidence,
and artifact sizes and SHA-256 hashes. Manifest schema 2 checks the consumer’s
supported RQL, policy, catalog, and native-model versions; engine versions are
provenance and do not gate compatibility. Rules may pin an exact semantic-pack
dependency; resolve it rather than selecting an unrelated latest bundle.
Default selection requires verified integrity and behavior that has not failed.
Pending or limited behavior remains visible in the receipt and does not establish
complete coverage. Historical schema 1 releases retain their original engine
bounds and qualification requirements.
The bifrost executable reads its engine profile in-process and performs
release discovery only for commands that activate policy or MCP packs. It
enumerates both release streams, validates the exact rules-to-packs pin, and
selects the newest complete eligible pair. bifrost pack-engine-profile
continues to print this build’s profile as JSON for inspection; the
reference lists its fields. The selector verifies
the cached manifest inventory on every use, refreshes it after one hour, and
hash-checks cached or downloaded artifacts before safe extraction.
Set BIFROST_OPEN_PACKS_OFFLINE=1 to use only the verified cache. Set
BIFROST_OPEN_PACKS_REFRESH=1 to ignore the cached manifest inventory and
discover current releases; these controls cannot be combined.
BIFROST_LAUNCHER_AUTO_INSTALL=0 also disables pack downloads. If no
eligible pair exists, or network access is unavailable and the cache is empty,
the binary reports a diagnostic and continues with embedded packs. Invalid
profiles, release metadata, and corrupt cached content fail explicitly.
For manual installation, download, verify, and safely extract a native bundle, then install it with the running engine’s portable command:
bifrost install-semantic-packs /path/to/bifrost-semantic-packs /path/to/cacheThe command checks the native index, manifests, shards, and generation identity, and installs into the engine’s versioned catalog under the cache root. Hosts can instead import the extracted bundle through the facade bootstrap:
export BIFROST_SEMANTIC_PACK_CACHE_ROOT=/path/to/cacheexport BIFROST_OPEN_SEMANTIC_PACK_BUNDLE=/path/to/bifrost-semantic-packsexport BIFROST_POLICY_PACK_ROOT=/path/to/extracted-rules/rulesbifrost --root /path/to/project --mcp 'symbol|extended'When both BIFROST_OPEN_SEMANTIC_PACK_BUNDLE and
BIFROST_POLICY_PACK_ROOT are unset, the bifrost binary supplies its
verified selection before the existing native and policy installers run.
Setting either variable supplies host-owned content and disables automatic
acquisition. The native bundle replaces embedded semantic registration; it is
not merged with it. An empty value, malformed bundle, or bundle that installs
no compatible pack makes activation fail. A policy report then carries
pack-activation-failed, and the run exits 2. A malformed policy catalog also
fails explicitly. Policy manifests and authored hashes are checked by the
same catalog loader used for embedded policies. Runtime activation still
requires the exact modeled dependency artifacts and workspace evidence;
installation alone does not establish applicability or complete coverage.
Use .bifrost/packs.json to control ecosystems and reviewed packs, as described
above. Empty ecosystems disables dependency discovery. Generated-production
reuse remains keyed to the exact artifact digest and producer identity; a
Linux production cannot be reused for a different toolchain or operating-system
artifact merely because its pack ID matches.
Generated-pack acquisition
Section titled “Generated-pack acquisition”Open content is acquired by the bifrost executable before runtime pack
activation, using the optional download feature in
brokk-bifrost-semantic-packs. Analysis, generic catalog activation, and local
dependency generation remain network-free. Local generation remains available
for exact dependency artifacts missing from the catalog.
Policy reports expose whether dependency activation was default,
configured, or disabled, together with the selected, missing,
incompatible, version-mismatched, rejected, or otherwise incomplete decisions.
Activation failures and incomplete runtime state remain typed and attributable;
they do not become a clean negative.
Every language whose discovery ran reports the standard-library pack it
expects, even when nothing selects it: bifrost.python-stdlib,
bifrost.php-builtin, bifrost.rust-stdlib, and bifrost.typescript-stdlib
(which also explains that no pack declares a JavaScript standard library). The
reason begins with the language. A workspace that declares no toolchain
version gets a rejected decision naming the files Bifrost read. A declared
toolchain with no installed pack gets a missing decision naming the
Bifrost-packs install route. Each Node built-in module that a JavaScript or
TypeScript file imports and no active pack declares, such as fs, gets a
missing decision named node:<module>. Online and offline runs record the
same decisions, because none of them involves the network.
Authoring and review tools
Section titled “Authoring and review tools”The bifrost-semantic-pack binary uses the production source parser, schema,
compiler, catalog, and activation resolver. Build it with the
release-tooling feature:
cargo run -p brokk-bifrost-semantic-packs --features release-tooling \ --bin bifrost-semantic-pack -- validate model.yaml
cargo run -p brokk-bifrost-semantic-packs --features release-tooling \ --bin bifrost-semantic-pack -- lint model.yaml --format json
cargo run -p brokk-bifrost-semantic-packs --features release-tooling \ --bin bifrost-semantic-pack -- compile model.yaml compiled-modelvalidate checks the exact production schema and compiler. lint also reports
duplicate IDs, unreachable or shadowed rules, ambiguous selectors, unused
captures, broad wildcard selectors, and conflicting emissions. Some duplicate
IDs and unknown capture references are compiler errors. The lint report keeps
those production diagnostics.
compile writes the canonical manifest.json and exact shard bytes. A second
run accepts identical files. It rejects an existing file with different bytes.
This behavior prevents an authoring command from replacing reviewed output.
csmi-check FILE validates a CSMI semantic document or pack manifest and
reports structural, semantic, integrity, and per-profile results. The binary
also has release commands (generate, merge, verify, install) and
summary-conversion commands. The
command reference lists every command, its
arguments, its JSON report format, and its exit status.
Human output is the default. --format json returns a stable versioned report.
Status 0 means that the result is complete and has no finding. Status 1 means
that source or conformance findings exist. Status 2 means that arguments or
inputs are incompatible, or that a bounded operation is incomplete.
The smallest useful generator rule has one exact structured trigger, one required capture, and one typed emission:
payload: kind: generator_rules rules: - id: rule.builder trigger: kind: annotation name: com.acme.GenerateBuilder captures: - name: owner_id binding: source: kind: enclosing_declaration projection: stable_id value_kind: stable_id cardinality: one emissions: - kind: declaration id: op: concat values: - op: capture name: owner_id - op: literal value: .builder name: op: literal value: builder declaration: kind: member owner: op: capture name: owner_id member_kind: method visibility: publicThe complete declaration example below shows a dependency-qualified rule. Its package, version, target, configuration, and toolchain constraints must match one complete activation-evidence row.
Catalog inventory and activation evidence
Section titled “Catalog inventory and activation evidence”list CATALOG shows installed packs, source attribution, and persisted catalog
activation references. Add an activation JSON file to resolve active packs and
show matched evidence, provenance, shadowing, incompatibility, and reasons:
{ "schema_version": 1, "bifrost_version": "0.8.21", "evidence": [ { "language": "java", "ecosystem": "maven", "package": { "name": "com.acme:widget", "version": "1.5.0" }, "toolchain": { "name": "jdk", "version": "17.0.1" }, "target": "jvm", "configuration": "release" } ]}bifrost-semantic-pack list /path/to/catalog activation.json --format jsonThe activation file can contain controls. Each control has scope (user
or workspace), action (enable or disable), pack_id, and optional
version or manifest_digest fields. A control cannot bypass compatibility.
Workspace rules and trust
Section titled “Workspace rules and trust”A repository can opt in to direct YAML or JSON files at this path:
.bifrost/semantic-models/Run bifrost-semantic-pack workspace-check WORKSPACE during review. Discovery
does not recurse. It reads files with the extension .json, .yaml, or
.yml and ignores other files without a diagnostic. It rejects symbolic
links, non-files, and files outside the canonical workspace. It allows at most
1,024 directory entries (limit.workspace_files) and 64 MiB per file
(limit.source_bytes). Each result contains the exact source SHA-256 and
compiled semantic digest.
Discovery is not ambient activation. A host calls
register_workspace_semantic_models, which discovers each file, compiles it,
and registers it as an EphemeralWorkspace session pack. The host then puts
the returned evidence into its activation request and supplies an explicit
workspace enable control for any pack that requires review. The production
runtime then applies normal source precedence. Workspace sources outrank
installed and shipped sources. Exact authored source or artifact declarations
still outrank model-only overlay facts.
workspace_semantic_models_not_active proves what actually activated, so a
registered model can never go missing without the host noticing.
These hosts run the reviewed workspace route:
| Host | When it runs | On failure |
|---|---|---|
A CLI policy run without --sources: bifrost --policy, --policy-file, and bifrost scan | every policy run, including the diff base, when the directory exists | a workspace-model-load-failed report diagnostic and exit 2 |
A SearchToolsService session: the MCP server, one-shot --tool and --query-file runs, a policy run with --sources, and the Rust and Python libraries | when the session activates packs, with BIFROST_WORKSPACE_SEMANTIC_MODELS=on (also 1 or enabled) | the session records a failed activation; a policy run in that session reports pack-activation-failed and exits 2 |
BIFROST_WORKSPACE_SEMANTIC_MODELS is off by default. It also accepts off,
0, and disabled; any other value is an error.
A pack that declares safety.review_required stays inert on every host until
an enable control names its pack id. Both routes read that list from
.bifrost/packs.json. A policy report shows an inert pack as a
workspace-model-inert warning.
Procedure-summary targets use the declaration identity that Bifrost publishes;
they are not source-text search patterns. has_receiver is the normalized
semantic receiver availability at the call boundary, not simply whether the
syntax has an AST object. For Java, a fully-qualified or import-qualified static
type qualifier such as URLDecoder.decode(...) is not an actual receiver, so a
summary authors "has_receiver": false; the engine normalizes the proven type
reference to that receiverless key. An instance transform such as s.trim()
uses "has_receiver": true and cannot cross-bind to a static summary. For Rust,
target.path is the artifact-relative path and target.symbol must be the
declaration row’s exact dot-qualified fq_name, with the authored
has_receiver flag and parameter_count matching that declaration’s
structured callable signature.
target.symbol has two forms, and they differ in what supplies the procedure’s
owner. A qualified symbol names its own owner: Acme.run at
path: "src/acme.ts" targets the member run of Acme, and the path is
provenance rather than part of the identity. A bare symbol targets a
module-level declaration, which has no owner in its name at all; the module that
target.path names is its owner. So run at path: "src/run.ts" targets the
top-level function run of the module src/run, the path with its extension
removed. Author the bare form for a top-level function in a language that
qualifies nothing by package, such as JavaScript, TypeScript, or Ruby. Do not
author a bare symbol for a member that has an owner: a qualified symbol and a
bare symbol name different declarations.
Set variadic: true when a target declares a rest formal, and count every
declared formal, including rest formals, in parameter_count. Schema fourteen
formals identify a positional rest (*args), a keyword rest (**kwargs), or
one rest slot that accepts both domains. A callable may declare one positional
and one keyword rest formal. Each direct extra positional or unmatched named
actual binds to the corresponding rest formal; an empty rest collection is
valid. Calls with *values or **options spreads and unmatched keywords
without a keyword-rest formal remain incomplete. Older unstructured variadic
summaries retain their one-final-formal rule. Fixed-prefix claims, such as
Testify’s refinement of err before msgAndArgs, apply at every accepted
actual arity. A summary still cannot describe flow from or a refinement of a
rest formal itself: authoring validation rejects claims that reference a
variadic-tail ordinal.
For example, a free function declared as parse_ruby_tree in
crates/bifrost-ruby/src/declarations.rs is authored as
crates.bifrost-ruby.src.declarations.parse_ruby_tree with
"has_receiver": false. Bifrost reports per-summary match counts in the pack
activation review; an active reviewed workspace summary that matches zero
procedures emits a workspace-model-inert warning naming the model, summary,
and symbol.
Workspace files are data only. The loader uses the normal safe YAML or JSON parser. It does not load arbitrary code, follow links, execute a generator, download content, or read outside the workspace trust boundary. A content edit changes its review hash.
Match debugging and conformance
Section titled “Match debugging and conformance”Analyzer hosts can use these public library functions:
explain_semantic_model_siteidentifies the pack, rule, source, activation evidence, captures, typed emissions, shadowing, and first failed predicate.preview_semantic_model_emissionsshows declarations, relationships, and aliases for one active rule and an explicit capture map.scan_unmapped_semantic_model_sitesscans normalized AST facts within file, node, and result limits. The caller supplies reviewed structured selectors and labels each family asmodel_eligible_generatororinspectable_source_macro.run_semantic_model_conformancechecks a golden fixture (fixture format version 1) against the production overlay and matcher.
The scan classification is explicit. A call-shaped AST node does not prove that generated output is safe to model. The scan never uses regular expressions, source-text search, generator execution, or an implicit download. An exhausted limit marks the report incomplete.
Conformance fixtures can assert symbols, owners, signatures, hierarchy,
relationships, forward definitions, inverse usages, authored anchors or
portable model URIs, provenance, completeness, and positive or negative
matches. Keep fixtures under review with the model. Run them after every model
change. The report uses bifrost_semantic_model_conformance/v1 and returns
explicit missing expectations.
For a miss, first inspect first_failed_predicate. Then inspect activation
evidence and shadowing. A trigger mismatch means that the normalized node or
exact trigger name did not match. An unbound capture means that the structured
fact did not supply one required value. A conflict means that equal-precedence
rules failed closed, so production emitted neither rule.
Version and extension rules
Section titled “Version and extension rules”Every source pack must contain a schema_version this build supports, from
2 through 17. The field is mandatory: omitted, zero, and unsupported versions
fail instead of falling back. Every object rejects unknown fields, and every
variant is explicitly tagged. A schema addition takes a new version number and
a new checked-in schema rather than silently widening a number that already
shipped.
Producers write 17. Schema seventeen adds runtime_bases to Python class
facts. An absent claim makes no assertion; an explicit empty list claims a
runtime root. A populated list claims the exhaustive ordered direct runtime
bases, using non-nullable, non-parameterized named or declared class references.
It is separate from typing inheritance and member-inventory completeness.
The current Python CSMI export profile rejects these claims rather than losing
them. Schema sixteen adds exact_return_classes to callable
declarations. This author-written set lists every runtime class a callable can
return; it does not follow from the return annotation or callable-shape
completeness. Without the claim, the result remains an open class bound.
Schema fifteen added typed module-value
facts and request roles on callable parameters. Reading a set of versions rather
than one exact number is what lets an installed older pack or release asset keep
loading across the bump and be regenerated on its producer’s normal cadence. A
field or variant a version introduces is rejected in a pack that declares an
earlier version, so a pack that an older reader accepts by its number is one
that older reader can also parse:
ambient_use on a schema_version: 2 pack is a validation error,
not an additive field. Schema eleven added structured
procedure-formal names,
passing modes, and default-presence facts. These let a summary bind supplied
positional or keyword actuals while leaving only declared-default formals
unbound. Schema eleven also introduced reviewed channel-close and
returned-callable invocation effects.
Schema twelve remains supported and carries the reviewed concurrency effects, using channel identities declared by the model. Unresolved context or callable identities remain open. Schema thirteen combines both histories’ structured formals and reviewed effect fields. Schema fourteen adds a directional kind to structured rest formals, so direct extra positional or unmatched named actuals bind exactly; call-site spreads remain incomplete.
Version ten adds native callable-value facts and scoped coverage to authored shards. A callable value names the exact declaration and invocation shape that produces it, while coverage records which source locations were reviewed.
Version nine adds kind: unknown for a parameter or result type port whose
type is unavailable. It preserves the fact that the port exists while leaving
its type unresolved. This differs from an absent result port and from an
incomplete signature or body; none of those absences supplies an unknown type
for a known port.
The machine-readable contract is
schemas/semantic-model-pack-v17.schema.json.
It is generated from AuthoredSemanticModelPack; a repository test requires
the checked-in bytes to match the Rust-derived schema exactly.
The version-two through version-eight schemas stay checked in as frozen
descriptions.
The version table lists each version’s additions and the validation code for each gate.
Version eight removes compatibility.bifrost. Version-seven and older
packs retain the required engine range; version-eight and newer packs omit it.
The producer version still names the tool that wrote the pack, but it does not
gate activation. When a catalog is upgraded, existing packs keep their bytes and
digests. Toolchain requirements, artifact identity, selectors, and integrity
checks continue to apply for every version.
Version seven adds no_concurrency_effects to procedure summaries. It is a
reviewed claim that the procedure neither synchronizes with caller-visible
memory nor retains it for concurrent work. Only a complete summary with no
concurrency_effects may make it.
Version six adds callable_surface_complete to TypeFact. It is a positive
inventory claim: every directly declared, externally visible callable and
every direct hierarchy edge of the type is in the pack. It does not cover
inherited members, fields, effects, or overload applicability.
Version five adds reviewed normal-result use obligations to procedure summaries. An obligation names one normal-result ordinal and either a pure transformation value or a fallible status with an explicit failure predicate. An omitted obligation makes no claim that discarding the result is safe or unsafe.
Version four adds the runtime_contracts companion for CSMI runtime-values 0.2.
It retains the five fact families and their semantic-document envelope. Packs
declaring an older schema cannot carry this companion. Importing a contract
does not establish deployment identity, consumer review, or a native runtime
load; those remain separate evidence requirements.
Version three: contextual declaration roles
Section titled “Version three: contextual declaration roles”Version three adds one optional field, ambient_use, to TypeFact and
MemberFact. It answers whether importing that declaration can consume it
without the importing file spelling the imported name: not_ambient proves that
every use spells the name, while implicit and given name a contextual role
the compiler selects by type. Absence means the producer did not review the
declaration, so a consumer must treat it as unknown; a Method, Property or
Class kind is not negative evidence either, because Scala records an implicit
val, def, class and object under exactly those kinds. Only not_ambient
proves anything, which is what lets the unused-import derivation publish a Scala
finding whose target is outside the workspace.
YAML is a presentation syntax, not a second data model. Loading permits one document and rejects duplicate keys, aliases, anchors, merge keys, includes, property expansion, legacy boolean spellings, excessive nesting, excessive events, and over-budget scalar or comment data. JSON and YAML both deserialize directly into types that reject unknown fields.
Envelope and activation
Section titled “Envelope and activation”The envelope records a stable pack ID and semantic version, producer identity,
language and ecosystem, toolchain compatibility (and an engine version range in
versions 2 through 7), provenance, an SPDX
license expression, completeness, safety metadata, and independently loadable
shards. The envelope reference lists each field,
whether it is required, and its allowed values.
Each shard has one or more activation selectors. A selector can identify a package, module, or declared toolchain using an exact name and optional SemVer constraint. It may narrow activation by target, configuration, or a lowercase SHA-256 artifact digest. The compiler derives sorted routing keys from these selectors and, for rule shards, their trigger kinds. The runtime uses the keys to avoid reading unrelated payloads, then strictly rechecks every populated selector field. Missing evidence never satisfies a constraint.
A version constraint states the compatible range the pack’s extracted API
surface serves, not the one build it was extracted from. A pinned release spec
must bound that range above, so a pack cannot answer for an API surface it was
never extracted from; bifrost-semantic-pack generate rejects an open
requirement such as >=21.0.0 in a spec. Authored packs are not subject to
this check: validate accepts an open requirement. The exact build stays
recorded as the pack version, the pinned artifact digests, and the upstream
provenance revision. Because activation no longer implies that
the workspace runs that exact build, the activation decision names both the
build the pack carries and the requirement that admitted the workspace: a
selected pack’s reason in packs.decisions reads pack version 21.0.8; matched toolchain jdk 21.0.0 against the pack requirement >=21.0.0, <22.0.0. A version
outside the range is still rejected, with a reason naming the workspace version
and the requirement.
Activation evidence is supplied as complete rows so a package, module,
toolchain, target, configuration, and artifact digest from different resolved
artifacts cannot be combined accidentally. Exact artifact evidence outranks
versioned coordinates, which outrank named coordinates and language-only
selectors. Ephemeral and durable workspace-produced sources outrank generated,
installed, pre-shipped, and embedded sources in that order. A workspace control
outranks a user control, but neither can bypass compatibility. Packs marked
review_required need an explicit compatible enable. Equal-rank conflicting
facts remain conflicts instead of becoming a last-write-wins answer. The
discovery and precedence reference gives the
full selection order and the activation status values.
The generation-scoped runtime owns every selected decoded shard and builds exact-key postings for type and member IDs and names, aliases, relation IDs and directions, and every generator trigger. Lookups do not read SQLite, pack files, or unrelated postings; the matcher has no wildcard fallback path. Work, index entries, working bytes, retained bytes, and explanations are bounded. Cancellation, corruption, stale generations, and exhausted budgets never publish a complete cached value.
Catalog and lifecycle
Section titled “Catalog and lifecycle”SemanticPackCatalog stores durable packs beneath one caller-selected shared
root. The caller chooses the root explicitly so a host can apply its own
platform and configuration policy. Catalog metadata lives in a separately
versioned SQLite database. Immutable shard bytes live once at
objects/sha256/<first-two-hex>/<remaining-hex>, keyed by the SHA-256 of their
exact stored representation. The manifest content digest identifies the
complete pack; semantic and uncompressed-content digests retain their distinct
artifact roles.
Installation validates the canonical manifest and every manifest-bound shard before publishing anything discoverable. Files are staged, synchronized, and atomically moved into the content-addressed tree. A single metadata transaction then publishes the complete pack, its normalized selectors, and its source. Installing identical bytes from several workspaces or sources reuses the same physical object while retaining every durable source attribution. Startup reconciliation removes bounded abandoned staging and unreferenced final files; it never promotes an orphan into an installed pack.
Candidate discovery narrows by language, ecosystem, and the populated package, module, toolchain, or artifact selector index without reading shard payloads. It then checks SemVer compatibility, target, configuration, and artifact identity from validated catalog metadata. Candidates are opaque catalog handles: callers can inspect their identity and source through accessors but cannot alter the descriptor or provenance used by verified loading. Loading rechecks the digest path, size, stored bytes, manifest envelope, and decoded shard. Missing or corrupt durable content becomes a safe miss and is quarantined in a writable catalog; a read-only catalog rejects durable mutations and suppresses repeated attempts only for that process.
Durable source kinds are installed, generated, pre-shipped, and workspace-produced. Embedded release resources and ephemeral-workspace packs use the same complete validation and selector path but remain in the catalog instance’s session memory, so they are never copied into durable storage. Persistent workspace active sets may reference only exact registered durable sources. In-memory workspaces may reference only exact session sources because they have no durable workspace identity that can own a cross-process activation. Their activation accounting is tied to the workspace store’s lifetime. Catalog active-set identity is a domain-separated digest over the full sorted manifest and source references, and durable activation rows protect selected objects across processes.
That catalog identity is deliberately distinct from the runtime
active_model_set_hash. The runtime hash covers selected semantic shard
digests, payload kinds, matcher precedence and activation evidence, extraction
gaps, and the matcher representation version. It excludes equivalent source
attribution and storage encoding. Analyzer-snapshot caches use a canonical
activation-request key and retain only complete immutable runtimes. Before
publication, the runtime rechecks source generations and coordinates selected
catalog references with the workspace store. A failed or incomplete build
preserves the previous active set.
Projection into synthetic analyzer declarations and model URIs
Section titled “Projection into synthetic analyzer declarations and model URIs”A ready declaration runtime publishes one immutable overlay in the analyzer
snapshot cache. The overlay owns typed type, member, hierarchy, and relation
rows plus exact indexes by stable fact ID, qualified name, alias, and location.
It is not part of the project filesystem: overlay records never appear in
Project::all_files(), never become ProjectFile values, and never trigger
file watching or source-cache operations.
The overlay also evaluates active generator-rule shards against the analyzer’s
normalized structural facts. It evaluates language_construct, annotation,
annotated_field, macro_invocation, and generator_invocation triggers,
every capture source except resolved_owner, bounded template expressions,
and typed declaration, relation, and alias emissions. A fully qualified
annotation trigger matches the same qualified spelling. An unqualified
annotation matches a qualified trigger only when exactly one non-wildcard
import in scope binds it to that qualified name. resolved_owner and
resolved_call triggers never match until the analyzer can supply their typed
resolver evidence; Bifrost does not replace that missing evidence with
source-text matching.
Every overlay declaration has one honest location. If the pack’s locator names
a real declaration in the current analyzer snapshot and the exact symbol and
path resolve, navigation uses that authored anchor and its real declaration
range. Otherwise Bifrost creates a deterministic, percent-encoded
bifrost-model://v1/<pack-semantic-digest>/<record-kind>/<stable-record-id>
URI with a deterministic virtual range. A model URI is a portable identity and
navigation target, not generated source. Source-reading tools render a bounded
typed description explicitly labeled as modeled content.
A unique navigates_to relation on a model-only declaration is followed by
definition and declaration navigation. Its target may be another unique
modeled declaration or an exact authored symbol. Multiple or conflicting
navigation targets fail closed, and an absent target is reported instead of
falling back to the model declaration.
Symbol search, symbol locations, definition and modeled-usage results, CodeQuery declaration projections, and MCP and Python JSON preserve the overlay provenance. It includes the active-model hash, pack semantic digest and identity/version, producer and version, stable record ID, catalog source and activation explanation, origin, proof kind, completeness, and ambiguity. Model relations are reported as semantic facts and do not invent source snippets or call-site ranges.
Authored workspace declarations and exact navigable artifact declarations win over model-only locations. A unique model fact fills information that authored analysis does not have. Equivalent facts deduplicate; equal-rank differing facts remain ambiguous, and definition, usage, hierarchy, and query consumers must not select one. Catalog mutation generation and SQLite data-version are part of runtime cache identity, so an install, replacement, quarantine, or source removal cannot leave an old overlay attached to an unchanged analyzer snapshot and activation request.
Accounting reports deduplicated installed and active bytes, physical objects, logical and active shards, sources, lookup hits and misses, quarantined packs, and activation counts by durable or session source. Pins, durable sources, workspace activations, reader leases, and in-flight installation reservations protect content from collection. Explicit bounded garbage collection removes only old packs with none of those roots, rechecks each object under the catalog write boundary, and reports bytes only when a file was actually removed.
Exact-artifact producers
Section titled “Exact-artifact producers”Bifrost can construct declaration packs directly from one caller-selected Java source JAR, Java class JAR, or .NET assembly. The producer API does not discover dependencies, infer package coordinates from filenames, download artifacts, solve classpaths, or install its result. Discovery remains an analyzer concern; production receives the exact path together with explicit pack, ecosystem, compatibility, activation, provenance, license, and safety metadata.
The producer reads and hashes the exact bytes once under a caller-controlled
artifact limit. It copies the lowercase SHA-256 into every supplied activation
selector and returns it beside the authored pack. Archive entry counts,
per-entry and total uncompressed bytes, declaration records, signature depth,
diagnostic count, diagnostic text, and diagnostic locations are bounded.
Invalid input that cannot be identified produces no pack and, when the caller’s
diagnostic budget permits, a bounded error diagnostic. Unsupported metadata or
an exhausted extraction limit can instead produce a useful partial pack with
stable diagnostic codes and a suppressed-diagnostic count.
Java source declarations are read from tree-sitter syntax. Java class
descriptors and generic Signature attributes use a bounded grammar parser; C#
types are read structurally from PE/CLI metadata. Producers emit public and
protected API types and members after applying enclosing-type visibility. Java
package-private declarations remain available to the legacy same-package
resolver but are not exported as reusable public API facts. Dependency entries
and assemblies remain external and are never added to Project::all_files().
Declaration identity deliberately excludes origin. Equivalent Java source and class declarations receive the same IDs, as do equivalent C# declarations from another copy of the same semantic API. Source paths, JAR entries, assembly metadata tokens, parameter names, and artifact digests do not participate in those IDs. They do participate in locators, activation, or compiled pack bytes, so a source pack and binary pack can share declaration IDs while retaining different pack and shard digests. Member identity includes owner, kind, name, generic arity, ordered parameter types, and return type. Including return type preserves distinct CLI metadata members such as conversion operators even though ordinary Java and C# source methods cannot overload only by return type.
Binary formats do not guarantee parameter names. signature.parameters[].name
is therefore optional: producers retain a source or binary name when it is
available and omit it otherwise rather than inventing arg0-style data.
signature.parameters[].passing_mode records whether a formal accepts
positional_only, positional_or_named, or named_only arguments. The field
defaults to positional_or_named; Python stub producers derive the other modes
from the structured /, *, *args, and **kwargs parameter regions so
model applicability cannot accept a call the declared API rejects.
Generic parameter names are retained from Java Signature or CLI GenericParam
metadata when present. Unsupported generic shapes make the result partial
instead of being flattened to a misleading string.
Preparing packs from local dependencies
Section titled “Preparing packs from local dependencies”Hosts can connect dependency discovery to the shared catalog without giving
the analysis library ownership of a global path. Open a
SemanticPackCatalog at the host-selected root, resolve exact records with
resolve_jvm_semantic_pack_dependencies or
resolve_csharp_semantic_pack_dependencies. Go hosts opt in through
AnalyzerConfig::go.dependency_discovery, call
resolve_go_semantic_pack_dependencies, and use GoDependencyPackAdapter.
Pass the returned
DependencyDiscoveryOutcome to prepare_discovered_dependency_semantic_packs
with the matching adapter. The discovery outcome carries bounded diagnostics,
completeness, cancellation, and input/resolution counts; an unresolved
coordinate or malformed dependency manifest therefore cannot collapse into a
successful empty dependency set. Preparation
never downloads packages, scans an entire package cache, or implicitly builds
a project. Maven and Gradle build-tool discovery remains explicitly offline
and opt-in.
Each generated-production key covers the ordered artifact roles and exact byte digests, producer and adapter versions, semantic-pack schema version, activation evidence, normalized ecosystem provenance, and every producer or compiler limit that can affect output. Paths and mtimes are excluded. Artifact locators stored by local-dependency adapters are content-addressed rather than derived from a local filename. A second workspace using identical bytes and evidence through the same catalog therefore reuses the verified manifest without invoking the producer. A byte, coordinate, target, configuration, asset-role, adapter, producer, or schema change creates a different production. Installation binds the production key, generated source, and verified manifest in one catalog transaction; corrupt bindings are safe misses, and garbage collection removes bindings with their unreferenced manifests after the configured age. Generated source attribution is cache metadata, not a permanent GC root; pins, active sets, and leases protect generated packs that remain in use. Lookup verifies the manifest and every referenced shard object before reporting reuse.
JVM records retain exact Maven coordinates and distinguish Maven reports, Gradle reports, Maven repositories, Gradle coordinate-cache directories, and explicit paths. A class JAR and optional source JAR form one production. Source facts and locators win for shared stable declaration IDs, binary-only facts are added, and incompatible facts make coverage partial with a bounded diagnostic. The legacy resolver still retains package-private declarations that are not exported as reusable public API facts. It consumes the same bounded dependency discovery result, while retaining its compatibility projection for those analyzer-only declarations.
.NET records retain NuGet package/version, target framework, configuration, reference/compile/runtime role, and project-reference provenance. Reference assets outrank compile and runtime duplicates for the same semantic assembly; different targets and project-output configurations remain separate evidence. Explicit assembly paths do not invent package coordinates.
Rust records use a passive, host-supplied evidence bundle. The bundle contains
Cargo metadata format-version-1 JSON, its Cargo.lock, the selected target and
configuration, selected workspace targets, exact per-package feature lists,
and an explicit rustdoc JSON artifact for every dependency API to index.
resolve_rust_semantic_pack_dependencies validates that registry, git, and
path packages are reachable from the selected targets and agree on package
version, source, checksum, crate name, target triple, rustdoc format, and
artifact binding. Dependency renames remain provenance; package and crate
identity do not change with the local binding name.
Bifrost does not run Cargo or rustdoc to create this evidence. It does not scan
Cargo caches or target directories, download crates, compile dependencies, run
build scripts, or load procedural macros. A host may generate rustdoc JSON in a
separately controlled build step and pass the resulting paths through
RustAnalyzerConfig. Bifrost then reads only those paths. The decoder is pinned
to one exact rustdoc-types format; missing, inconsistent, or unsupported
artifacts produce explicit incomplete coverage. Public procedural-macro names
may appear in a pack, but macro code is never loaded or executed. Exact nightly
toolchain strings are retained in production provenance and cache identity
rather than treated as semantic-version compatibility coordinates.
Go discovery asks only the configured go executable for machine-readable
go env and go list metadata. The child process has its ambient Go
configuration cleared, uses GOTOOLCHAIN=local, GOPROXY=off,
GOSUMDB=off, GOENV=off, and CGO_ENABLED=0, and receives the configured
GOOS, GOARCH, and build tags. When non-test files are excluded, a second
metadata-only go list with cgo enabled identifies cgo surfaces that the
disabled view would otherwise classify only as ignored. It never builds,
tests, runs, or generates a package and it never downloads a module or
toolchain. The selected standard
library, module-cache, local replacement, workspace, and vendor files become
exact source-set inputs; normalized relative paths and retained bytes determine
their identity, not absolute cache locations or mtimes.
Generated Go packs contain exported packages, types, aliases, functions,
variables, constants, methods, fields, structured signatures, type parameters
and constraints, underlying types, embeddings, receiver forms, and promoted
members. Exact import paths remain package identity even when the declared
package name differs from the path’s last segment. Non-exported carrier types
may be retained to derive a public promoted surface, but overlay search and
navigation do not expose them. Activated facts participate in definition,
hover, signature, hierarchy, symbol, and whole-workspace reference paths while
dependency and GOROOT files remain outside Project::all_files().
Coverage is explicitly partial when selected packages report errors, cgo or
generated source participates in the selected build, build constraints are
malformed or otherwise rejected by the configured Go toolchain, source is
malformed, or local artifacts, time, cancellation, or a configured bound stop
discovery. Files that the toolchain excludes for the exact target and tags do
not enter the pack and do not by themselves make that configured surface
partial. Cgo execution, compiler-equivalent type checking, SSA/body indexing,
go generate, and implicit network resolution are not provided. Go internal
package access is checked from canonical import paths, and authored workspace
declarations take precedence over otherwise matching pack facts.
Python environment records are intentionally more explicit. A host supplies a
declared interpreter implementation, version, platform, standard-library root,
optional bundled-stub roots, and selected distribution roots; Bifrost never
discovers sys.path, starts Python, imports a module, executes package setup,
or contacts a package index. It reads only .pyi and safe .py files plus
static .dist-info/.egg-info metadata. Precedence is deterministic: bundled
stubs, stub artifacts, inline py.typed source, then safe implementation
source. Dynamic exports, malformed files, and missing static surfaces remain
partial coverage with diagnostics rather than invented declarations. Hosts
activate the result explicitly with
WorkspaceAnalyzer::activate_python_environment_packs; dependency files stay
outside Project::all_files(), and external navigation uses bifrost-model:
locations rather than synthetic workspace files.
A stub declares part of its surface inside a sys.version_info or
sys.platform block. The producer never evaluates such a block. It records the
block’s condition on each declaration the block encloses, as an inclusive
minimum toolchain version, an exclusive maximum, and required or excluded
activation targets. A condition it cannot express is recorded as an
uninterpreted guard, which keeps the declaration and states that the pack read
less than the whole condition. Activation then drops a declaration only when
the pinned toolchain version or target provably fails a recorded constraint,
so an unknown coordinate and an uninterpreted guard both keep the declaration.
The declared platform is therefore the interpreter’s own sys.platform
value, the same vocabulary a stub’s platform guard names.
A library host supplies this environment in
AnalyzerConfig::python.environment (PythonEnvironmentConfig): the
implementation, version, platform, standard_library_root,
bundled_stub_roots, and distribution_roots. Separately,
.bifrost/packs.json can bind a source tree to installed wheels through
python_runtime_environments, so Bifrost can verify the installed files
against the original wheel archives without running Python. The
reference describes that field. The separately
distributed bifrost-lsp server documents its own initialization options.
Ruby records likewise use passive, host-supplied evidence. A
RubyDependencyApiEvidence row identifies one Gemfile.lock by path and
SHA-256, the exact Ruby version and platform, explicit approved archive roots,
and each selected gem’s name, version, source, optional checksum, and .gem
archive path. resolve_ruby_semantic_pack_dependencies reads only those named
files. It does not run Ruby, Bundler, RubyGems, Sorbet, Steep, extension builds,
gem hooks, or generators; it does not scan gem caches or access the network.
The Ruby adapter reads a .gem as bounded nested tar and gzip streams without
extracting it. It projects RBS through the typed ruby-rbs parser and parses
RBI and ordinary Ruby declarations with tree-sitter. RBS takes precedence over
equivalent RBI, which takes precedence over ordinary source. Reopened scopes
and overloads are retained, while contradictory typed declarations make the
pack partial and remain visible as conflicts. Classes, modules, instance and
singleton methods, attributes, aliases, structured signatures, and ordered
prepend, include, and extend relations are supported. Unsupported dynamic
metaprogramming produces bounded partial diagnostics rather than speculative
facts.
An embedded host supplies the evidence explicitly:
use std::path::PathBuf;
use brokk_bifrost::{ AnalyzerConfig, RubyAnalyzerConfig, RubyDependencyApiEvidence, RubyGemApiArtifact,};
let config = AnalyzerConfig { ruby: RubyAnalyzerConfig { dependency_api_evidence: vec![RubyDependencyApiEvidence { lockfile_path: PathBuf::from("Gemfile.lock"), lockfile_sha256: "<lowercase-sha256>".into(), ruby_version: "3.4.1".into(), platform: "ruby".into(), approved_archive_roots: vec![PathBuf::from("/controlled/gems")], gems: vec![RubyGemApiArtifact { name: "rack".into(), version: "3.2.1".into(), source: "https://rubygems.org/".into(), checksum: Some("<lowercase-sha256>".into()), gem_archive_path: PathBuf::from("/controlled/gems/rack-3.2.1.gem"), }], }], }, ..AnalyzerConfig::default()};Relative lockfile and archive paths are resolved against the project root;
canonical archive paths must remain under the project root or an explicit
approved root. Hosts then pass the discovery outcome to
prepare_discovered_dependency_semantic_packs with
RubyDependencyPackAdapter, compose its exact evidence into the activation
request, and activate it through the normal semantic-model runtime. Archive
members receive digest-qualified logical locations and never become project
files.
Three more adapters read only local files. The npm adapter reads
package-lock.json and npm-shrinkwrap.json lockfiles and the manifests of
locally installed packages to find exact declaration entry points; it does not
add dependency files to the workspace. The Composer adapter reads
composer.lock, vendor/composer/installed.json, and installed package trees
under approved vendor roots, with at most 64 autoload rules per package; it
never runs Composer, a Composer script, or a plugin. The C++ adapter reads
compile_commands.json and builds packs from the headers reachable from
include roots outside the workspace. The Python dependency resolver reads the
configured environment described above, or, when none is configured, the
workspace’s declared toolchain to select a standard-library pack. The
ecosystem table maps each .bifrost/packs.json
label to its languages, input files, and bounds.
Preparation is bounded by dependency, artifact, total-byte, producer, compiler, and diagnostic limits. It checks cancellation between file chunks, JAR entries or source files, CLI metadata batches, and every lookup/production/compile/install boundary. Missing, unreadable, malformed, unsupported, cancelled, or over-budget inputs remain explicit partial coverage; they never become an authoritative empty result. The returned profile separates artifacts and bytes read from reused and generated packs. Partial generated packs are reused because identical inputs to a deterministic producer cannot improve their coverage. Reuse reports an actionable partial coverage diagnostic instead of rerunning the producer.
DependencyPackPreparationOutcome::compose_activation_request merges exact
successful evidence into a host-owned activation request. It returns no
request after cancellation or wholly unavailable partial preparation, so a
host cannot accidentally replace a previously complete workspace active set
with authoritative empty dependency coverage. Preparation itself never
publishes an analyzer overlay or workspace active set.
Declaration-fact payload
Section titled “Declaration-fact payload”A declaration shard contains typed records rather than arbitrary maps: types,
members, structured signatures and type references, explicit member ownership,
typed hierarchy edges, aliases, extension surfaces, navigation/reference
relations, and typed source or artifact locators. Type and member facts carry
visibility and relevant modifiers. Language-facing names retain ordinary
language spelling such as TValue, _value, or getURL; only pack and fact
identities use Bifrost’s lowercase stable-ID grammar. The
declaration facts reference lists every field of
TypeFact, MemberFact, Signature, Parameter, and TypeRef, with its
default and the meaning of its absence. The example below is a complete,
valid pack.
A curated declaration pack may remain globally partial while one reviewed
fixed-arity member sets callable_family_complete: true. The claim covers only
that member’s exact structural lookup family: language, owner, member name,
receiver form, and arity. It does not prove the owner’s whole member surface or
the pack complete. A second declaration in the same family remains ambiguous,
and authoring validation rejects the claim on variadic members until their
multi-arity completeness can be represented explicitly. Omit the field (its
default is false) whenever that exact family has not been exhaustively
reviewed.
schema_version: 2pack_id: acme.widgetversion: "1.2.0"producer: name: artifact-scanner version: "2.0.0"language: javaecosystem: mavencompatibility: bifrost: ">=0.8.0, <1.0.0" toolchains: - name: jdk requirement: ">=17.0.0"provenance: source: "https://repo.example/acme/widget-1.2.0.jar" revision: "sha256:example"license: Apache-2.0completeness: completesafety: generated_code_only: false review_required: falseshards: - id: declarations.widget activation: - package: name: com.acme:widget version: ">=1.0.0, <2.0.0" targets: [jvm] configurations: [release] payload: kind: declaration_facts types: - id: type.widget name: com.acme.Widget type_kind: class visibility: public type_parameters: [t] hierarchy: - hierarchy_kind: extends target: kind: named name: java.lang.Object aliases: [com.acme.LegacyWidget] extension_surfaces: [com.acme.WidgetExtensions] locator: kind: artifact path: com/acme/Widget.class symbol: com.acme.Widget members: - id: member.widget.create owner: type.widget name: create member_kind: method visibility: public signature: parameters: - name: input type: kind: named name: java.lang.String returns: kind: named name: com.acme.Widget aliases: [] locator: kind: artifact path: com/acme/Widget.class symbol: create(java.lang.String) relations: - id: relation.widget.navigation relation_kind: navigates_to from: member.widget.create to: type.widgetCurated Go behavior packs activate alongside declaration companion packs when
an exact package function or concrete result type must be selected at a call
site. The companions are deliberately partial package surfaces: each
declaration is a positive identity and signature fact, while an unlisted
package member remains unknown rather than proved absent. Separate companions
ship for os, errors, log, net, net/url, net/http, bytes,
encoding/pem, crypto/x509, sync/atomic, the concurrency summaries, and
Testify require and assert; the testing pack already carries its partial
concrete-receiver declarations. Keeping a
partial declaration surface out of a complete behavior pack also preserves the
envelope invariant that a partial pack cannot claim a complete summary. The
declarations-only path/filepath pack publishes the module plus the exact
variadic Join(...string) string signature, allowing a containing modeled call
to distinguish its single result from Go’s multi-result argument expansion
without attaching a behavior summary to Join.
Generator-rule payload
Section titled “Generator-rule payload”A generator shard declares a typed trigger, trigger-relative capture bindings, and typed declaration, alias, or relation emissions. Capture sources identify the matched node, the enclosing declaration, the owning type, the owned fields (all, mutable only, or uninitialized final only), the resolved owner, one argument, an argument suffix, or a named annotation argument. A projection then requests a name, stable ID, type, text, or path. The compiler checks that each source is available for its trigger and that its projection, declared value kind, and cardinality agree. Each source has one required cardinality.
A many capture produces one emission for each value; several many
captures in one rule must produce the same number of values. A group capture
keeps its values together in one emission, where repeated_parameters renders
once per value. A template cannot use an optional capture. Language-name
positions accept identifier or stable-ID captures, type positions accept only
type captures, and stable-ID positions accept only stable-ID captures. Unknown
captures and stable-ID templates with unsafe boundaries or case transforms fail
compilation. The generator rules reference lists
every trigger, capture source, template operation, and emission.
schema_version: 2pack_id: acme.buildersversion: "1.0.0"producer: name: policy-author version: "1.0.0"language: javaecosystem: mavencompatibility: bifrost: ">=0.8.0, <1.0.0"provenance: source: "https://docs.example/acme-builders"license: MITcompleteness: partialsafety: generated_code_only: true review_required: trueshards: - id: generators.builders activation: - package: name: com.acme:builders version: ">=1.0.0, <2.0.0" payload: kind: generator_rules rules: - id: rule.builder trigger: kind: annotation name: com.acme.GenerateBuilder captures: - name: owner_id binding: source: kind: enclosing_declaration projection: stable_id value_kind: stable_id cardinality: one - name: entity binding: source: kind: enclosing_declaration projection: name value_kind: identifier cardinality: one - name: entity_type binding: source: kind: enclosing_declaration projection: type value_kind: type cardinality: one emissions: - kind: declaration id: op: concat values: - op: capture name: owner_id - op: literal value: .builder name: op: transform transform: pascal_case value: op: capture name: entity declaration: kind: member owner: op: capture name: owner_id member_kind: method visibility: public signature: returns: kind: capture name: entity_typeProcedure-summary payload
Section titled “Procedure-summary payload”A procedure-summary shard describes externally reviewed behavior without activating it. Each record has a stable ID and a structured target consisting of a canonical artifact-relative path, an exact symbol, normalized semantic receiver availability, and parameter count. The compiler derives a pack-scoped model ID, a summary contract version, and a content digest for every record; the defensive decoder recomputes and verifies those fields.
For Java, has_receiver distinguishes an actual value receiver from a type
qualifier. Fully-qualified and import-qualified static calls such as
URLDecoder.decode(...) bind with false, after structured resolution proves
that the qualifier names the target owner. Instance transforms bind with
true; unresolved, shadowed, ambiguous, or mismatched receivers remain
inconclusive rather than being guessed. This field remains a boolean and its
canonical key is unchanged.
Transfers connect a receiver or zero-based parameter to a normal return, an indexed normal result of a multi-result procedure, the receiver, an exceptional return, a declared capture, or a declared heap location. Each transfer carries an explicit normal or exceptional exit kind, matching the reusable-summary contract even when an exceptional exit writes a heap or capture location. Effects represent allocation, calls to another summary in the same pack, escapes, unknown calls, unknown-call boundaries, explicitly ambiguous call sets, and sanitizers that remove named taint labels on a declared transfer. Inputs must exist on the target, outputs must reference a location of the right kind, call targets must exist, and all collections have fixed validation budgets. Duplicate targets and duplicate IDs are rejected across shards.
normal_continuation_absent: true is an optional reviewed control claim that
the exact procedure never returns normally. Omission or false makes no claim.
The flag can be the only substantive content in a summary, and a partial
summary may state it because completeness of other behavior is independent of
this explicit control fact. A summary carrying the flag cannot also contain a
normal transfer, an allocation or sanitizer whose output is a normal return, a
result contract, or a normal-return refinement. Exceptional transfers and
effects that occur before termination remain valid. normal_result_count is
signature metadata rather than a normal-return claim, so it may also remain on
a non-returning summary. At activation time, matching summaries must agree on
the flag; an empty or conflicting match never proves that normal control is
absent.
declared_effects is a separate, optional list of namespaced effect
identifiers the reviewed pack attributes to the exact procedure, such as
acme.network_io. Unlike effects, a declared effect names no input, output,
or callee and never joins the transfer graph; it lets a policy ask whether a
call performs an effect without new analyzer code. Each entry carries:
id: a stable identifier that must contain at least one., so two vendors can ship packs without colliding on a bare word likewrite.timing:immediate(before the call returns),deferred(scheduled by the call), orunknown(the pack asserts the effect but not when it happens).certainty:definite(every execution performs it) orpossible(some execution may).
A summary may declare at most 64 effects, and one summary may not repeat an
id. A partial summary must carry substantive content: a transfer, a
complete transfer partition, an effect, a concurrency effect, a precondition
review, a result contract, a result-use obligation, a conditional refinement
or write, a normal-return refinement, a class decorator identity, or
normal_continuation_absent. Declared effects alone do not satisfy that rule.
A complete summary may be empty. Two activated summaries that match the same
procedure and disagree about their declared effects resolve to a conflict and
are not used. Omitting the field changes no digest, so a pack that declares no
effects keeps the compiled bytes and the digests it had before the field
existed.
A procedure summary can publish optional call-entry preconditions over its
receiver and parameters. The field is three-state: omission means entry
requirements were not reviewed, an empty list is a reviewed claim that there
are none, and a populated list states the exact predicates required at the
call. Exact call-argument analysis uses these claims only when every possible
runtime target is covered and agrees. This is distinct from normal-return
refinements, which establish a predicate after a call returns.
A result contract relates one indexed normal result to a predicate over a
different indexed result, such as a resource whose validity requires its paired
error to be null. The finite predicate vocabulary is null, non_null,
true, and false; the Boolean values let a reviewed (value, ok)-style API
state which exact outcome validates the protected result. They do not evaluate
arbitrary expressions. A member contract can then describe one operation on
that result by exact member name and parameter count. Its own optional
preconditions field has the same three states:
- omitted means the operation’s input requirements were not reviewed;
- an empty list means the operation was reviewed and requires no input predicate;
- a populated list names each receiver or parameter predicate the operation requires.
These states are not interchangeable. Omitting knowledge must not create a finding or a clean negative, while a reviewed empty list is positive evidence that the operation is applicable without first establishing the result’s success predicate. Preconditions reuse the summary’s typed inputs and result predicate vocabulary, so a pack cannot invent an unbound receiver, parameter, or textual condition.
Completeness is explicit at both levels. A partial record remains partial
after compilation and decoding; a partial pack cannot claim a complete
record. Completeness is evidence metadata only and does not enable matching or
flow application.
Summaries can also carry concurrency effects, transfer partitions,
conditional result refinements and indirect writes, normal-return type
refinements, covers_overrides, ordinary_heap_unchanged, and a class
decorator identity. The
procedure summaries reference lists every field,
its minimum schema version, and its validation rules.
payload: kind: procedure_summaries summaries: - id: summary.helper target: path: com/acme/Flows.class symbol: helper(java.lang.String) has_receiver: true parameter_count: 1 completeness: partial locations: - id: location.receiver-field location_kind: heap transfers: - input: kind: parameter ordinal: 0 exit_kind: normal output: kind: normal_return effects: - kind: allocation event: event.helper.allocate output: kind: heap location: location.receiver-field - kind: unknown_call_boundary event: event.helper.unknown-boundary declared_effects: - id: acme.network_io timing: immediate certainty: definite - id: acme.audit_log timing: deferred certainty: possibleShipped packs
Section titled “Shipped packs”The engine embeds 42 reviewed packs: Scala case-class and Lombok and getset
generator rules, JDK 21 result-use obligations, Node runtime-values,
child-process, and Buffer packs, Python process-input and os.system packs,
and Go standard-library, concurrency, and Testify packs. Hosts register them
when they bootstrap the catalog. Most activate when the workspace contains the
pack’s language. The eight Node packs set safety.review_required and also need
an enable entry. The shipped packs table lists
each pack, what it models, and what activates it.
Reviewed Node runtime packs
Section titled “Reviewed Node runtime packs”The embedded registry ships reviewed Node runtime packs for JavaScript
and TypeScript: a runtime-values pack per language modeling the process
global exposure with env static-property and argv static-index
keyed-read behaviors, and a child-process summaries pack modeling the
exact execSync(command) member. TSX workspaces use the TypeScript
packs.
With the reviewed summaries active, the engine consumes them for dispatch:
a require-bound or namespace-bound receiver whose exact external member the
resolver proved closes its residual dynamic-dispatch arm, the declaration
pack’s dispatch reports exhaustive coverage, and a policy run whose only
external callee is the modeled call concludes ProvenBySummary with
definite findings. Complete stays reserved for proof derived from
analyzed code, so a summary-closed run never claims it. A member write or a
module-object escape anywhere in the workspace refuses the closure, and a
callee the activated packs do not summarize keeps the run inconclusive.
Runtime-values activation is a layered contract. An exposure authored as
enabled is intrinsically eligible within its model; it never bypasses
pack activation. Every shipped Node runtime pack sets
safety.review_required, so
activation additionally needs exact host-supplied evidence — language,
npm ecosystem, the exact Node artifact coordinates and digest, and the
reviewed profile configuration — plus an explicit compatible enable
control naming the pack id. The activation report retains that enable
decision, and published endpoints retain the exposure, behavior, and
active-model-set identities.
A workspace supplies that evidence by declaring the reviewed Node revision in
its root package.json (engines.node or volta.node), .nvmrc, or
.node-version. The declaration is code-runtime evidence about the analyzed
program’s runtime, not a configuration document: it is how the workspace states
that the code under analysis runs on the reviewed 22.11.0 distribution. Only
that exact revision resolves to the reviewed archive digest and profile
configuration, so no other pin, and no enable entry alone, can select the
pack.
Authored packs carry no binding evidence or observations. Only
workspace-derived analyzer evidence can authoritatively describe lexical
bindings, writes, keyed loads, and observation identity in a particular
workspace, so the production evaluator joins the authored exposure and
behavior records with the workspace’s own syntax and executable-semantic
facts. Lexically bound roots are conclusively excluded; reassignment,
dynamic keys, unsupported runtimes and profiles, and incomplete analysis
stay typed incomplete. Configuration documents can neither activate nor
intersect the code runtime-value domain; a .bifrost/packs.json entry names a
pack for the review gate but is not runtime evidence.
Canonical artifacts and digests
Section titled “Canonical artifacts and digests”Compilation expands defaults, sorts semantic sets by stable ID, preserves ordered parameters/capture paths/concatenation operands, and serializes compact canonical JSON. Comments, YAML versus JSON, whitespace, and authored object order therefore do not affect the compiled semantic bytes. Changing an ordered parameter does.
The manifest remains uncompressed and readable. Each descriptor records the payload kind, routing keys, raw and stored sizes, record count, declared and referenced stable-ID inventories, encoding, and three lowercase SHA-256 digests:
semantic_sha256identifies activation, compatibility, payload, completeness, and safety after normalization. Provenance, license, and storage encoding do not change it.content_sha256identifies the complete uncompressed shard, including provenance, license, producer, and pack version.stored_sha256identifies the bytes actually transported or stored and therefore changes when encoding changes.
The manifest has its own semantic_sha256 over the ordered shard semantic
identities and a content_sha256 over the entire manifest view except that
content field itself. The content digest therefore binds producer, provenance,
license, compatibility, inventories, routing, sizes, encodings, and every shard
digest while allowing those non-semantic fields to remain outside semantic
identity.
content_sha256, stored_sha256, and the manifest content_sha256 are ordinary
SHA-256 of the exact byte sequence named above. Semantic hashes use
domain-separated length framing:
SHA256(u64be(domain_length) || domain || u64be(byte_length) || bytes). The
shard domain is bifrost.semantic-model.shard.semantic.v1; the manifest domain
is bifrost.semantic-model.manifest.v1. “Canonical JSON” here means the compact
UTF-8 field order emitted by this schema’s compiler, not RFC 8785.
Automatic storage uses fixed raw DEFLATE level 6 only when it saves at least 1 KiB and at least five percent. Otherwise the descriptor points to raw canonical JSON. Encoding never changes semantic or content identity.
Limits and defensive decoding
Section titled “Limits and defensive decoding”Default compilation limits source, each raw shard, and each stored shard to 64 MiB; the readable manifest to 16 MiB; the pack to 1 GiB raw; the pack to 4,096 shards and two million records; each shard to 250,000 records; strings to 16 KiB; and recursive type/template structures to depth 64. Callers can lower these limits. Default compiler output is therefore accepted by the matching default decoder limits.
Decoding checks the manifest and declared sizes before allocation, validates the stored digest before decompression, streams DEFLATE into a bounded buffer, requires the exact raw size and content digest, validates the authored semantics, and re-normalizes decoded values to reject non-canonical JSON or semantic-set ordering. Shard ID, payload kind, routing keys, declaration inventories, record count, and semantic digest must agree with the descriptor. Manifest decoding checks pack-wide declaration uniqueness and references; manifest-bound shard decoding also requires every duplicated envelope field to agree. Truncation, trailing compressed data, excessive expansion, corruption, invalid semantics, or version mismatch fails closed.