/** * withCodeRifts — additive orchestration layer above the frozen security core (slices S1 + S2 + * composition observation). * * S1 DELIVERS: a single entry point that wraps the frozen `guardToolRegistry` with a MANDATORY * `operation` and returns, side by side, (a) the registry's own untouched coverage report and (b) a * separately-computed, deliberately-narrower composition-level assurance. It performs NO IO — every * input is host-supplied, exactly like the primitives it composes. * * S2 ADDS startup honesty: it refuses to return a result that would LOOK protected while protection is * incomplete. Concretely S2 adds — (1) an explicit `unknownToolPolicy` default of 'mutating' when the * caller did not specify one, so an unclassified tool is never silently downgraded to readonly; * (2) an optional `requireCoverage` that aborts construction when the registry's coverage is weaker * than required, by an EXPLICIT strength ordering; (3) weakening-override residuals recorded onto * `composition_assurance` so the trace survives into any later coverageReport consumer. S2 adds NO * enforcement at call time. * * COMPOSITION OBSERVATION: optional `onEvent` is forwarded UNCHANGED onto the guard config. * Optional `onSettledCall` is a composition-level hook: it fires EXACTLY ONCE for every SETTLED * call through the RETURNED tool table (GUARDED | PASSTHROUGH | BYPASSED × RETURNED | THREW). * Discriminant is an EXPLICIT TAG (`kind: 'settled_call'` plus route/terminal), never the absence of * a field. On the GUARDED+RETURNED arm, `outcome` is non-optional. Observation is NOT enforcement — * it does not change COMPOSITION_CALL_POLICY_COMPLETE, coverage, residuals, or whether any tool runs. * Settled-call observation holds NO ratios at runtime; pure fold helpers remain optional * host-side tools (see foldTableSettledCalls / guardedFractionAmongRoutes). Dispatch counts live * on `coverage_observed` (Half A always; Half B only when the host reports). * * TELEMETRY GAPS (honest boundaries — read before claiming "every mutation was checked"): * - onEvent does NOT emit a dedicated event for BLOCK or REQUIRE_APPROVAL; after preflight_result * those paths return blocked with no further emit. Those outcomes ARE visible via onSettledCall * on the GUARDED arm (outcome.executed === false, outcome.verdict.kind === 'BLOCK' | 'APPROVAL'). * - The GuardEvent payload carries no envelope, receipt, or fingerprint — only optional decisionId * (and action/cause/signals/…). GUARDED+RETURNED onSettledCall carries the full GuardOutcome, * including outcome.verdict.envelope where the frozen path attached one. * - onSettledCall sees ONLY calls through the table withCodeRifts returned. Host-invoked raw tools * outside that table are invisible. Never treat observation as "total operations" or "100% * enforcement coverage". * - onEvent is partial even for other branches (e.g. closed-availability stops may emit only * preflight_start; the declared type 'execution_skipped' is never emitted by the frozen core). * - onEvent still lacks envelope/receipt. onSettledCall exposes them on GUARDED+RETURNED when present. * S5: when threadReceipts is enabled (default), the composition ALSO retains the last enforced * receipt token and re-injects it as previous_receipt on the next preflight — host previousReceipt * always wins over the cursor. This is carry-forward of a token, not chain authenticity. * * S2 does NOT re-guard what the registry already fails closed on. A `forceReadonly`/`classify` * downgrade of a heuristic mutator is, under the registry's default `failOnUnguardedMutator:true`, * already a thrown `FORCE_READONLY_MUTATOR` (no report exists); when the caller passes * `failOnUnguardedMutator:false` it is already registry coverage 'BYPASSED', which fails any * `requireCoverage` above BYPASSED by the ordering alone. The composition RECORDS residuals from * registry warnings. When forceReadonly lists a tool that declared explicit mutationClass, the * registry emits `force_readonly_ignored_explicit_mutation_class:` (break-glass ignored). * * COMPOSITION_CALL_POLICY_COMPLETE (when the composition may claim product-level runtime * inescapability) requires ALL of the following — do not flip the constant when only a subset lands: * (1) Automatic binders for call shapes that ALREADY carry both sides of the change (old_string/ * new_string, edits[]) — DONE. Not the same as covering every real agent edit shape. * (2) Receipt carry-forward (S5) — composition cursor EXISTS (threadReceipts default on; host * previousReceipt overrides; advance only on enforced + receipt token; refuse under overlap). * NOT sufficient alone to flip this constant: package never self-attests chain authenticity * (consumer re-runs verifyReceiptChainLinkage), and concurrent overlap refuses to advance. * (3) A freshness-safe source of prior content for write-style calls (path + new content only) — * PURE CORE shipped (`assessFreshness` / `assessWriteStylePrior` in freshness.ts): content- * identity recompute, four outcomes, write-style requires artifact id. NOT DONE: wiring into * guardToolCall enforce path, host resolve adapter, receipt-bound optional tree hash, or * flipping COMPOSITION_CALL_POLICY_COMPLETE. * * THE TWO-SCOPE RULE (never merged): * - `registry_report` is EXACTLY what `guardToolRegistry` returned, passed through untouched. * - `composition_assurance` is the narrower PRODUCT-level statement, computed SEPARATELY. */ import type { RawTool, ProtectedTool, EnforcementCoverage, RegistryCoverageReport, ToolBinder, ToolMutationClass } from './tool-registry.js'; import type { GuardConfig, GuardEvent, GuardOutcome } from './types.js'; import type { MutatorRegister } from './atomic-profile.js'; /** Registry config fields callers may forward untouched (S1/S2 do not re-declare them). */ export type WithCodeRiftsRegistryConfig = { unknownToolPolicy?: 'mutating' | 'readonly' | 'reject'; classify?: Record; binders?: Record; forceReadonly?: string[]; failOnUnguardedMutator?: boolean; }; /** Route of a settled call through the returned table. Explicit tag values — never inferred by field absence. */ export type CallRoute = 'GUARDED' | 'PASSTHROUGH' | 'BYPASSED'; /** How a settled call finished. Explicit tag values. */ export type CallTerminal = 'RETURNED' | 'THREW'; /** * One settled call through the tool table withCodeRifts returned. * * Discriminant is the EXPLICIT `kind: 'settled_call'` plus `route` and `terminal` tags — never * "outcome present vs missing". On GUARDED+RETURNED, `outcome` is required (non-optional). * * Replaces the former `ObservedOutcome` (`{ toolName, outcome }` only for guarded returns). The old * name implied every observation carried a GuardOutcome and that only guarded tools were visible; * both were false once passthrough/threw routes ship, so the name is retired rather than overloaded. */ export type SettledCallObservation = { kind: 'settled_call'; route: 'GUARDED'; terminal: 'RETURNED'; toolName: string; outcome: GuardOutcome; } | { kind: 'settled_call'; route: 'GUARDED'; terminal: 'THREW'; toolName: string; error: unknown; } | { kind: 'settled_call'; route: 'PASSTHROUGH'; terminal: 'RETURNED'; toolName: string; result: unknown; } | { kind: 'settled_call'; route: 'PASSTHROUGH'; terminal: 'THREW'; toolName: string; error: unknown; } | { kind: 'settled_call'; route: 'BYPASSED'; terminal: 'RETURNED'; toolName: string; result: unknown; } | { kind: 'settled_call'; route: 'BYPASSED'; terminal: 'THREW'; toolName: string; error: unknown; }; /** * Counts of settled-call routes from a host-collected event list. * Names are route tags only — not "total operations" or "enforcement coverage". */ export type TableSettledCallRouteCounts = { GUARDED: number; PASSTHROUGH: number; BYPASSED: number; }; /** * Pure fold: count routes in a host-owned list of settled-call observations. * The package never accumulates these at runtime. */ export declare function foldTableSettledCalls(events: readonly SettledCallObservation[]): TableSettledCallRouteCounts; /** * Fraction of GUARDED among observed routes, or ABSENT when a ratio would be misleading. * * Returns `kind: 'absent'` (not zero) when: * - no settled calls were observed, or * - only a single route tag has a non-zero count (one-sided observation). * * Never returns a number that could be read as "0% enforcement" or "100% coverage" from partial data. * The package does not call this automatically. */ export declare function guardedFractionAmongRoutes(counts: TableSettledCallRouteCounts): { kind: 'absent'; why: 'no_settled_calls' | 'one_route_only'; } | { kind: 'present'; fraction: number; }; /** * THE PROFILE IS A VERSIONED CONTRACT, not a bag of flags (roadmap 1100, invariant #4). * * WHAT `_V1` MEANS. It names the NINE conditions this package checks at construction today, frozen * under that name forever: requireCoverage COMPLETE · requireFreshness · requireExecutionStateMatch * · requireConditionalWrite · requireCommitObservation · registry.failOnUnguardedMutator · * registry.unknownToolPolicy not 'readonly' · a resolvePriorContent resolver · executionGrant.enabled. * * WHY THE SUFFIX, and the argument is this week's own release history. 10.0.0 and 12.0.0 were MAJOR * releases for one reason: the meaning of `ENFORCING_STRICT` changed under its own name — first to * require the execution chain, then the conditional-write gate. A caller who wrote * `profile: 'ENFORCING_STRICT'` in 9.x got a different contract in 12.x without touching a line. * With a version in the name, each of those is a NEW name (`_V2`), every adopter on `_V1` keeps * exactly what they asked for, and the cost of a stricter rule drops to zero for everyone who did * not ask for it. The next tightening MUST be `_V2`; adding a condition to `_V1` reintroduces the * defect this type exists to remove. * * `'ENFORCING_STRICT'` (unsuffixed) is a DEPRECATED ALIAS that resolves to `_V1` and must resolve * to `_V1` forever. Re-pointing it at a future `_V2` would silently move every existing caller, * which is precisely the migration this design refuses. * * THE HONEST LIMIT, and it belongs on the type rather than in a changelog because this is what a * reader of the type is entitled to know: * * A profile can require CONFIGURATION at construction time. It can NEVER require EXECUTOR * BEHAVIOUR. Nine checked conditions have verified nine conditions — they have not verified that * any write was atomic, that a nonce was consumed exactly once, or that a commit happened. Those * are facts about an executor this package does not run and does not observe. A profile that * implied otherwise would be the overstatement class we spend most of our time removing. */ export type WithCodeRiftsProfile = 'ENFORCING_STRICT' | 'ENFORCING_STRICT_V1'; export type WithCodeRiftsAtomicProfile = 'ENFORCING_ATOMIC' | 'ENFORCING_ATOMIC_V1' | 'ENFORCING_ATOMIC_V2'; /** The canonical contract name. The unsuffixed alias resolves here and always will. */ export declare const PROFILE_ENFORCING_STRICT_V1: "ENFORCING_STRICT_V1"; export { PROFILE_ENFORCING_ATOMIC_V1, PROFILE_ENFORCING_ATOMIC_V2 } from './atomic-profile.js'; export type WithCodeRiftsInput = { /** Raw tool list handed to the frozen guardToolRegistry (required). */ tools: RawTool[]; /** The CodeRifts client guardToolRegistry's config.guard.client expects (required; checked at construction). */ client: GuardConfig['client']; /** * REQUIRED session-level operation — no default. Receipts bind to an operation and merge != deploy, * so a silent default would risk evaluating a deploy under merge semantics. Governs generic mutating * tools; specialised classes keep their operationForClass-derived operation. */ operation: string; /** * Opt-in profile. `ENFORCING_STRICT_V1` locks the fail-closed conjunction (requireCoverage COMPLETE, * requireFreshness, requireExecutionStateMatch true, requireConditionalWrite, requireCommitObservation, * failOnUnguardedMutator, unknownToolPolicy mutating) plus a resolvePriorContent resolver and an * enabled execution grant, and ABORTS construction on any conflicting opt-down. * `ENFORCING_STRICT` is a deprecated alias for `_V1` and behaves identically. * Absent: today's defaults (freshness/conditional-write remain opt-in). Not weakenable when set. * See WithCodeRiftsProfile for what a profile can and cannot require. */ profile?: WithCodeRiftsProfile | WithCodeRiftsAtomicProfile; /** Optional; carried through untouched. Artifact resolution is a later slice — S1 invents no use for it. */ repository?: string; /** * Optional (S2). Minimum REGISTRY-level coverage required at construction. If the registry's actual * coverage is weaker than this (by the COVERAGE_STRENGTH ordering), construction ABORTS. It does NOT * demand composition-level inescapability (unreachable while COMPOSITION_CALL_POLICY_COMPLETE is * false — binders-for-both-sides alone do not suffice; see file header) — a green construction here * is NOT a product-level runtime-enforcement guarantee. */ requireCoverage?: EnforcementCoverage; /** Optional passthrough of existing GuardToolRegistryConfig fields (forwarded as given). */ registry?: WithCodeRiftsRegistryConfig; /** * Optional. Forwarded UNCHANGED onto the guard config (config.guard.onEvent) so the frozen * guardToolCall emit path can reach it. Not wrapped, not filtered, no extra events added. * See header TELEMETRY GAPS: partial; does not carry envelope/receipt/fingerprint. * Alone does NOT unlock WARN/MONITOR — also set monitoringSinkWired: true (host assertion). */ onEvent?: (e: GuardEvent) => void; /** * Optional. Host assertion that a monitoring sink is intentionally wired. Forwarded onto * GuardConfig.monitoringSinkWired. Must agree with onEvent for CONTINUE_WITH_MONITORING. * See GuardConfig.monitoringSinkWired docs: claim, not delivery proof. */ monitoringSinkWired?: boolean; /** * Optional dedicated CWM delivery sink (callback or HTTP). Forwarded onto GuardConfig. * Distinct from onEvent. See monitoring_delivery on the CWM outcome. */ monitoringSink?: GuardConfig['monitoringSink']; monitoringSinkTimeoutMs?: GuardConfig['monitoringSinkTimeoutMs']; ackHmacKey?: GuardConfig['ackHmacKey']; /** * Optional. Invoked once per SETTLED call through the returned tool table (every route and both * terminals). Discriminated union — see SettledCallObservation. Throws and rejected promises are * swallowed so observation never changes host-visible execution. Replaces the former `onOutcome` * (guarded-only) hook. */ onSettledCall?: (o: SettledCallObservation) => void | PromiseLike; /** * Optional prior chain-receipt token (or getter). When set, ALWAYS wins over the composition * cursor for the preflight `previous_receipt` field — the host may know a prior the composition * does not. String or zero-arg getter (same shape as GuardConfig.previousReceipt). */ previousReceipt?: string | (() => string | undefined | null); /** * Automatic receipt carry-forward for this composition instance (default true). * When enabled, each enforced+receipt-verified guarded call advances a per-composition cursor * that is supplied as previous_receipt on the next preflight unless the host overrides. * * Default ON (opt-out) — unlike freshness, which is opt-in because it needs a host resolver: * threading only reuses a token the composition already observed. Set false for independent * root receipts every call. Does not verify or re-sign; does not claim chain authenticity. */ threadReceipts?: boolean; /** * Optional freshness prior resolver (opt-in). Forwarded onto GuardConfig.resolvePriorContent. * When absent: composition residual composition_freshness_not_configured; per-call basis * NOT_CONFIGURED. When present: host commitment — failures are DEGRADED, not silent opt-out. */ resolvePriorContent?: GuardConfig['resolvePriorContent']; /** Policy: write-style without ACTIVE freshness does not proceed. Default false (API opt-in). */ requireFreshness?: boolean; /** STALE_CONTEXT policy opt-out forwarded onto GuardConfig.allowStaleContext. */ allowStaleContext?: boolean; /** * Policy: write-style without host conditional_write:true does not enforce. * Default false (API opt-in). Forwarded onto GuardConfig.requireConditionalWrite. * Host residual when policy is on without host report: composition_unconditional_write_under_policy. */ requireConditionalWrite?: boolean; /** * Policy (ID842): T2 execution-time fingerprint recheck. Forwarded UNCHANGED onto * GuardConfig.requireExecutionStateMatch. Absent inherits guard@8 default true * (fail-closed). Explicit opt-down: false | 'warn'. */ requireExecutionStateMatch?: GuardConfig['requireExecutionStateMatch']; /** * Policy (ID781 T3): post-commit observation. Forwarded onto GuardConfig.requireCommitObservation. * Absent inherits default true. Explicit opt-out: false (emits commit_observation_check_disabled; * does not change enforced). Under `profile: 'ENFORCING_STRICT'`, false aborts construction * (`requireCommitObservation conflicts`) and the lock forces true. */ requireCommitObservation?: GuardConfig['requireCommitObservation']; /** * S6 auto-recheck. Default OFF. `{ maxAttempts: 1|2|3, applyFix }` — the HOST applies * the fix (guard never writes artifacts). Hard cap 3 re-preflights. */ autoRecheck?: GuardConfig['autoRecheck']; /** * S1 auto-derive. Default OFF. `true` or `{ readers?: { fs, api, db, registry } }`. * Fills before/after when the host did not supply args.artifacts. Flip-to-default * is a later, evidence-gated decision. */ autoDerive?: GuardConfig['autoDerive']; /** * S2-F2a R3. Customer-pinned executor registry `{ registry }`. Forwarded onto GuardConfig. * Default absent — no verification attempted, CAS evidence stays host_claimed (no penalty). */ executorAttestation?: GuardConfig['executorAttestation']; /** ATOMIC: explicit mutator register (replaces isWriteStyleCall for this profile only). */ mutatorRegister?: MutatorRegister; /** * THE CANONICAL V2 IDENTITY (1198). These bind the ATOMIC construction check * and the posture receipt, AND they are what goes on the authorize request — * one source for both. `executionGrant.` is a deprecated alias; * setting both with different values throws at init (resolveV2Fields). */ executorId?: string; adapterId?: string; targetUri?: string; /** * Added here in 1198. policyHash had a reader (atomic-profile.ts passes it to * the posture verifier) and no writer on this path, so the binding was * permanently undefined; tenantId and audienceHash existed on the wire side * only. All three now have the same single home as the other three. */ tenantId?: string; policyHash?: string; audienceHash?: string; casAdapter?: unknown; readBack?: (ctx: unknown) => unknown; credentialBoundary?: true | Record; /** * Opt-in CWM monitoring attestation. Forwarded onto GuardConfig. * `{ kid, signer }` — host sign(bytes), never a raw key. Absent = today's CWM (no token). */ monitoringAttestation?: GuardConfig['monitoringAttestation']; /** * Optional host system-prompt / instruction text. Forwarded onto GuardConfig. * Observation only (policy_presence). Absent = field omitted on the outcome. */ systemPrompt?: string; /** * Native execution grant (9.6.0). Default OFF (absent → 9.5.0 byte-identical). * Forwarded onto GuardConfig.executionGrant. `{ enabled: true }` makes THIS * call's authorize request a grant; resolveStateNonce is per-call ATOMIC nonce. */ executionGrant?: GuardConfig['executionGrant']; }; /** The narrower product-level statement, computed separately from the registry's own report. */ export type CompositionAssurance = { coverage: EnforcementCoverage; inescapable_runtime: boolean; residuals: string[]; /** * Whether the host supplied resolvePriorContent (composition-level claim). * Does NOT mean any given call was fresh — that is outcome.freshness per call. * Never implies inescapable_runtime. */ freshness_resolver_wired: boolean; /** * Live classification of observed tool traffic vs the returned table. * UNKNOWN_OUTSIDE_SCOPE until the host reports dispatches (Half B). * Not COMPLETE — COMPLETE on registry_report is table-truth, not agent-truth. */ readonly observed_class: import('./coverage-observed.js').CoverageObservedClass; }; export type WithCodeRiftsResult = { tools: ProtectedTool[]; /** Untouched RegistryCoverageReport — the registry's own truth (may be COMPLETE / inescapable). */ registry_report: RegistryCoverageReport; /** Product-level assurance — deliberately narrower than the registry. */ composition_assurance: CompositionAssurance; /** * Per-composition receipt cursor handle (always present). When enabled is false the getters * stay empty / skip as disabled. NOT product-truth chain evidence. */ receipt_thread: ReceiptThreadHandle; /** * Observed coverage for THIS withCodeRifts instance (the run). Half A always; * Half B via reportToolDispatch. Not a process-wide session. */ coverage_observed: import('./coverage-observed.js').CoverageObservedHandle; /** Carried through untouched from input when present (no resolution behaviour in S1/S2). */ repository?: string; }; /** * Why the composition receipt cursor did not advance after a settled guarded call. * Concurrent overlap is a first-class reason: a last-write-wins cursor under parallelism would * produce a chain that LOOKS ordered and is not — refuse and report rather than guess. */ export type ReceiptCursorSkipReason = 'concurrent_overlap' | 'not_enforced' | 'no_receipt_token' | 'threw_before_outcome'; /** * Composition-held receipt carry-forward diagnostics. * * This is a TOKEN cursor only: it does not verify signatures, linkage, or authorization. * Consumers that need product truth re-run verifyReceiptChainLinkage (and signature verify) * on an export they hold — the package never reports its own chain as authentic. */ export type ReceiptThreadHandle = { /** Automatic carry-forward enabled for this composition instance. */ enabled: boolean; /** Last token advanced into the cursor (undefined if never advanced). */ lastToken: () => string | undefined; /** * Why the most recent settled guarded call did not advance the cursor. * null means the last considered call advanced (or no call has settled yet). */ lastSkipReason: () => ReceiptCursorSkipReason | null; }; /** * The unversioned spelling still resolves to `_V1`, and always will — see the type doc above: * re-pointing it at a future `_V2` would silently move every existing caller, which is the * migration this design refuses. * * SO THIS NOTICE PROMISES NOTHING. It does not say deprecated, it does not name a removal * version, and it must not: the alias is permanent by design, and a notice announcing a removal * that will never happen is a false statement shipped to every consumer on the alias. What it * does is point at the name that carries the version, so a caller who wants the guarantee the * suffix gives can ask for it by name. * * ONCE PER PROCESS, and the flag is module-level for the same reason `policy.ts` uses one: a * per-call notice on a construction path that a host may run per request is noise, and noise is * how a line stops being read. * * The wire value is untouched. `GUARD_PROFILE_WIRE_VALUE` stays the suffix-less string that four * downstream modules compare against; this observes the spelling, it does not rewrite it. */ export declare const UNVERSIONED_PROFILE_NOTICE = "ENFORCING_STRICT is the unversioned spelling of ENFORCING_STRICT_V1; prefer the versioned name."; /** Test-only reset. Exported so a test can assert the once-per-process behaviour more than once. */ export declare function _resetUnversionedProfileNoticeForTest(): void; /** * Wrap guardToolRegistry with a mandatory operation and a separately-computed composition assurance. * Fails at CONSTRUCTION (never at first tool call) for a missing client, a missing/empty operation, an * invalid requireCoverage value, (S2) an unmet requireCoverage, or `profile: 'ENFORCING_STRICT'` plus a * conflicting opt-down / missing resolvePriorContent. Registry-thrown construction errors * propagate UNCHANGED. */ export declare function withCodeRifts(input: WithCodeRiftsInput): WithCodeRiftsResult; //# sourceMappingURL=with-coderifts.d.ts.map