%engine.manifest GitHub issue

A debug/introspection tool. Returns a summary of the running Caspian process — what's underneath, what's loaded, how long it's been running.

vibecode
{"vibecode": {
    "doc": "engine_manifest",
    "role": "spec for the %engine.manifest method, a debug and introspection tool that returns a summary hash describing the current process (process metadata, os, engine, Caspian and loaded libs)",
    "purpose": "debug_and_introspection_only",
    "key_concepts": ["debug_tool_not_enforcement_layer", "report_of_actuals_no_constraints",
        "four_sections_process_os_engine_caspian", "libs_with_per_entry_metadata",
        "coverage_subsection_when_coverage_is_on"]
}}

%engine.manifest is a debug tool. It returns a hash describing the running process — the operating system underneath, the engine implementation, the Caspian language version, and the loaded libraries — so a developer (or tooling on a developer's behalf) can answer "what's happening in this process right now?"

It is a reporter. It does not enforce anything, does not check anything against expectations, and has no opinion about whether the running process is correctly configured. Authoring expectations and checking compliance is a separate concern — see Manifest as a constraints file (deferred from V1.0).

Structure GitHub issue

Top-level sections:

A coverage section also appears when code coverage is on (see Coverage below).

Example GitHub issue

This is %engine.manifest os: true — the call with the os option enabled, so all four sections are present. A no-argument call would omit the os block (see Options below).

copy

json
{
    "process": {
        "steps": 3938238,
        "time": {
            "start": "2026-06-03T14:22:08.413Z",
            "stop": "2026-06-03T14:25:47.901Z",
            "run": "PT3M39.488S"
        }
    },

    "os": {
        "name": "Linux",
        "distribution": "Redhat",
        "version": "33.54.6"
    },

    "engine": {
        "name": "Lua",
        "version": "3.302.29",

        "libs": {
            "lua-lib": {},
            "libsodium": {"version": "1.0.20"}
        }
    },

    "caspian": {
        "version": "3.2.1",
        "source": "https://caspian.uno/3.2.1",

        "libs": {
            "foo.bar/gup": [
                {
                    "version": "2.3.45",
                    "timestamp": "2023-08-12",
                    "file": "...",
                    "line": 343,
                    "source": "https://sdjf.ff/adf",
                    "ranges": [{"ts_min": "2023-01-01"}, {"semver": "^2.3"}]
                }
            ]
        }
    }
}

Sections GitHub issue

process GitHub issue

Information about the running process itself.

Origin. At process start, the engine records a wall-clock timestamp (which becomes time.start) and initializes a step counter to 0. When %engine.manifest is called, the engine captures the current wall-clock timestamp (time.stop), computes time.run as the delta, and reads the current step counter. The three time values and the step count are stable for a given call — they reflect the moment of the manifest call, not whatever has happened in between accessing the returned hash and reading its fields.

Both values are advisory. They report how the process is going, not how it has to. The natural use case is calibration for timeouts: run the program, observe runtime and/or steps, then set a timeout comfortably above the observed value. steps is the more stable number for that purpose because it's deterministic — the same program and inputs produce the same step count regardless of host machine. runtime adds the wall-clock dimension but inherits whatever variability the machine introduced (system load, CPU speed, etc.), so it's better as a sanity check than a sole bound.

os GitHub issue

Opt-in via os: true — not in the default output (see Options for why).

engine GitHub issue

caspian GitHub issue

vibecode
{"vibecode": {
    "field": "caspian.libs[].ranges",
    "type": "array_of_range_expressions",
    "appended_per_load_call": "each_puck_call_resolving_to_this_version_pushes_its_range_constraint",
    "entry_shape": "whatever_call_site_range_syntax_produces",
    "engine_role": "records_does_not_interpret",
    "primary_use_today": "debug_why_was_this_version_selected",
    "design_intent": "enables_deferred_constraints_files_feature_by_letting_tooling_derive_constraints_from_recorded_manifest",
    "deferred_until_then": "field_is_debug_only",
    "constraints_files_idea": "https://puck.uno/documentation/ideas/caspian/manifest-constraints"
}}

Load records are process-level state, not object-level — once loaded, entries persist for the life of the process regardless of scope/GC of whatever triggered them.

Post-V1: dependency tree. The libs section today is a flat hash — every loaded library appears, but the manifest doesn't say which library pulled which in. A post-V1 feature will add dependency tracking: for each entry, record which other entries triggered its load (the parent libraries) so callers can reconstruct the dependency tree from the manifest. Useful for supply-chain investigation, version-conflict diagnosis, and "why is this library here?" questions. Deferred from V1 because the cheap implementation depends on per-library role identity that the engine's role tracking hasn't matured into yet. Non-breaking when it lands — likely a per-entry pulled_in_by field or a sibling lib_deps hash. See #539 for the design sketch.

vibecode
{"vibecode": {
    "property": "load_records_are_process_level_state",
    "persistence": "lifetime_of_process",
    "decoupled_from": ["triggering_call_site_scope", "triggering_object_lifetime", "gc_of_either"],
    "survives": ["call_site_return", "local_var_scope_exit", "triggering_object_gc"],
    "records_retained": ["timestamp", "file_and_line_provenance", "accumulated_ranges"],
    "enables": "after_the_fact_debugging_of_what_was_loaded_when_from_where_under_which_constraints",
    "storage": "engine_maintains_load_records_registry_manifest_reads_from_it"
}}

coverage GitHub issue

Appears when %engine.coverage is set to a non-off value. Shows what Caspian code did not run since coverage was turned on — the gaps. The use case is verifying that a test suite actually exercises everything in the system; the gaps are what's missing.

The section is absent entirely when coverage is off — opting into tracking via %engine.coverage is what makes it appear, no separate manifest option is needed. Turning coverage off resets the recorded data; turning it back on starts fresh.

Scope. Coverage always tracks every loaded .caspian file — user code plus all libraries. There is no user-only mode; once tracking is on, every executable line in every loaded file gets a hit count maintained against it.

The value assigned to %engine.coverage controls what's retained in the report:

Setting Retained in report
true uncovered lines only (count = 0)
integer N (≥ 0) lines whose final hit count is ≤ N
:all every executable line, with its hit count

A few notes on the integer form:

The threshold is applied at report time using the same trim-to-threshold reduction as cover-band (see Format below) — every line that ran is counted internally; the setting just controls what survives into the manifest.

Option for full detail. An option (name TBD) on %engine.manifest itself can override the retention setting and produce every executable line with its hit count, regardless of what %engine.coverage was set to. Useful for hot-path analysis or tooling that wants the unreduced picture without changing the in-process coverage setting.

Block-form: scoped tracking. %engine.coverage can also be called with a block:

%engine.coverage do
    $foo.bar
end

Within the block, coverage tracks what's actually running. When the block exits, the captured data is what subsequent %engine.manifest calls report. The block form scopes tracking to a specific region of execution — useful when you want to know what some particular operation touched, without the noise of the rest of the program. The retention threshold (gaps only by default) applies to the block-captured data the same way.

Format: cover-band shape. The data is organized as a two-level hash, modeled on the cover-band Ruby library:

{
    "<file_path>": {
        "<line_number>": <hit_count>,
        ...
    },
    ...
}

The default uncovered-lines report is this same shape, reduced: lines with hit_count > 0 are dropped, then files that end up empty are dropped, so what's left is exactly the gaps. The full-detail option emits the unreduced form.

We'll make Caspian-specific adjustments to the file-path form and any other details as the feature lands.

Options GitHub issue

%engine.manifest accepts a hash of options that toggle which fields are included. The default call returns most of what the manifest can carry; a few fields are excluded by default and require an explicit opt-in. Two reasons a field gets held out of the default:

Known options:

Option What it enables Why opt-in
os: true Adds the os section. Security: someone could accidentally publish their OS details.

More options will be added as additional opt-in fields land. Each gets a row here with the security or expense reason that justifies keeping it off by default.

See also GitHub issue


© 2026 Puck.uno