agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
198 lines • 10.1 kB
TypeScript
/**
* artifacts/conformance/types — what a store presents to the battery, and what
* the battery hands back.
*
* The sibling of `hosting/conformance/types`, applied to the claim-check port
* for the same reason: five stores, five sets of doubles, and a flaw in the
* PORT's semantics would be invisible to all of them at once.
*/
import type { ArtifactRef, ArtifactScope, ArtifactStore } from '../types.js';
/**
* Every case in the battery, by name.
*
* A closed union on purpose. A harness DECLARES the cases it cannot satisfy by
* writing their names down, and a name that is a free-form string is a name
* that goes stale silently — a declaration for a case that was renamed would
* keep suppressing nothing at all, which is the same shape as the bug this
* whole battery exists to catch.
*/
export type ArtifactStoreCaseName = 'put-mints-a-ticket-head-describes-get-redeems' | 'payloads-round-trip-as-the-value-they-were-given' | 'refs-are-minted-never-derived-from-the-payload' | 'a-ref-alone-opens-nothing' | 'confusable-scopes-are-not-one-scope' | 'missing-expired-and-foreign-scope-are-one-absence' | 'expiry-is-stated-at-mint-never-sprung' | 'delete-removes-and-deleting-an-absence-is-agreement' | 'list-pages-newest-first-and-carries-no-payload' | 'awkward-scope-values-are-names-not-paths' | 'oversized-payload-is-refused-before-the-write' | 'parent-refs-are-proven-at-mint' | 'malformed-puts-are-refused-by-name' | 'a-declared-time-axis-rides-the-ticket' | 'refusals-carry-no-payload-and-no-scope' | 'digest-is-minted-over-the-payload-and-rides-the-ticket' | 'get-refuses-a-payload-that-no-longer-matches-its-digest' | 'get-stream-does-not-verify-the-digest' | 'streamed-put-round-trips-and-declares-its-bytes' | 'streaming-members-are-feature-detected';
/**
* Members a case cannot run without.
*
* `putStream` and `getStream` are OPTIONAL on the port — a store that cannot
* move a payload without holding it whole leaves them ABSENT rather than
* faking one — so a store that lacks one is not failing anything, and a case
* about it is reported `'not-applicable'` rather than passed or failed. That
* is the port's own feature-detection rule, applied to its own battery.
*/
export type ArtifactStoreMember = 'putStream' | 'getStream';
/**
* Harness hooks a case cannot run without.
*
* Each one is something no store can be asked to do through the port itself:
* move its clock, damage its own bytes, or come into being with a different
* budget. There is no portable way to do any of them, so they are the
* harness's job — and a case that needs one nobody supplied FAILS rather than
* skipping (see {@link ArtifactStoreOutcome}).
*/
export type ArtifactStoreHarnessHook = 'advanceTime' | 'corrupt' | 'boundedStore';
/**
* How the battery reaches one store.
*
* A factory rather than an instance, because most of the battery needs a store
* with NOTHING in it — a listing case that saw another case's rows would be
* asserting on somebody else's fixtures — and because a store that has been
* closed, or whose directory was removed, cannot be reset in place. One store
* per case, disposed after it, is the only shape that holds for a `Map`, a
* directory, an embedded database and a bucket at once.
*/
export interface ArtifactStoreHarness {
/** What this store is called in a report. */
readonly name: string;
/**
* A fresh store holding no artifacts. Called once per case.
*
* May be sync or async: some stores open a file, some await a connection,
* and a battery that demanded one shape would exclude half the stores it is
* here to check.
*/
createStore(): ArtifactStore | Promise<ArtifactStore>;
/**
* Release what `createStore` (or {@link boundedStore}) acquired. Called
* after every case, including the ones that failed — a store left open by a
* failing case is a handle leak that surfaces three cases later as a
* confusing second failure.
*/
disposeStore?(store: ArtifactStore): void | Promise<void>;
/**
* Move THIS store's clock forward by `ms`.
*
* Expiry is the one law that cannot be observed without time passing, and
* sleeping for it would make the battery slow enough that somebody deletes
* the case. A store built on an injectable clock implements this in one
* line; a store whose time comes from a service it does not control must
* DECLARE the cases below by name.
*/
advanceTime?(store: ArtifactStore, ms: number): void | Promise<void>;
/**
* Replace one artifact's stored PAYLOAD with different, well-formed bytes,
* behind the store's back — the artifact must still be readable, just no
* longer what was put.
*
* There is no portable way to do this — it is a poke at the file, the row,
* or the object — so it is the harness's job. It is what makes the integrity
* law observable at all: `get` re-hashes and must refuse. A store nothing
* outside it can reach must DECLARE those cases with the reason.
*/
corrupt?(store: ArtifactStore, scope: ArtifactScope, ref: ArtifactRef): void | Promise<void>;
/**
* A fresh, empty store whose per-scope byte budget is `maxBytesPerScope`.
*
* The ceiling law needs a ceiling small enough to hit in a test, and a
* budget is configuration rather than a verb — there is no way to ask for it
* through the port. A store with no configurable ceiling declares the case.
*/
boundedStore?(maxBytesPerScope: number): ArtifactStore | Promise<ArtifactStore>;
/**
* Cases this store cannot satisfy, BY NAME, each with the reason.
*
* The reason is required, and it is the point. A store may legitimately be
* unable to satisfy a case — a `Map` closed over by its factory genuinely
* cannot be corrupted from outside — and the honest way to record that is a
* sentence somebody can disagree with. A silent skip is a pass with the
* evidence removed.
*
* A declared case still RUNS. If it turns out to pass, the report says so,
* and the declaration is stale: a gate that absolves itself is worth
* catching, and so is a gate nobody needed.
*/
readonly declared?: Partial<Record<ArtifactStoreCaseName, string>>;
}
/** The helpers a case is handed, beside the store. */
export interface ArtifactConformanceKit {
/**
* A scope nothing else in this run uses. Cases address their own scope so
* two batteries pointed at one shared backend cannot read each other's rows.
*/
scope(suffix: string): ArtifactScope;
/**
* The unique token behind {@link scope}, for the cases that build their own
* scope TUPLES — the confusable pairs mean nothing if a helper rewrites the
* very fields whose spelling is under test.
*/
readonly token: string;
/**
* The STORE's own idea of now, read the only way the port exposes it: mint a
* throwaway artifact in a private scope and read the `createdAt` it stamped.
*
* A store on an injected clock and a store on the wall clock are both
* entitled to their own calendar, so a case that computed an expiry from
* `Date.now()` would state a time the store may consider the distant future
* — and then pass by never expiring anything.
*/
now(store: ArtifactStore): Promise<number>;
/** Move the store's clock. Present only where the case declared the hook. */
advance(store: ArtifactStore, ms: number): Promise<void>;
/** Damage a stored payload. Present only where the case declared the hook. */
corrupt(store: ArtifactStore, scope: ArtifactScope, ref: ArtifactRef): Promise<void>;
/** A second store with a small byte budget; disposed with the case's own. */
bounded(maxBytesPerScope: number): Promise<ArtifactStore>;
/** The harness, for a case that wants to name it in a message. */
readonly harness: ArtifactStoreHarness;
}
/** One case in the battery. */
export interface ArtifactStoreCase {
readonly name: ArtifactStoreCaseName;
/** The law it holds, in one sentence — printed beside a failure. */
readonly law: string;
/** Optional port members without which this case does not apply. */
readonly members?: readonly ArtifactStoreMember[];
/** Harness hooks without which this case cannot run at all. */
readonly harnessNeeds?: readonly ArtifactStoreHarnessHook[];
run(store: ArtifactStore, kit: ArtifactConformanceKit): Promise<void>;
}
/**
* How one case came out.
*
* - `'passed'` — the store holds the law.
* - `'not-applicable'` — the case is about an OPTIONAL member this store does
* not implement. Feature detection, not a gap.
* - `'declared'` — the store implements the member and cannot satisfy the
* case, and said so by name. `stillFails: false` means the declaration is
* stale: it passes now.
* - `'failed'` — including "needed a harness hook nobody provided and nobody
* declared", because an undeclared skip is exactly what this battery
* refuses to let look like a pass.
*/
export type ArtifactStoreOutcome = {
readonly case: ArtifactStoreCaseName;
readonly law: string;
} & ({
readonly status: 'passed';
} | {
readonly status: 'not-applicable';
readonly missing: ArtifactStoreMember;
} | {
readonly status: 'declared';
readonly reason: string;
readonly stillFails: boolean;
} | {
readonly status: 'failed';
readonly error: Error;
});
/** What one store's whole run came to. */
export interface ArtifactStoreReport {
/** The harness name. */
readonly store: string;
readonly outcomes: readonly ArtifactStoreOutcome[];
readonly passed: number;
readonly notApplicable: number;
readonly declared: number;
readonly failed: number;
/** True when nothing failed. Declarations do not make a store
* non-conformant — they make it conformant WITH STATED LIMITS, which is a
* different claim, and the report prints both. */
readonly ok: boolean;
}
//# sourceMappingURL=types.d.ts.map