/** * Existing-sheet adoption (MVP Phase 1 — dry-run introspection). * * Reads one foreign spreadsheet tab (never provisioned by this library) and * produces a binding report against an entity's User_Input route: which * sheet columns bind to which entity properties by header NAME (position * and order are irrelevant), which columns are ignored, which entity fields * have no column, whether the bound columns are contiguous (fast bulk write * path) or segmented (per-segment write requests), and whether a usable * business-key (PK) column exists. * * The analysis is PURE: headers + rows in, report out. No network, no * SQLite, no mutation. The only sheet mutation the full adoption will ever * perform is appending system columns (`__hikoutei_row_id`, and the PK * column when it must be generated) — never a rewrite of existing cells * (design D2/D4, `design/existing-sheet-adoption-design.md`). * * Startup contract (D5, fail-closed): dry-run analysis runs BEFORE any * provisioning mutation and BEFORE any supervisor starts; the service does * not enter its running state until a later milestone completes the seeding. */ import type { GoogleSheetsApiProviderOptions } from "../../../../contracts/sheets/googleSheetsApi.js"; import type { ResolvedHikouteiEntityDescriptor } from "../../../api/entity.js"; import { SyncServiceError } from "../errors.js"; import type { InternalSyncProjectionConfig } from "../contracts.js"; import type { RegisteredSyncProjectionDefinition } from "../../../../contracts/sheets/sheetsProvisioning.js"; /** * Thrown instead of starting the sync service when `adopt.mode === "dry-run"`. * Carries the complete read-only report; the spreadsheet was not mutated. */ export declare class ExistingSheetAdoptionDryRunReportError extends SyncServiceError { readonly report: ExistingSheetAdoptionRunReport; constructor(report: ExistingSheetAdoptionRunReport, message: string); } /** Per-entity adoption request from the service options. */ export interface ExistingSheetAdoptionEntitySpec { /** The existing tab that becomes this entity's User_Input route (D1). */ readonly tabName: string; /** * Sheet header that carries the business key. `"auto"` (or absent) prefers * the column matching the entity's primary-key property and falls back to * appending a generated PK column (D4). */ readonly identityFrom?: string | "auto"; /** * Explicit header → property binding for sheets whose headers differ from * the entity's property names (design §12, C1: adoption-only). Mapped * headers take precedence over name matching; unmapped headers keep the * name-binding/ignore rules. The map must cover headers that EXIST in the * tab and properties that ARE declared; a mapped PK header absorbs the D4 * alias (identityFrom may name the mapped header). */ readonly columnMap?: Readonly>; } /** Top-level adoption startup spec. */ export interface ExistingSheetAdoptionSpec { readonly mode: "dry-run" | "adopt"; readonly entities: Readonly>; } /** One bound column: entity property → sheet column. */ export interface ExistingSheetAdoptionColumnBinding { readonly field: string; /** 0-based column index in the sheet. */ readonly columnIndex: number; readonly columnLetter: string; readonly header: string; /** * When the binding came from `columnMap`, the sheet header it was mapped * FROM (the map key). The exact-header expectation for this column is this * value instead of `field` (design §12). */ readonly mappedFromHeader?: string; } export type ExistingSheetAdoptionProblemSeverity = "error" | "warning"; export interface ExistingSheetAdoptionProblem { readonly severity: ExistingSheetAdoptionProblemSeverity; readonly code: "MISSING_IDENTITY_COLUMN" | "DUPLICATE_HEADER" | "MISSING_FIELD" | "DUPLICATE_IDENTITY_VALUE" | "EMPTY_IDENTITY_VALUE" | "NO_PK_CANDIDATE" | "TAB_NOT_FOUND" | "EMPTY_TAB" | "COLUMN_SEGMENTATION" | "COLUMN_OCCUPIED" | "DECLARATION_ORDER_MISMATCH" | "NO_BOUND_COLUMNS" | "EXACT_HEADER_MISMATCH" | "IDENTITY_ALIAS_UNSUPPORTED" | "COLUMN_MAP_UNKNOWN_PROPERTY" | "COLUMN_MAP_DUPLICATE_PROPERTY" | "COLUMN_MAP_UNKNOWN_HEADER"; readonly message: string; readonly detail?: Readonly>; } /** One contiguous run of bound column indices. */ export interface ExistingSheetAdoptionColumnSegment { readonly startColumnIndex: number; readonly endColumnIndex: number; } export interface ExistingSheetAdoptionEntityReport { readonly entityName: string; readonly tabName: string; /** `"ready"` = adoption may proceed; `"blocked"` = at least one error. */ readonly status: "ready" | "blocked"; readonly sheetHeaders: readonly string[]; readonly totalRows: number; readonly emptyRows: number; readonly bindings: readonly ExistingSheetAdoptionColumnBinding[]; readonly ignoredColumns: readonly { readonly columnLetter: string; readonly header: string; }[]; readonly missingFields: readonly string[]; readonly contiguity: "contiguous" | "segmented"; readonly segments: readonly ExistingSheetAdoptionColumnSegment[]; readonly pk: { readonly source: "existing-column" | "auto-generate"; /** Sheet header of the PK column when sourced from an existing column. */ readonly column?: string; readonly generatedCount?: number; readonly duplicates?: readonly { readonly value: string; readonly rowNumbers: readonly number[]; }[]; }; readonly columnsToBeAdded: readonly string[]; readonly tabsToProvision: readonly string[]; readonly problems: readonly ExistingSheetAdoptionProblem[]; } export interface ExistingSheetAdoptionRunReport { readonly mode: "dry-run"; readonly ok: boolean; readonly entities: readonly ExistingSheetAdoptionEntityReport[]; } /** Raw foreign-tab content handed to the analyzer (already read). */ export interface ExistingSheetTabSnapshot { readonly headers: readonly string[]; /** Data rows below the header row; every cell is a raw string or empty. */ readonly rows: readonly (readonly (string | undefined)[])[]; } interface AnalyzeInput { readonly entityName: string; readonly tabName: string; readonly snapshot: ExistingSheetTabSnapshot; readonly descriptor: ResolvedHikouteiEntityDescriptor; readonly userOwnedFields: readonly string[]; readonly identityFrom: string | "auto" | undefined; readonly columnMap?: Readonly> | undefined; readonly systemStateTabName: string; readonly syncConflictsTabName: string; } /** * Pure per-entity adoption analysis. Headers + rows in, report out — * no I/O, no mutation. Deterministic: identical input always yields an * identical report. */ export declare function analyzeExistingSheetAdoptionEntity(input: AnalyzeInput): ExistingSheetAdoptionEntityReport; /** One managed column of the adopted User_Input tab. */ export interface ExistingSheetAdoptionManagedColumn { readonly field: string; readonly columnIndex: number; readonly header: string; } /** Computed layout for one adopted entity (adopt mode). */ export interface ExistingSheetAdoptionLayout { readonly entityName: string; readonly tabName: string; readonly managedColumns: readonly ExistingSheetAdoptionManagedColumn[]; /** The `__hikoutei_row_id` system column (last managed column + 1). */ readonly rowIdColumnIndex: number; /** The PK column (existing within the span, or the appended one). */ readonly pkColumnIndex: number; readonly pkGenerated: boolean; /** The PK column's header (existing header text, or the PK property name when generated). */ readonly pkHeader: string; /** Registered range override for the User_Input route, e.g. `B:F`. */ readonly registeredRange: string; /** Columns appended by adoption: row-id, and the PK when generated. */ readonly appendedColumns: readonly { readonly columnIndex: number; readonly header: string; }[]; } /** Extracts the adopt-mode layout from the dry-run report + snapshot. */ export declare function computeExistingSheetAdoptionLayout(input: { readonly entityName: string; readonly tabName: string; readonly report: ExistingSheetAdoptionEntityReport; readonly headers: readonly string[]; readonly rows: readonly (readonly (string | undefined)[])[]; readonly descriptor: { readonly properties: readonly { readonly name: string; readonly primary: boolean; }[]; }; readonly userOwnedFields: readonly string[]; }): { readonly layout: ExistingSheetAdoptionLayout; readonly extraProblems: readonly ExistingSheetAdoptionProblem[]; }; export interface ExistingSheetAdoptionStartupPlanEntity { readonly entityName: string; readonly tabName: string; readonly entityTableName: string; readonly sheetId: number; readonly tabTitle: string; readonly layout: ExistingSheetAdoptionLayout; readonly rowIdColumnIndex: number; readonly pkAppend?: { readonly columnIndex: number; readonly header: string; }; readonly dataRows: readonly { readonly rowIndex: number; readonly pkValue: string; }[]; } export interface ExistingSheetAdoptionStartupPlan { readonly report: ExistingSheetAdoptionRunReport; readonly entities: readonly ExistingSheetAdoptionStartupPlanEntity[]; } /** * §12 columnMap: attaches the adopted route's PHYSICAL header row (the * legacy headers) to the matching projection definition, positionally * parallel to the canonical field-name headers. The alignment holds by * construction: the C4 declaration-order gate forces the managed column * order to equal the field declaration order, and an appended generated-PK * column carries the property name itself as its header (no translation). * Provisioning, observation, and the writer read this single source — * three-way drift is structurally impossible. */ export declare function withAdoptedPhysicalHeaders(definitions: readonly RegisteredSyncProjectionDefinition[], plan: ExistingSheetAdoptionStartupPlan): readonly RegisteredSyncProjectionDefinition[]; /** * Plans the adoption startup (D5). Reads the foreign tab, runs the pure * analysis, and computes the layout — BEFORE any provisioning mutation: * * - `dry-run`: always throws {@link ExistingSheetAdoptionDryRunReportError} * carrying the full report; the spreadsheet is untouched and the service * never starts. * - `adopt`: a blocked report throws the same error; a ready report returns * the startup plan (layout, appended columns, data rows) for the * bootstrap's seeding phase. */ export declare function planExistingSheetAdoptionStartup(input: { readonly adopt: ExistingSheetAdoptionSpec; readonly spreadsheetId: string; readonly transport: GoogleSheetsApiAdoptionReader; readonly descriptors: readonly ResolvedHikouteiEntityDescriptor[]; readonly projections: InternalSyncProjectionConfig; readonly userOwnedFieldsByEntity: Readonly>; readonly requestTimeoutMs?: number; }): Promise; /** Minimal reader surface the adoption startup needs (satisfied by the real transport). */ export interface GoogleSheetsApiAdoptionReader { getSpreadsheet(request: { readonly spreadsheetId: string; readonly ranges: readonly string[]; readonly fields: string; readonly timeoutMs?: number; }): Promise; getValues(request: { readonly spreadsheetId: string; readonly range: string; readonly timeoutMs?: number; }): Promise<{ readonly values?: readonly (readonly (string | number | boolean | null)[])[]; }>; batchUpdate(request: { readonly spreadsheetId: string; readonly requests: readonly unknown[]; }): Promise; } /** * Builds the full-tab read range for one foreign tab: the A1-quoted tab * name (embedded single quotes doubled) plus the grid-derived end column, * so tabs wider than the historical ZZ hard cap are read in full. */ export declare function adoptionTabRange(tabTitle: string, columnCount: number): string; /** * Resolves the transport the adoption reader uses. Prefers the injected * transport when it exposes the raw `getValues` capability; otherwise builds * the real ADC-backed HTTP transport via the composition root (dry-run reads * a genuinely foreign tab, so no route registration exists yet). Injected * transports without the capability fail closed with a stable message * instead of guessing. * * P8-C: the concrete `GoogleSheetsApiHttpTransport` construction is * composition-owned wiring (`ports.createAdoptionReaderTransport`); the * capability validation stays engine-owned exactly as before. */ export declare function resolveAdoptionReaderTransport(options: GoogleSheetsApiProviderOptions | undefined, createTransport: (providerOptions: GoogleSheetsApiProviderOptions | undefined) => GoogleSheetsApiAdoptionReader): GoogleSheetsApiAdoptionReader; /** Replaces the adopted entities' declared User_Input range with the derived one. */ export declare function withAdoptionRegisteredRangeOverride(projections: InternalSyncProjectionConfig, plan: ExistingSheetAdoptionStartupPlan): InternalSyncProjectionConfig; export {}; //# sourceMappingURL=existingSheetAdoption.d.ts.map