import type { Stats } from "node:fs"; import { type FileLockOptions } from "../config/file-lock"; import type { SkillActiveEntry } from "../skill-state/active-state"; import { type AuditEntry, type CanonicalGjcWorkflowSkill, type WorkflowStateMutationOwner } from "../skill-state/workflow-state-contract"; /** * Sole sanctioned project `.gjc/**` writer module (gate G1). * * All native `.gjc/**` filesystem mutations must route through these primitives. * The primitives validate project `.gjc/**` ownership, create parent directories, * and emit workflow receipts or audit entries where applicable by the caller's * supplied mutation context. No lockfiles are used; isolation is by atomic rename, * append, O_EXCL creates, conditional deletes, per-entry active-state files, * and derived active-state snapshots. * Transaction journals are per mutation id under the session state transactions directory; * they are recovery evidence only, never global locks or waiters, so stale * journals do not block unrelated state reads or writes. */ export type WriterCategory = "state" | "artifact" | "ledger" | "log" | "report" | "agents" | "prune" | "force" | "transaction"; export interface StateWriterReceiptContext { cwd?: string; skill: CanonicalGjcWorkflowSkill; owner: WorkflowStateMutationOwner; command: string; sessionId: string; mutationId?: string; nowIso?: string; verb?: string; fromPhase?: string; toPhase?: string; forced?: boolean; } export interface StateWriterAuditContext { cwd?: string; sessionId?: string; category: WriterCategory; verb: string; owner: WorkflowStateMutationOwner; skill?: CanonicalGjcWorkflowSkill | string; mutationId?: string; fromPhase?: string; toPhase?: string; forced?: boolean; } export interface WorkflowEnvelopeIntegrityMismatch { path: string; expected: string; actual: string; } export interface WorkflowTransactionJournal { version: 1; mutation_id: string; status: "pending" | "committed"; created_at: string; updated_at: string; caller?: CanonicalGjcWorkflowSkill; callee?: CanonicalGjcWorkflowSkill; paths: string[]; steps: string[]; } export type StateWritePolicy = "source" | "cache"; export interface GuardedStateWriterOptions extends StateWriterOptions { policy: StateWritePolicy; expectedRevision?: number; sourceRevision?: number; } export type GuardedWriteResult = { path: string; written: true; revision: number; stamped: unknown; } | { path: string; written: false; reason: "stale-skip"; revision: number; }; export interface GuardedStateWriteReceipt { path: string; revision: number; stamped: unknown; } export declare function guardedStateWriteReceipt(result: GuardedWriteResult): GuardedStateWriteReceipt | undefined; export interface StateWriterOptions { cwd?: string; receipt?: StateWriterReceiptContext; audit?: StateWriterAuditContext; sourceRevision?: number; /** Advance a cache source revision under the target lock when the value carries none. */ advanceSourceRevision?: boolean; /** * Cross-process lock tuning for read-modify-write paths that route through * `withWorkflowStateLock` / `updateJsonAtomic`. Omit for the hardened * `withFileLock` defaults. */ lock?: FileLockOptions; /** * Caller already holds the workflow state lock for this target path (via * `withWorkflowStateLock`). Skip re-acquisition to avoid self-deadlock. */ lockHeld?: boolean; } export declare class StateWriteConflictError extends Error { readonly path: string; readonly expectedRevision: number; readonly persistedRevision: number; constructor(path: string, expectedRevision: number, persistedRevision: number); } export interface DeleteIfOwnedOptions extends StateWriterOptions { predicate?: (current: unknown) => boolean | Promise; } export interface DeleteResult { path: string; deleted: boolean; } export interface ActiveSessionScope { sessionId?: string; } export interface ActiveEntryWriteResult { entryPath: string; snapshotPath: string; } export interface HardPruneSelectorContext { path: string; value: unknown; } export interface GenericHardPruneTarget { path: string; category: WriterCategory | string; } export interface GenericHardPruneSelectorContext { path: string; category: WriterCategory | string; stat: Stats; readJson: () => Promise; } export type GenericHardPruneSelector = (context: GenericHardPruneSelectorContext) => boolean | Promise; export interface ForceOverwriteOptions extends StateWriterOptions { raw?: boolean; } export type HardPruneSelector = (context: HardPruneSelectorContext) => boolean | Promise; export declare class AlreadyExistsError extends Error { readonly path: string; constructor(path: string); } export type StrictMutationReadResult = { kind: "absent"; } | { kind: "corrupt"; error: string; } | { kind: "valid"; value: Record; }; export declare function readExistingStateForMutation(filePath: string): Promise; export declare function workflowEnvelopeContentSha256(value: unknown): string; export declare function stampWorkflowEnvelopeChecksum(value: T, filePath: string, computedAt?: string): T; export declare function detectWorkflowEnvelopeIntegrityMismatch(filePath: string): Promise; type ActiveStateCacheInvalidator = (cwd?: string, sessionId?: string) => void; export declare function setActiveStateCacheInvalidator(invalidator: ActiveStateCacheInvalidator): void; export declare function persistedStateRevision(value: unknown): number; export declare function matchesGuardedStateWriteReceipt(current: unknown, receipt: GuardedStateWriteReceipt): boolean; export declare function writeGuardedJsonAtomic(targetPath: string, value: unknown, options: GuardedStateWriterOptions): Promise; export declare function writeGuardedWorkflowEnvelopeAtomic(targetPath: string, value: unknown, options: GuardedStateWriterOptions): Promise; export declare function writeJsonAtomic(targetPath: string, value: unknown, options?: StateWriterOptions): Promise; export declare function writeWorkflowEnvelopeAtomic(targetPath: string, value: unknown, options?: StateWriterOptions): Promise; export declare function writeTextAtomic(targetPath: string, text: string, options?: StateWriterOptions): Promise; /** * Serialize a read-modify-write (or any multi-step mutation) against concurrent * writers of the same `.gjc/**` target. Uses the cross-process directory lock * from `withFileLock`, keyed on the resolved file path, so separate CLI/agent * processes (e.g. team-mode workers) cannot interleave one writer's read with * another writer's write and silently drop the first mutation (issue #646). * * The lock is advisory: it only protects callers that route through it, so every * read-modify-write of a given file MUST acquire this lock for the same resolved * path. `atomicWrite`'s temp-file + rename crash-atomicity is preserved; this * layers concurrency-atomicity on top without weakening it. */ export declare function withWorkflowStateLock(targetPath: string, fn: () => Promise, options?: StateWriterOptions): Promise; export declare function updateJsonAtomic(targetPath: string, mutator: (current: T | undefined) => T | Promise, options?: StateWriterOptions): Promise; export declare function appendJsonl(targetPath: string, entry: unknown, options?: StateWriterOptions): Promise; export interface AppendJsonlIdempotentOptions extends StateWriterOptions { /** * Identity key for an entry. Two entries that produce the same non-`undefined` * key are duplicates, so only the first is appended. Return `undefined` to opt a * candidate out of dedup (it is always appended). Use `key` for the common case * where identity reduces to a single string. */ key?: (entry: unknown) => string | undefined; /** * Equivalence predicate: return `true` when `existing` already represents * `candidate`, suppressing the append. Use when identity cannot be reduced to a * single string key. When both `key` and `equals` are supplied, `equals` wins. */ equals?: (candidate: unknown, existing: unknown) => boolean; } export interface AppendJsonlIdempotentResult { path: string; /** `true` when the entry was written; `false` when an equivalent entry already existed. */ appended: boolean; /** The pre-existing entry that suppressed the append, when `appended` is `false`. */ duplicate?: unknown; } /** * Append `entry` to a JSONL file only when no equivalent entry already exists — * the shared idempotent append primitive (issue #660). * * `appendJsonl` is a pure append with no dedup, so every recurring "duplicate * ledger row" bug (#638, #643, #645) had to be patched with bespoke per-call-site * guards. This primitive centralizes the read-check-append cycle: a caller * declares identity once via `key` or `equals` instead of re-deriving the lookup * at each site. * * The read-then-append is serialized through the same cross-process workflow lock * as `updateJsonAtomic`, so two concurrent idempotent appends cannot both observe * "no duplicate" and both write (the #646 TOCTOU that a plain `appendJsonl` * preceded by a manual existence check is still exposed to). * * Scope note: this dedups the *append* only. Call sites whose idempotency must * also skip a coupled mutation — e.g. the plan/state rewrite in #643/#645 — still * need a whole-operation guard; this primitive is the ledger-level half of that. */ export declare function appendJsonlIdempotent(targetPath: string, entry: unknown, options: AppendJsonlIdempotentOptions): Promise; export declare function appendText(targetPath: string, text: string, options?: StateWriterOptions): Promise; export declare function createJsonNoClobber(targetPath: string, value: unknown, options?: StateWriterOptions): Promise; export declare function deleteIfOwned(targetPath: string, predicateOrOptions?: ((current: unknown) => boolean | Promise) | DeleteIfOwnedOptions): Promise; export declare function removeFileAudited(targetPath: string, options?: StateWriterOptions): Promise; /** * Active entry files under `.gjc/_session-{id}/state/active/.json` are authoritative. The * adjacent `skill-active-state.json` file is only a derived cache rebuilt from * those entries, so concurrent snapshot rebuilds can race without losing any * writer's per-skill state. */ export declare function writeActiveEntry(cwd: string, sessionScope: string | ActiveSessionScope | undefined, skill: string, entry: SkillActiveEntry, options?: StateWriterOptions): Promise; export declare function removeActiveEntry(cwd: string, sessionScope: string | ActiveSessionScope | undefined, skill: string, options?: StateWriterOptions): Promise; export declare function readActiveEntries(cwd: string, sessionScope?: string | ActiveSessionScope): Promise; export declare function rebuildActiveSnapshot(cwd: string, sessionScope?: string | ActiveSessionScope, options?: StateWriterOptions): Promise; export declare function mergeActiveState(cwd: string, sessionScope: string | ActiveSessionScope | undefined, skill: string, entry: SkillActiveEntry, options?: StateWriterOptions): Promise; export declare function writeArtifact(targetPath: string, content: string, options?: StateWriterOptions): Promise; export declare function writeReport(targetPath: string, content: string, options?: StateWriterOptions): Promise; export declare function writeLogJsonl(targetPath: string, entry: unknown, options?: StateWriterOptions): Promise; export declare function softDelete(targetPath: string, meta: Record, options?: StateWriterOptions): Promise; export declare function hardPruneJson(targetPaths: readonly string[], selector: HardPruneSelector, options?: StateWriterOptions): Promise; export declare function hardPrune(targets: readonly GenericHardPruneTarget[], selector: GenericHardPruneSelector, options?: StateWriterOptions): Promise; export declare function forceOverwrite(targetPath: string, rawValue: unknown, options?: ForceOverwriteOptions): Promise; export declare function appendAuditEntry(cwd: string, sessionIdOrEntry: string | AuditEntry, maybeEntry?: AuditEntry): Promise; export declare function readWorkflowTransactionJournal(cwd: string, sessionId: string, mutationId: string): Promise; export declare function beginWorkflowTransactionJournal(input: { cwd: string; sessionId: string; mutationId: string; caller?: CanonicalGjcWorkflowSkill; callee?: CanonicalGjcWorkflowSkill; paths: string[]; }): Promise; export declare function updateWorkflowTransactionJournal(cwd: string, sessionId: string, mutationId: string, patch: Partial): Promise; export declare function completeWorkflowTransactionJournal(cwd: string, sessionId: string, mutationId: string): Promise; export {};