import type { ContentPack } from './refs.js'; import type { ValidationError } from './validate.js'; /** * Every top-level key a `ContentPack` may carry. Anything else is refused. * * This list IS the contract. It is written out rather than derived from the type * because a type cannot be enumerated at runtime, and a gate that guesses its own * allowlist is the silent-pass it replaces. */ export declare const ALLOWED_PACK_KEYS: readonly ["schemaVersion", "entities", "zones", "dialogues", "quests", "abilities", "statuses", "verbs", "archetypes", "backgrounds", "itemUseEffects", "districts", "buildCatalog", "progressionTrees", "placements", "encounterAnchors", "hazardDefinitions", "itemPlacements", "entityAi", "ruleset", "ruleProfiles", "meta", "manifest", "factions", "items", "factionPresences", "pressureHotspots"]; /** * The keys whose contents can change what the simulation computes, and therefore * the ONLY keys the content hash covers. * * Paradox's shape: hash what affects the simulation, not the whole file, so a * comment or an asset path does not invalidate a pack. The exclusions are part * of the contract, not an implementation detail — `schemaVersion` is metadata, * and `buildCatalog`/`progressionTrees` are session-scoped (consumed before a * world exists; see `SESSION_SCOPED_KEYS` in intake.ts). */ export declare const SIM_AFFECTING_KEYS: readonly ["entities", "zones", "dialogues", "quests", "abilities", "statuses", "verbs", "itemUseEffects", "districts", "placements", "encounterAnchors", "hazardDefinitions"]; export type GateCheckId = 'engine-version' | 'module-ids' | 'content-hash' | 'key-allowlist'; /** One check's outcome, in diff shape: expected vs actual vs what to do. */ export type GateCheckResult = { check: GateCheckId; ok: boolean; /** Absent when the check was not applicable (e.g. no hash recorded). */ skipped?: string; expected?: string; actual?: string; message?: string; hint?: string; }; export type GateResult = { ok: boolean; checks: GateCheckResult[]; errors: ValidationError[]; advisories: ValidationError[]; /** A human-readable diff report. Empty string when everything passed. */ report: string; }; /** * What the gate needs to know about the engine it is admitting a pack into. * * Two fields are optional ON PURPOSE, and their absence is REPORTED rather than * treated as a pass. `ai-rpg-engine validate ` has no booted engine * and, because the forge writes `content-pack.json` and `manifest.json` as * separate files, usually no manifest either — so it can genuinely only run the * key allowlist. A gate that silently "passes" the three checks it never ran is * the failure mode this whole cycle exists to remove. */ export type GateContext = { /** The running engine's version, e.g. the `version` from package.json. */ engineVersion: string; /** * The module ids actually REGISTERED in the booted engine — * `engine.moduleManager.getModules().map(m => m.id)`. * * Resolution happens against reality, not against a static catalog. C0's * phantom nine exist precisely because `DEFAULT_MODULES` was a hand-maintained * list with a comment asking a human to keep it in sync. A list cannot drift * if there is no list; and this also accepts a pack that ships its OWN module * (starter-merchant's `contract-core`), which a static engine catalog would * wrongly refuse. * * Omit when no engine is booted — the module check then reports itself * unverified instead of passing or failing on nothing. */ registeredModuleIds?: readonly string[]; /** The manifest the pack claims. Omit when the caller has no manifest to check. */ manifest?: { engineVersion?: unknown; modules?: unknown; contentHash?: unknown; }; }; /** * Deterministic JSON: object keys sorted at every depth, array order preserved. * * Array order is content, not formatting — reordering zones can change which * zone a fallback picks — so it is inside the hash on purpose. */ export declare function canonicalize(value: unknown): string; /** * SHA-256 over the pack's sim-affecting subset. Absent keys are omitted (not * hashed as `undefined`), so adding an empty `quests: []` does change the hash — * an empty declaration is a claim, and the gate treats it as one. */ export declare function computeContentHash(pack: ContentPack): string; /** * Closest registered id to an unknown one, by shared stem then edit distance. * * ⚠ STATED CEILING, because the alternative is a hardcoded alias table that * would rot: this recovers `rumor-core → rumor-propagation` (shared `rumor` * stem) and does NOT recover `movement-core → traversal-core` or * `npc-ai-core → cognition-core`, which share no surface at all. Those two are * fixed at the source in world-forge rather than guessed at here. A suggestion * is a courtesy; the refusal is the contract. */ export declare function suggestModuleId(unknown: string, candidates: readonly string[]): string | undefined; /** * Run all four checks and produce a diff-style report. * * Every check runs even after one fails: an author fixing a version range should * find out about the phantom module id in the same pass, not on the next one. */ export declare function runLoadGate(pack: ContentPack, ctx: GateContext): GateResult; /** The diff-style report. Failures first, each as expected / actual / fix. */ export declare function formatGateReport(checks: readonly GateCheckResult[]): string; //# sourceMappingURL=gate.d.ts.map