/**
 * Shared payload preparation for `create_entity` / `update_entity` across
 * the MCP surface ([src/mcp/tools.ts](../mcp/tools.ts)) and the agent surface
 * ([src/agent/tools/graph-tools.ts](../agent/tools/graph-tools.ts)).
 *
 * Owns:
 *   - Actor identity build (`buildActor`).
 *   - Base-field auto-stamping for create (`stampCreateBaseFields`).
 *   - Person.status synonym normalization (`normalizePersonStatus`).
 *   - Two composers (`prepareCreatePayload`, `prepareUpdatePayload`) that
 *     return ready-to-pass options for `graph.create` / `graph.update`.
 *
 * Does NOT own surface-specific concerns: MCP keeps its allowlist check,
 * `withAgentActivity` chip wiring, `emitEntityChange` broadcast, and rich
 * diagnostics; the agent keeps its content sanitiser, workspace dedup
 * warning, eager-skill permission gate, user_handle bridge checks, and
 * `onMutation` / `onAgentActivity` callbacks.
 *
 * Errors from `graph.update` — `SilentDropError`, `DroppedAssetError`,
 * `BroadcastDisabledLiveClientsError`, `CommitFailedDirtyError`, etc. —
 * propagate through the composers unchanged; callers map them to their
 * surface's error format. (The old `LiveEditorPendingError` live-typing
 * refusal was retired with live-merge: server-writes now CRDT-merge into the
 * live Y.Doc rather than being blocked.)
 */
import type { AuthUser } from './auth-service.js';
import type { GitUser } from './git.js';
import type { CreateEntityOptions, UpdateEntityOptions, EntityFile } from '../core/graph.js';
import type { EntityType } from '../core/validation.js';
import type { BodyWriteResult } from '../server/body-write-coordinator.js';
/**
 * Canonical body-revision hash (SHA-256 hex) — the `baseRevision` precondition
 * format BodyWriteCoordinator checks inside the per-doc lock. Agent write tools
 * (patch_entity, create_artifact) pass `bodyRevisionHash(currentBody)` so a
 * racing concurrent write is rejected (StaleBaseRevisionError) instead of
 * silently clobbered. Matches `sha256OfBody` in graph.ts / body-write-coordinator.
 */
export declare function bodyRevisionHash(body: string): string;
/**
 * Person.status accepts only `current` | `former` per PersonSchema, but
 * real-world payloads from external agents carry many synonyms. Map them
 * to canonical before validation rejects the call. Unknown values fall
 * through unchanged so validation surfaces them loudly.
 *
 * On create: defaults missing status to `current`.
 * On update: only normalises when status is supplied — don't fabricate a
 * field the caller didn't ask to change.
 */
export declare const PERSON_STATUS_ALIASES: Record<string, 'current' | 'former'>;
/**
 * Build the git-author / audit-trail identity for an authenticated session.
 * `handle` is the canonical id stamped into `created_by` / `updated_by` and
 * used as the git author; falls back to `user-<id>` if a token from before
 * the handle backfill is still in use.
 *
 * Surfaces that may run without an authenticated user (CLI, server-only
 * automations, tests) should keep their existing orchestrator `GitUser` and
 * pass it directly to the composers — `buildActor` requires an `AuthUser`.
 */
export declare function buildActor(user: AuthUser): GitUser;
/**
 * EntityBaseSchema requires entity_id, created_at, updated_at, created_by,
 * and updated_by. External callers (Claude Desktop, Cursor, the agent)
 * typically pass only type-specific data plus the top-level entity_id, so
 * the helper seeds the rest rather than forcing every caller to know the
 * convention. Caller-supplied values for created_at / created_by / etc.
 * win; entity_id is always overwritten to match the top-level arg (it is
 * the lookup key and cannot diverge from the filename).
 */
export declare function stampCreateBaseFields(data: Record<string, unknown>, entityId: string, actorHandle: string): Record<string, unknown>;
export declare function normalizePersonStatus(data: Record<string, unknown>, isCreate: boolean): Record<string, unknown>;
/** Minimal workspace surface the ref validator needs: resolve an entity id to
 *  its type, workspace-wide. `WorkspaceManager.findEntityById` satisfies this. */
export interface RefResolver {
    findEntityById(entityId: string): {
        entityType: string;
    } | null;
}
/** Optional surface to validate an account/user reference (`[[user:<id>]]`).
 *  When supplied, a ref to a non-existent account is rejected; when omitted,
 *  well-formed user refs pass (mirrors the forward-ref tolerance for entities).
 *  `AuthService.getUserById` satisfies this. */
export interface UserRefResolver {
    userExists(userId: string): boolean;
}
/** Thrown when a reference field resolves to the wrong entity type (e.g. a
 *  task `assignee` pointing at a deal/org), or a user ref is used where the
 *  field expects a non-person entity. Callers map it to their surface's
 *  error envelope (HTTP 400, MCP fail, agent `{ success: false }`). */
export declare class EntityRefError extends Error {
    readonly field: string;
    readonly expectedType: string;
    readonly actualType: string;
    readonly target: string;
    readonly code = "ASSIGNEE_NOT_PERSON";
    constructor(field: string, expectedType: string, actualType: string, target: string);
}
/** Thrown when an account reference (`[[user:<id>]]`) names an account that
 *  doesn't exist in this workspace. Only raised when a {@link UserRefResolver}
 *  is supplied to {@link assertEntityRefs}. */
export declare class UnknownUserRefError extends Error {
    readonly field: string;
    readonly userId: string;
    readonly code = "UNKNOWN_USER_REF";
    constructor(field: string, userId: string);
}
/**
 * Enforce `refType` field scoping for an entity create/update payload. For
 * each built-in ref field present on `data`, parse the reference and:
 *   - ENTITY ref: resolve it and throw {@link EntityRefError} when it resolves
 *     to a different entity type. Unresolved (forward-ref) and empty pass.
 *   - USER ref (`[[user:<id>]]`): accepted only where the field expects a
 *     `person` (a user IS a person-identity). With a {@link UserRefResolver}
 *     supplied, a ref to a non-existent account throws {@link UnknownUserRefError}.
 * No-op for types without ref fields (everything except `task` today).
 *
 * `data` may be a partial update payload — only fields actually present are
 * checked, so a content-only or unrelated-field update never trips this.
 */
export declare function assertEntityRefs(resolver: RefResolver, entityType: string, data: Record<string, unknown> | undefined, userResolver?: UserRefResolver): void;
/**
 * Thrown when a write to an `artifact` entry's body isn't a valid ArtifactBundle.
 * Agents kept reaching for create_entity / update_entity / patch_entity with a
 * markdown (or hand-assembled, incomplete) body, which then fails to render on
 * the canvas. The message steers them to the right tool + the exact bundle shape.
 */
export declare class ArtifactBodyError extends Error {
    readonly reasons: string[];
    constructor(reasons: string[]);
}
/**
 * Guard the `artifact` body on every server-side write (create/update/patch) so
 * a markdown or malformed body never lands on disk to fail at render time.
 * No-op for non-artifact types and for frontmatter-only writes (content omitted).
 * The valid create_artifact path passes a serialized, already-validated bundle,
 * so it sails through untouched.
 */
export declare function assertValidArtifactBody(entityType: string, content: string | undefined): void;
export interface PrepareCreateInput {
    entityType: EntityType;
    entityId: string;
    data: Record<string, unknown>;
    content?: string;
    folderPath?: string;
    commitMessage?: string;
}
/**
 * Compose the options for `graph.create`. Stamps base fields, mirrors
 * `entity_id` from the top-level arg into frontmatter, normalises
 * `person.status` synonyms, and threads the actor through as the git
 * commit author. The caller still owns access checks, allowlist
 * enforcement, content sanitisation, and broadcast/chip wiring.
 */
export declare function prepareCreatePayload(input: PrepareCreateInput, actor: GitUser): CreateEntityOptions;
export interface PrepareUpdateInput {
    entityType: EntityType;
    entityId: string;
    data?: Record<string, unknown>;
    content?: string;
    commitMessage?: string;
    /** Optimistic-concurrency precondition (SHA-256 hex of the body the caller
     *  read). Checked inside the per-doc lock; a mismatch throws
     *  StaleBaseRevisionError so a racing write loses loudly, not silently. */
    baseRevision?: string;
}
/**
 * Compose the options for `graph.update`. Mirrors `entity_id` from the
 * top-level arg into frontmatter (defensive against callers that pass a
 * disagreeing value), normalises `person.status` synonyms when status is
 * supplied, and declares `source: 'server-write'` whenever content is
 * present so the body-write path engages the per-doc lock + live-editor
 * guard. `graph.update` re-stamps `updated_at` / `updated_by` itself.
 */
export declare function prepareUpdatePayload(input: PrepareUpdateInput, actor: GitUser): UpdateEntityOptions;
/**
 * Thrown when an update wrote but produced no on-disk diff despite the
 * caller intending a change — i.e. the validator dropped unknown fields or
 * the supplied content matched the existing body. Carries the entity coords
 * and which inputs were ignored so callers can map it to their surface's
 * failure envelope (MCP `fail`, agent `{ success: false }`).
 */
export declare class SilentDropError extends Error {
    readonly entityType: string;
    readonly entityId: string;
    readonly dropped: string[];
    constructor(entityType: string, entityId: string, dropped: string[]);
}
/**
 * Thrown when a whole-body `update_entity` would remove embedded asset
 * references (image/attachment embeds: `![[Foo.png]]` transclusions or
 * `![alt](/api/assets/…)` markdown images) that are present in the entity's
 * CURRENT body but absent from the submitted content.
 *
 * This is the guard for the silent-image-loss incident: `get_entity` reads
 * the last-persisted disk body, which can lag the live editor (a user's
 * freshly-placed image lives in the Y.Doc before autosave commits it). An
 * agent that fetched stale content, edited text, and resent the whole body
 * would drop those embeds — and because asset binaries live outside git,
 * the lost references aren't recoverable from history. We refuse the write
 * (no disk mutation happens) unless the caller explicitly opts in.
 *
 * Carries the dropped references so callers can map it to their surface's
 * conflict envelope and tell the agent exactly what to preserve.
 */
export declare class DroppedAssetError extends Error {
    readonly entityType: string;
    readonly entityId: string;
    readonly dropped: string[];
    constructor(entityType: string, entityId: string, dropped: string[]);
}
/** Minimal graph surface diagnosedUpdate needs. */
export interface DiagnosedUpdateGraph {
    get(entityType: EntityType, entityId: string): EntityFile;
    update(options: UpdateEntityOptions): Promise<EntityFile>;
    /** Live editor body when a seeded Y.Doc is open, else null. Optional so
     *  coordinator-less test/CLI graphs satisfy the interface; the asset-drop
     *  guard falls back to the on-disk body when it's absent. */
    getLiveBody?(entityType: EntityType, entityId: string): string | null;
}
export interface DiagnosedUpdateResult {
    entity: EntityFile;
    frontmatter_changed: boolean;
    content_changed: boolean;
    noop: boolean;
    /** Post-commit broadcast disposition (null on the non-coordinator path). */
    broadcast: BodyWriteResult['broadcast'] | null;
}
/**
 * Run graph.update with the c6ef9297 diagnostic contract: snapshot before,
 * update, then report whether frontmatter / content actually changed.
 * Throws {@link SilentDropError} when the caller intended a change that
 * produced no diff. Captures the body-write broadcast disposition.
 *
 * `inputs` are the caller's RAW intent (pre-payload-prep) — used only to
 * decide whether a no-op was actually a silent drop. graph.get may throw
 * (entity not found); callers' existing try/catch surfaces it.
 */
export declare function diagnosedUpdate(graph: DiagnosedUpdateGraph, options: UpdateEntityOptions, inputs: {
    data?: Record<string, unknown>;
    content?: string;
}, guard?: {
    allowAssetRemoval?: boolean;
}): Promise<DiagnosedUpdateResult>;
/** Graph surface {@link applyPatchEntity} needs — diagnosedUpdate's plus a
 *  required `getLiveBody` (the real Graph always implements it). */
export interface PatchEntityGraph extends DiagnosedUpdateGraph {
    getLiveBody(entityType: EntityType, entityId: string): string | null;
}
export interface ApplyPatchInput {
    entityType: EntityType;
    entityId: string;
    /** Literal (non-regex) substring to replace. */
    find: string;
    replace: string;
    /** When set, the match count must equal this or the patch is rejected
     *  AMBIGUOUS (guards against an unintended broad replace). */
    expectedCount?: number;
    commitMessage?: string;
}
/** Discriminated result of {@link applyPatchEntity}. Each surface maps it to
 *  its own response envelope (MCP `ok`, agent `wrapToolResult`). */
export type PatchEntityOutcome = {
    status: 'no_match';
} | {
    status: 'ambiguous';
    occurrences: number;
} | {
    status: 'conflict';
    attempts: number;
} | {
    status: 'applied';
    entity: EntityFile;
    occurrences: number;
    frontmatter_changed: boolean;
    content_changed: boolean;
    noop: boolean;
    broadcast: BodyWriteResult['broadcast'] | null;
};
/**
 * Apply a literal substring find/replace to an entry body under optimistic
 * concurrency, shared by the MCP + in-app patch_entity tools.
 *
 * - Reads the LIVE editor body (falling back to disk) for the find/replace, so
 *   a patch never reintroduces the stale-read problem or drops a user's
 *   freshly-typed/placed content.
 * - Derives `baseRevision` from the DISK body (`entity.document.content`) — the
 *   coordinator checks the precondition against disk, so hashing the live body
 *   would mismatch on every attempt when an editor has unsaved edits and
 *   dead-end the retry loop at a spurious conflict. (Same rule as
 *   `graph.appendDatasetRows`.)
 * - On a stale-base conflict (a concurrent write landed since the read) it
 *   re-reads and re-applies, up to {@link MAX_PATCH_ATTEMPTS}, so sibling
 *   patches fired in one agent turn compose instead of last-write-wins clobber.
 *
 * `DroppedAssetError` and any other error propagate to the caller's existing
 * catch. The caller owns activity chips, broadcast emit, and the envelope.
 */
export declare function applyPatchEntity(graph: PatchEntityGraph, actor: GitUser, input: ApplyPatchInput): Promise<PatchEntityOutcome>;
