/** * Manifest identity validation — the entry id, the linked task ids and the * file reference of a `pipeline_manifest` entry (T12829). * * Before this module `cleo manifest append` stored whatever it was given: a * `--task` of `T99999` (no such task), a 900-character JSON blob, or * `../../x`. The shorthand builder turns `--task` into both the entry id and * the `file` reference (`.cleo/agent-outputs/--.md`), and * `cleo manifest show` reads `file` relative to the project root. So the blob * became an unreadable path (ENAMETOOLONG), and a `../` task made `show` read * a file outside the project. Nothing is written to that path; the defect is * an unvalidated identity that later flows into a filesystem read. * * Every check here is pure except {@link findMissingLinkedTasks}, which reads * the task store. Append rejects a problem before writing; show refuses a * malformed id or unsafe file before touching the filesystem; `cleo doctor * manifest-rows` reports rows stored before these checks existed. * * @module * @task T12829 */ /** Longest accepted manifest entry id. */ export declare const MANIFEST_ENTRY_ID_MAX_LENGTH = 200; /** * Accepted manifest entry id characters: an alphanumeric first character, * then alphanumerics and `.`, `_`, `:`, `@`, `+`, `-`. No path separator, * whitespace or control character can appear, so an id can never name a * path outside the directory it is joined onto. */ export declare const MANIFEST_ENTRY_ID_PATTERN: RegExp; /** Longest accepted `file` reference. */ export declare const MANIFEST_FILE_MAX_LENGTH = 1024; /** Longest accepted single path segment of a `file` reference (filesystem NAME_MAX). */ export declare const MANIFEST_FILE_SEGMENT_MAX_BYTES = 255; /** * Whether `value` is acceptable as a manifest linked task id: the canonical * `T` shape, or a structured `T…` id CLEO mints and stores * (`isStorableTaskId`, at most 64 characters, no whitespace or separators). * A JSON blob, a path, or an over-long value is never accepted. * * @param value - Candidate task id. * @returns `true` when the value can name a task. */ export declare function isManifestTaskId(value: unknown): value is string; /** One identity problem found in a manifest entry. */ export interface ManifestIdentityIssue { /** The entry field at fault. */ field: 'id' | 'linked_tasks' | 'file'; /** `E_VALIDATION` for a malformed value; `E_NOT_FOUND` for a well-formed task id with no task. */ code: 'E_VALIDATION' | 'E_NOT_FOUND'; /** The offending value, truncated to 120 characters for display. */ value: string; /** What is wrong. */ message: string; } /** * Why a manifest entry id is unacceptable, or `null` when it is fine. * * @param id - Candidate entry id. * @returns A reason, or `null`. * @example * ```ts * manifestEntryIdProblem('T123-implementation-20260929'); // null * manifestEntryIdProblem('../../x'); // 'must start with ...' * ``` */ export declare function manifestEntryIdProblem(id: unknown): string | null; /** * Why a manifest `file` reference is unsafe, or `null` when it is fine. * * Lexical checks only: an absolute, drive, UNC or backslash path is refused, * and the reference joined onto `root` must stay inside `root`. A symbolic * link inside the project can still point outside it, so every reader goes * through {@link resolveContainedFile} / {@link readContainedFile}, which * resolve real paths. URI references are left to `show`, which accepts only * `cleo://docs/`. * * @param file - Candidate `file` value. * @param root - Directory the reference is resolved against. * @returns A reason, or `null`. */ export declare function manifestFileProblem(file: unknown, root: string): string | null; /** * Format checks for a manifest entry's id, linked task ids and file reference * (an absent `file` is not checked). Pure: no store access. Pair with {@link findMissingLinkedTasks} to check * that the linked tasks exist. * * @param entry - The entry's identity fields. * @param root - Directory `file` is resolved against (the project root). * @returns Every problem found; empty when the entry is well formed. */ export declare function manifestIdentityIssues(entry: { id?: unknown; linked_tasks?: unknown; file?: unknown; }, root: string): ManifestIdentityIssue[]; /** * The well-formed linked task ids that name no task in the project store * (archived tasks count as existing). * * @param taskIds - Linked task ids; malformed ones are skipped (format checks report them). * @param projectRoot - Project whose task store is consulted. * @returns The ids with no task, in input order, without duplicates. */ export declare function findMissingLinkedTasks(taskIds: readonly string[], projectRoot: string): Promise; /** Outcome of resolving a manifest `file` reference against a directory on disk. */ export type ContainedFileResolution = /** The reference names an existing path whose real location is inside the real root. */ { status: 'ok'; realPath: string; } /** Nothing exists at the reference (including a dangling symbolic link). Never read. */ | { status: 'not-found'; } /** The reference is malformed or resolves outside the root. Never read. */ | { status: 'unsafe'; reason: string; }; /** Outcome of reading a manifest `file` reference; `ok` carries the content. */ export type ContainedFileRead = { status: 'ok'; realPath: string; content: string; } | { status: 'not-found'; } | { status: 'unsafe'; reason: string; }; /** * Resolve a manifest `file` reference to its real path, requiring it to stay * inside `root` after every symbolic link is followed (T12829). * * {@link manifestFileProblem} is lexical: `link/secret.txt` passes when * `link` is an in-project symlink to a directory outside the project. This * resolves both the root and the target with `realpathSync` and requires the * real target under the real root. A missing target (or a dangling link) is * `not-found` and must not be read; a URI reference is `unsafe` here because * it is not a filesystem path. * * @param root - Directory the reference is resolved against. * @param file - The stored `file` reference. * @returns The resolution; only `ok` may be read, and only via `realPath`. * @throws The underlying filesystem error for anything other than not-found or a link loop. * @example * ```ts * resolveContainedFile('/proj', 'link/secret.txt'); // { status: 'unsafe', ... } when link -> ../outside * ``` */ export declare function resolveContainedFile(root: string, file: unknown): ContainedFileResolution; /** * Read a manifest `file` reference as UTF-8 text, only when its real path is * inside `root` (see {@link resolveContainedFile}). * * The resolved real path is opened once, without following a final symbolic * link and without blocking on a FIFO, and the content is read through that * file descriptor. After opening, the path is re-resolved and must still name * the same inode, so a component swapped for a link between the check and the * open is refused rather than read. * * @param root - Directory the reference is resolved against. * @param file - The stored `file` reference. * @returns The content, or why it was not read. * @throws The underlying filesystem error for failures other than not-found * (a directory throws `EISDIR`). */ export declare function readContainedFile(root: string, file: unknown): ContainedFileRead; //# sourceMappingURL=manifest-identity.d.ts.map