/** * Render an AgentCore session history as dogwood replay-trace text (#1685). * * This module is pure: normalized events in, text out, no transport and no * filesystem. It exists separately from `./trace-fetch.ts` for two reasons. * The Bedrock AgentCore observability surface is in preview and moving, so the * thing most likely to be rewritten is the fetch, and the grammar it renders * into is the thing least likely to move. And the line grammar has two traps in * it that are worth testing byte-for-byte without a mock HTTP stack in the way. * * ## The grammar * * From the #1657 verification (§6, read from * `dogwood-language/src/interpreter/log_parse.rs` at the pinned SHA * `5063bcc2d6d6cf5024d1b0498e6cc8ef52cbcf0c`), one event per line: * * ``` * @ [scope(principal: , resource: )] * [entities(: { } [in [, …]], …)] * [request_context(: { … }, …)] * ::Action::""::(: , …) * ``` * * The three envelopes are optional and must appear in that order; the trailing * group is the logged record. Blank lines are skipped and there is no comment * syntax, so a `//` inside a value survives. * * ## The two traps * * 1. **`request_context(…)` and the logged record are different bags.** The * Cedar request is built from the first, temporal predicates match against * the second, and a field supplied to only one weakens either the Cedar * check or the temporal check while the replay still exits 0 with a verdict * that looks authoritative. So every payload group an event carries is * written to *both*, always — there is no per-event opt-out here, because an * AgentCore history has no way to express "this field is deliberately * Cedar-invisible". A decision-kind event with no payload at all is a * refusal, not a shrug: see {@link renderAgentCoreTrace}. * 2. **Actions must be fully qualified.** `AgentCore::Action::"Transfer"`, * never `Transfer` — a short name leaves the temporal predicate unmatched * while Cedar still authorizes. A bare tool name from the history is * qualified here; an already-qualified one is validated and passed through. * * ## Decoupling * * Nothing here imports the cedar lexicon, and the cedar lexicon imports nothing * from here. The contract between them is the text. The value and event types * below are deliberately shaped so an object built here is *structurally* * assignable to the cedar side's `TraceValue`/`TraceEvent` for anyone who wants * that, but neither package depends on the other to get it, and the rendering * is implemented twice on purpose. Where a choice was free — the space inside * `{ … }`, the `, ` between fields, the order of the record's own injections — * it matches `lexicons/cedar/src/dogwood/trace.ts` byte for byte, so a trace * fetched from AWS and a trace built by hand look the same to a reader and to * the parser. */ /** `Ns::Type::"id"` in value position. */ export interface AgentCoreEntityRef { readonly traceValue: "entity"; readonly uid: string; } /** `1.50` — Cedar's decimal surface form, which a JS number cannot carry. */ export interface AgentCoreDecimal { readonly traceValue: "decimal"; readonly text: string; } /** Surface text passed through untouched — the escape hatch, used sparingly. */ export interface AgentCoreRaw { readonly traceValue: "raw"; readonly text: string; } /** A tagged value: one rendered specially rather than by JS type. */ export type AgentCoreTagged = AgentCoreEntityRef | AgentCoreDecimal | AgentCoreRaw; /** Anything that can sit in a trace field, in Cedar surface forms. */ export type AgentCoreTraceValue = string | number | boolean | AgentCoreTagged | readonly AgentCoreTraceValue[] | { readonly [key: string]: AgentCoreTraceValue; }; /** A named group of fields — what one `request_context` envelope entry holds. */ export type AgentCoreFields = { readonly [key: string]: AgentCoreTraceValue; }; /** * A history that cannot become an honest trace. * * Thrown rather than worked around. Every case this covers is one where the * alternative is a trace that replays green and proves less than it looks like * it proves, which is the failure mode the whole module is arranged against. */ export declare class AgentCoreTraceError extends Error { /** 0-based position in the event list, when one event is to blame. */ readonly index?: number | undefined; constructor(message: string, /** 0-based position in the event list, when one event is to blame. */ index?: number | undefined); } /** `Ns::Type::"id"`. Validated at construction, so a short name cannot slip through. */ export declare function agentCoreEntityRef(uid: string): AgentCoreEntityRef; /** * A Cedar decimal, written as it should appear (`"1.50"`). * * `context` names the field it came from when there is one, so a payload that * cannot be represented says *where* rather than only *what*. */ export declare function agentCoreDecimal(text: string, context?: string): AgentCoreDecimal; /** Surface text, rendered verbatim. For values this module has no shape for. */ export declare function agentCoreRaw(text: string): AgentCoreRaw; /** Render one value in the Cedar surface form the trace parser reads. */ export declare function renderValue(value: AgentCoreTraceValue): string; /** `{ a: 1, b: "x" }` — a record body, braces included. */ export declare function renderFields(fields: AgentCoreFields): string; /** * One decision-relevant thing that happened in an AgentCore session, in the * shape the fetch normalizes to and the renderer reads. * * This is deliberately *not* the dogwood event shape. It is the semantic shape * of an agent session — who acted, on what, with which payload — so that a * change to the observability surface under preview churn is a change to the * mapping in `./trace-fetch.ts` and not to the grammar below. */ export interface AgentCoreSessionEvent { /** When it happened, epoch **milliseconds**. Converted to the trace's i64 here. */ readonly timeMs: number; /** The session it belongs to. Lands in the logged record as `sessionId`. */ readonly sessionId: string; /** Unique within the session. Lands in the logged record as `requestId`. */ readonly eventId: string; /** * The dogwood event kind. `request` / `response` / `error` conventionally; * the truth is whichever kinds the project's `.dwschema` marks `decision`, * and that file is not visible from here. */ readonly kind: string; /** * The tool or operation. A bare name (`"Transfer"`) is qualified with the * namespace; an already-qualified `Ns::Action::"Name"` is validated and used * as-is. Never rendered short. */ readonly action: string; /** Who acted — an actor id, or a fully qualified uid to override the type. */ readonly actor: string; /** What was acted on — a runtime/gateway id, or a fully qualified uid. */ readonly target: string; /** The call's input payload. Lands in both bags as the `input` group. */ readonly input?: AgentCoreFields; /** The call's result. Lands in both bags as the `output` group. */ readonly output?: AgentCoreFields; /** A failure's detail. Lands in both bags as the `error` group. */ readonly error?: AgentCoreFields; /** * Anything the source carried that has no field of its own. Lands in both * bags as the `attributes` group. */ readonly attributes?: AgentCoreFields; } /** How `@` is derived from `timeMs`. */ export type AgentCoreTimeOrigin = /** Epoch seconds. Absolute, comparable across sessions, large numbers. */ "epoch-seconds" /** Seconds since the earliest event, so the first line reads `@0`. */ | "relative-seconds"; /** What {@link renderAgentCoreTrace} takes. */ export interface AgentCoreTraceOptions { /** Cedar namespace for actions and entity types. Default `"AgentCore"`. */ readonly namespace?: string; /** Entity type for `actor`. Default `"Actor"`. */ readonly principalType?: string; /** Entity type for `target`. Default `"Runtime"`. */ readonly resourceType?: string; /** Default `"epoch-seconds"`. */ readonly origin?: AgentCoreTimeOrigin; /** * Kinds that produce a decision, and so build a Cedar request. Default * `["request"]`, the convention the default event schema follows. A * history-only event never becomes a Cedar request, so an absent payload on * one is not a weakening. */ readonly decisionKinds?: readonly string[]; /** Weakenings to tolerate. Empty by default — see {@link renderAgentCoreTrace}. */ readonly allow?: readonly AgentCoreTraceIssueKind[]; } /** What {@link auditAgentCoreEvents} can find. */ export type AgentCoreTraceIssueKind = /** A decision-kind event with no payload: the Cedar request carries no context. */ "no-request-context" /** Two events in one session sharing an id: `requestId` stops identifying anything. */ | "duplicate-event-id"; /** One finding against a normalized history. */ export interface AgentCoreTraceIssue { readonly kind: AgentCoreTraceIssueKind; /** 0-based position in the *sorted* event list. */ readonly index: number; readonly timeMs: number; readonly message: string; } /** * `Ns::Action::"Name"` from a bare tool name, or a qualified action validated. * * The fully-qualified requirement is the second §6 trap: a short name leaves * every temporal predicate unmatched while Cedar still authorizes, so the * replay is green and blind. */ export declare function qualifyAction(action: string, namespace: string, index?: number): string; /** `Ns::Type::"id"` from a bare id, or a qualified uid validated. */ export declare function qualifyUid(id: string, namespace: string, type: string, what: string, index?: number): string; /** * The weakening checks, applied to a normalized history. * * Everything here is something that makes a replay *weaker* rather than * something that makes it fail, which is exactly the class a green replay run * hides. Structural malformation throws instead: see {@link AgentCoreTraceError}. */ export declare function auditAgentCoreEvents(events: readonly AgentCoreSessionEvent[], options?: AgentCoreTraceOptions): AgentCoreTraceIssue[]; /** One line's worth: the envelopes and the logged record, fully resolved. */ export interface AgentCoreTraceLine { readonly timestamp: number; readonly action: string; readonly kind: string; readonly scope: { readonly principal: string; readonly resource: string; }; readonly requestContext: AgentCoreFields; readonly record: AgentCoreFields; } /** * Resolve one normalized event into the envelopes and the logged record. * * Both bags get every payload group; the record additionally gets the event * schema's own injections (`callerPrincipal`, `callerResource`, `sessionId`, * `requestId`), which are never part of the Cedar request. Field order matches * the §6 example line: groups first, then the injections. */ export declare function toTraceLine(event: AgentCoreSessionEvent, timestamp: number, options?: AgentCoreTraceOptions, index?: number): AgentCoreTraceLine; /** One line as the trace parser reads it. Never ends in a newline. */ export declare function renderTraceLine(line: AgentCoreTraceLine): string; /** A rendered trace, and what the audit tolerated on the way. */ export interface AgentCoreTrace { /** The text, one event per line, newline-terminated. */ readonly text: string; /** The resolved lines, in render order. */ readonly lines: readonly AgentCoreTraceLine[]; /** Findings the caller allowed. Empty unless `allow` named something. */ readonly issues: readonly AgentCoreTraceIssue[]; } /** * Render a normalized AgentCore history as dogwood trace text. * * The inversion of the §6 traps: every payload group lands in both bags, every * action comes out fully qualified, the history is ordered the way the * interpreter reads it, and a history that would produce a weakened trace * throws instead. Naming a kind in `allow` is how a caller says the weakening * is the point — the alternative was a trace that replays green and proves * less than it looks like it proves. */ export declare function renderAgentCoreTrace(events: readonly AgentCoreSessionEvent[], options?: AgentCoreTraceOptions): AgentCoreTrace; //# sourceMappingURL=trace-render.d.ts.map