PtcRunner.Kernel.SafeMetadata (PtcRunner v0.15.0)

Copy Markdown View Source

Validates the closed, payload-free metadata vocabulary used by canonical events.

Safe metadata is intentionally less expressive than general JSON. Every caller-supplied name/model/provider label is reduced to a one-way SHA-256 fingerprint before it reaches a canonical event. Tags and workflow annotations use finite semantic vocabularies: types and keys are closed, and enumerated values are closed. A phased workflow annotation may also carry mission, a bounded identifier rather than a free string. Prompts, credentials, generated source, and arbitrary application data therefore require private inspection.

Summary

Functions

Returns whether an annotation belongs to the finite canonical vocabulary.

Builds the public usage.capability_denials key for one denial.

Maximum distinct closed-class keys retained in usage.capability_denials.

Builds the public usage.capability_refusals key for one error envelope.

Maximum distinct closed-class keys retained in usage.capability_refusals.

Projects an explicit failure value to bounded, payload-free taxonomy.

Returns the canonical non-reversible fingerprint for one bounded identifier.

Returns whether a value is an already-normalized canonical-label map.

Projects an agent LLM failure to one closed, payload-free provider class.

Validates labels and fingerprints caller-defined identifier fields.

Projects a Lisp capability error envelope to a payload-free rejection class.

Functions

annotation?(arg1, data)

@spec annotation?(term(), term()) :: boolean()

Returns whether an annotation belongs to the finite canonical vocabulary.

"agent-verification" carries only a status from accepted, rejected, or unresolved. It never carries candidate values, verifier feedback, or evidence.

"progress" carries exactly one enumerated stage. "agent-action" is the shipped agent loop's coarse per-turn record: turn (an integer from 0 through 127), max_turns (1 through 128), a runtime-authored opaque invocation identifier, and kind (one of tool-call, protocol-error, provider-error, max-calls, or model-output-truncated). A phased agent run adds exactly phase (0 through 7), phase_turn (0 through 127), and mission (the phase's mission name) — all three or none, so a partial shape stays out of the vocabulary. It never carries detailed reasons, generated source, or model content — those stay in the agent's own history and, when enabled, in private inspection records.

capability_denial_key(reason)

@spec capability_denial_key(term()) :: {:ok, binary()} | :error

Builds the public usage.capability_denials key for one denial.

A denial is a capability call the environment rejected before any callback ran, so unlike a refusal it carries no result envelope to classify — and the name it asked for is model-authored text in a mission. The key is therefore the runtime's own closed rejection class, never the requested name. An uncatalogued reason is not a denial and is not counted.

capability_denial_map_limit()

@spec capability_denial_map_limit() :: 2

Maximum distinct closed-class keys retained in usage.capability_denials.

This is capability_refusal_map_limit/0's convention on the sibling map: terminal usage admission reserves this many maximum-length class keys plus $overflow, and that reservation is inside EventBudget.minimum_normal_payload_bytes/0, so raising this constant raises the published event_payload_bytes floor every manifest and host document must clear. Further classes increment $overflow.

capability_refusal_key(environment, result)

@spec capability_refusal_key(:workflow | :mission, map()) :: binary()

Builds the public usage.capability_refusals key for one error envelope.

The key is "<environment>/<kind>/<reason>". A known atom stays readable, an unrecognized atom is the same one-way fingerprint rejection_class/1 uses, and a missing or non-atom field is unknown. Distinct keys are capped by capability_refusal_map_limit/0; further classes increment $overflow.

capability_refusal_map_limit()

@spec capability_refusal_map_limit() :: 2

Maximum distinct closed-class keys retained in usage.capability_refusals.

Terminal usage admission reserves this many fingerprint-length keys plus $overflow, and EventBudget.minimum_normal_payload_bytes/0 is derived with that reservation already in the projection, so raising this constant raises the published event_payload_bytes floor every manifest and host document must clear. Further classes increment $overflow.

failure_taxonomy(value)

@spec failure_taxonomy(term()) :: map()

Projects an explicit failure value to bounded, payload-free taxonomy.

Known framework categories remain readable. An application-defined category is represented only by a stable fingerprint, so repeated failures can be grouped without putting the caller's value into a public error or trace. Values without a scalar kind field produce no public taxonomy.

fingerprint(value)

@spec fingerprint(binary()) :: binary()

Returns the canonical non-reversible fingerprint for one bounded identifier.

labels?(labels)

@spec labels?(term()) :: boolean()

Returns whether a value is an already-normalized canonical-label map.

llm_provider_failure(value)

@spec llm_provider_failure(term()) :: map()

Projects an agent LLM failure to one closed, payload-free provider class.

normalize_labels(labels)

@spec normalize_labels(term()) :: {:ok, map()} | {:error, :invalid_safe_metadata}

Validates labels and fingerprints caller-defined identifier fields.

rejection_class(result)

@spec rejection_class(term()) :: map()

Projects a Lisp capability error envelope to a payload-free rejection class.

Known Kernel kind and reason atoms remain readable on canonical capability-stopped events. An unrecognized atom is retained only as a one-way fingerprint so a missed Kernel class still groups without putting the atom name on the public trace. Non-atoms, details, and messages produce no public fields.