@open-tomato/rafa 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -23
- package/dist/PROMPT.md +18 -1
- package/dist/adapters/logger/settings.d.ts +132 -0
- package/dist/bugs/closest.d.ts +19 -0
- package/dist/bugs/fenced.d.ts +8 -0
- package/dist/bugs/places.d.ts +20 -0
- package/dist/bugs/record.d.ts +145 -0
- package/dist/bugs/title.d.ts +83 -0
- package/dist/bundled/agents/doc-updater.md +17 -3
- package/dist/bundled/agents/loop-implementer.md +14 -2
- package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +10 -3
- package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +5 -0
- package/dist/bundled/skills/dev-planner/SKILL.md +101 -18
- package/dist/cli.js +74726 -62960
- package/dist/config/entry.d.ts +119 -0
- package/dist/config/index.d.ts +36 -0
- package/dist/config/index.js +51 -0
- package/dist/config/shape.d.ts +232 -0
- package/dist/config-registry.d.ts +127 -0
- package/dist/config-schema-board-project.d.ts +157 -0
- package/dist/config-schema-bugs.d.ts +71 -0
- package/dist/config-schema-errors.d.ts +54 -0
- package/dist/config-schema-features.d.ts +64 -0
- package/dist/config-schema-hub.d.ts +150 -0
- package/dist/config-schema-issues.d.ts +95 -0
- package/dist/config-schema-logger.d.ts +91 -0
- package/dist/config-schema-loop-continue.d.ts +122 -0
- package/dist/config-schema-loop-retries.d.ts +94 -0
- package/dist/config-schema-release.d.ts +307 -0
- package/dist/config-schema-tests.d.ts +170 -0
- package/dist/config-schema-triage.d.ts +142 -0
- package/dist/config-schema-wrap-up.d.ts +96 -0
- package/dist/core/command/command.d.ts +228 -0
- package/dist/core/command/confirm.d.ts +67 -0
- package/dist/core/command/index.d.ts +112 -0
- package/dist/core/command/lazy.d.ts +54 -0
- package/dist/core/command/registry-types.d.ts +96 -0
- package/dist/core/command/running.d.ts +96 -0
- package/dist/core/command/spends.d.ts +39 -0
- package/dist/core/command/types.d.ts +92 -0
- package/dist/core/command/version.d.ts +4 -0
- package/dist/core/config/branch-template.d.ts +20 -0
- package/dist/core/config/config-items.d.ts +86 -0
- package/dist/core/config/config-load.d.ts +47 -0
- package/dist/core/config/config-origins.d.ts +57 -0
- package/dist/core/config/config-readers.d.ts +167 -0
- package/dist/core/config/config-schema.d.ts +207 -0
- package/dist/core/config/config-sections.d.ts +493 -0
- package/dist/core/config/config.d.ts +320 -0
- package/dist/core/config/core-section.d.ts +39 -0
- package/dist/core/config/dangerous.d.ts +58 -0
- package/dist/core/config/default-routing.d.ts +16 -0
- package/dist/core/config/index.d.ts +141 -0
- package/dist/core/config/scope.d.ts +42 -0
- package/dist/core/config/section.d.ts +206 -0
- package/dist/core/config/sections.d.ts +45 -0
- package/dist/core/config/standard-schema.d.ts +27 -0
- package/dist/core/git/commit.d.ts +232 -0
- package/dist/core/git/gh-runner.d.ts +24 -0
- package/dist/core/git/git.d.ts +22 -0
- package/dist/core/git/index.d.ts +85 -0
- package/dist/core/loop/claude.d.ts +388 -0
- package/dist/core/loop/hop-record.d.ts +52 -0
- package/dist/core/loop/index.d.ts +179 -0
- package/dist/core/loop/session-env.d.ts +20 -0
- package/dist/core/loop/session-record-parse.d.ts +63 -0
- package/dist/core/loop/sessions.d.ts +408 -0
- package/dist/core/loop/suite-failure.d.ts +25 -0
- package/dist/core/output/active.d.ts +100 -0
- package/dist/core/output/stream.d.ts +20 -0
- package/dist/core/output/text.d.ts +55 -0
- package/dist/core/plan/block-reader.d.ts +46 -0
- package/dist/core/plan/blocks.d.ts +139 -0
- package/dist/core/plan/declaration.d.ts +366 -0
- package/dist/core/plan/plan-stamp.d.ts +75 -0
- package/dist/core/runtime/identity.d.ts +21 -0
- package/dist/core/runtime/index.d.ts +46 -0
- package/dist/effort/store/bring-forward.d.ts +131 -0
- package/dist/effort/store/check-rows.d.ts +31 -0
- package/dist/effort/store/commit-stats.d.ts +42 -0
- package/dist/effort/store/copy.d.ts +35 -0
- package/dist/effort/store/development-build.d.ts +105 -0
- package/dist/effort/store/effort-levels.d.ts +13 -0
- package/dist/effort/store/findings.d.ts +214 -0
- package/dist/effort/store/fix-schema.d.ts +184 -0
- package/dist/effort/store/index.js +10 -7
- package/dist/effort/store/location.d.ts +44 -0
- package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
- package/dist/effort/store/merge-conflicts.d.ts +91 -0
- package/dist/effort/store/merge-rules.d.ts +82 -0
- package/dist/effort/store/merge-store.d.ts +211 -0
- package/dist/effort/store/merge-union.d.ts +92 -0
- package/dist/effort/store/migrate.d.ts +146 -0
- package/dist/effort/store/migrations.d.ts +92 -0
- package/dist/effort/store/ndjson-files.d.ts +114 -0
- package/dist/effort/store/origins.d.ts +116 -0
- package/dist/effort/store/plan-stubs.d.ts +79 -0
- package/dist/effort/store/rebuild-aside.d.ts +172 -0
- package/dist/effort/store/schema-plan.d.ts +102 -0
- package/dist/effort/store/session-row-types.d.ts +64 -0
- package/dist/effort/store/settings.d.ts +13 -0
- package/dist/effort/store/sqlite.d.ts +340 -0
- package/dist/effort/store/store-generation.d.ts +17 -0
- package/dist/effort/store/store-identity.d.ts +220 -0
- package/dist/effort/store/store-meta.d.ts +305 -0
- package/dist/effort/store/store-replaced.d.ts +72 -0
- package/dist/effort/store/types.d.ts +223 -0
- package/dist/errors/codes.d.ts +56 -0
- package/dist/errors/rafa-codes.d.ts +36 -0
- package/dist/index-2a7nb3an.js +125 -0
- package/dist/{index-r28fzsvc.js → index-39kbcw68.js} +350 -165
- package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
- package/dist/index-cy98y87p.js +2597 -0
- package/dist/index-gqz4jn6j.js +13447 -0
- package/dist/index-gvek4r8m.js +710 -0
- package/dist/index-j4k2vdz3.js +1125 -0
- package/dist/index-jh1r4kcn.js +1067 -0
- package/dist/{index-mgh2s9bj.js → index-ykggr043.js} +58 -455
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index.js +14715 -21516
- package/dist/learning/index.js +5 -6
- package/dist/plan/index.js +12 -11
- package/dist/plan-prompt.md +3 -1
- package/dist/ports/index.d.ts +732 -0
- package/dist/ports/learning.d.ts +101 -0
- package/dist/ports/log-level.d.ts +13 -0
- package/dist/ports/spec-review.d.ts +66 -0
- package/dist/pr/checks.d.ts +133 -0
- package/dist/pr/types.d.ts +422 -0
- package/dist/ready-4f8edezt.js +2016 -0
- package/dist/release-templates/release-bot-fragment.yml +153 -0
- package/dist/release-templates/release-publish.yml +110 -0
- package/dist/release-templates/release-settle.yml +122 -0
- package/dist/release-templates/release-tag.yml +102 -0
- package/dist/report/parse.d.ts +415 -0
- package/dist/schema/project-id.d.ts +79 -0
- package/dist/schema/tiers.d.ts +107 -0
- package/package.json +41 -3
- package/dist/index-hf2k497d.js +0 -16
- package/dist/index-nsr78ct2.js +0 -8219
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a merge of two effort stores does with each table: the registry
|
|
3
|
+
* every table of the SQLite store has an entry in.
|
|
4
|
+
*
|
|
5
|
+
* ## Scope
|
|
6
|
+
*
|
|
7
|
+
* A `merged` table travels: a merge unions its rows from both stores,
|
|
8
|
+
* one copy of each identity. Each carries `origin_store` and
|
|
9
|
+
* `origin_seq`, since migration `row-origins` or, for a table created
|
|
10
|
+
* after it, from its own migration, with the partial unique index
|
|
11
|
+
* `<table>_by_origin` over the pair, and is one of the
|
|
12
|
+
* `ORIGIN_TABLES` (`origins.ts`). A `local` table never leaves its
|
|
13
|
+
* machine: the migration log, the store's identity row, the ids it
|
|
14
|
+
* replaced and the merge trail describe this one file, and a merge neither reads them from the
|
|
15
|
+
* other store nor writes them from it.
|
|
16
|
+
*
|
|
17
|
+
* ## Identity
|
|
18
|
+
*
|
|
19
|
+
* The columns that make two rows of a merged table the same row, for a
|
|
20
|
+
* row whose origin is NULL, as every row written before `row-origins`
|
|
21
|
+
* or by an older runtime is. A UUID `id` where the table has one, the
|
|
22
|
+
* session id for sessions and dispatches, the sha for commits, and the
|
|
23
|
+
* natural key the table's own UNIQUE constraint or index names for the
|
|
24
|
+
* rest. Two NULLs in an identity column count as equal, as the
|
|
25
|
+
* `ifnull` of `skill_invocations_by_use` counts them.
|
|
26
|
+
*
|
|
27
|
+
* ## Edited fields
|
|
28
|
+
*
|
|
29
|
+
* The store is almost append-only. A column a production statement
|
|
30
|
+
* changes after the insert is an edited field, and the merge has to know
|
|
31
|
+
* which of two stores' values to keep. There are two today.
|
|
32
|
+
* `findings.tracker_ref`, written by `writeTrackerRef`
|
|
33
|
+
* (`tracker-refs.ts`) once a bug is filed, is {@link SET_ONCE}: a
|
|
34
|
+
* filled value beats NULL, and two different filled values mean one
|
|
35
|
+
* finding was filed twice, which the merge keeps in `merge_conflicts`.
|
|
36
|
+
* A reference that supersedes another is a row of its own, never a new
|
|
37
|
+
* value of this field; the `findings` entry says how.
|
|
38
|
+
* `commits.row_json` is {@link RECOMPUTED}: its `minutesSincePrevious`
|
|
39
|
+
* is rewritten by the merge itself (`merge-commit-gaps.ts`) for each
|
|
40
|
+
* commit brought in and the commit after it in time, so two rows of one
|
|
41
|
+
* commit that differ only in that key are the same row.
|
|
42
|
+
*
|
|
43
|
+
* ## What holds the registry to the schema
|
|
44
|
+
*
|
|
45
|
+
* `merge-rules.sweep.test.ts` reads a store brought through every migration
|
|
46
|
+
* and fails on a table with no entry, an entry with no table, a merged
|
|
47
|
+
* table without both origin columns and their partial unique index, an
|
|
48
|
+
* identity or edited column the table lacks, and a production edit of a
|
|
49
|
+
* merged table's column that has no rule here. A migration that adds a
|
|
50
|
+
* table therefore adds its entry in the same commit.
|
|
51
|
+
*/
|
|
52
|
+
/** Whether a merge unions a table's rows or leaves the table on its machine. */
|
|
53
|
+
export type MergeScope = 'merged' | 'local';
|
|
54
|
+
/**
|
|
55
|
+
* A filled value beats NULL, and two different filled values are both
|
|
56
|
+
* kept, the incoming one as a conflict.
|
|
57
|
+
*/
|
|
58
|
+
export declare const SET_ONCE = "set-once";
|
|
59
|
+
/**
|
|
60
|
+
* The merge recomputes the field from the rows it holds, so two stores'
|
|
61
|
+
* values of it are never compared: for `commits.row_json`, its
|
|
62
|
+
* `minutesSincePrevious` key.
|
|
63
|
+
*/
|
|
64
|
+
export declare const RECOMPUTED = "recomputed";
|
|
65
|
+
/** How a merge settles an edited field whose two stores disagree. */
|
|
66
|
+
export type EditRule = typeof SET_ONCE | typeof RECOMPUTED;
|
|
67
|
+
/** A table a merge unions. */
|
|
68
|
+
export interface MergedTableRule {
|
|
69
|
+
readonly scope: 'merged';
|
|
70
|
+
/** The columns two rows with a NULL origin are matched by. */
|
|
71
|
+
readonly identity: readonly string[];
|
|
72
|
+
/** Every column a production statement changes after the insert, with its rule. */
|
|
73
|
+
readonly edited: Readonly<Record<string, EditRule>>;
|
|
74
|
+
}
|
|
75
|
+
/** A table that never leaves its machine. */
|
|
76
|
+
export interface LocalTableRule {
|
|
77
|
+
readonly scope: 'local';
|
|
78
|
+
}
|
|
79
|
+
/** One table's entry in {@link MERGE_RULES}. */
|
|
80
|
+
export type MergeRule = MergedTableRule | LocalTableRule;
|
|
81
|
+
/** Every table of the SQLite store, by name, and what a merge does with it. */
|
|
82
|
+
export declare const MERGE_RULES: Readonly<Record<string, MergeRule>>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The merge `rafa effort merge` runs: another device's store file
|
|
3
|
+
* unioned into this store, built beside it, checked, and swapped in
|
|
4
|
+
* behind a whole-file backup.
|
|
5
|
+
*
|
|
6
|
+
* ## What it refuses
|
|
7
|
+
*
|
|
8
|
+
* Every refusal comes before anything is built or copied, so both files
|
|
9
|
+
* keep their bytes. In the order they are asked:
|
|
10
|
+
*
|
|
11
|
+
* - A project whose `store` is `ndjson`: its sessions and commits are
|
|
12
|
+
* NDJSON, which carries no origin, so a merge needs the SQLite
|
|
13
|
+
* backend. The text names {@link MOVE_TO_SQLITE}. The backend is the
|
|
14
|
+
* caller's resolved config, never read here.
|
|
15
|
+
* - A store file absent on either side, and a journal or write-ahead
|
|
16
|
+
* log beside either one (`refuseInFlight`, `rebuild-aside.ts`).
|
|
17
|
+
* - A damaged other file: one SQLite cannot open as a database, or
|
|
18
|
+
* whose `integrity_check` answers anything but `ok`. A plain file
|
|
19
|
+
* copy taken during a write holds a half-written page; a copy made
|
|
20
|
+
* by `rafa effort copy` is consistent.
|
|
21
|
+
* - A store on either side that `planSchema` (`schema-plan.ts`) will
|
|
22
|
+
* not let this rafa write, with the plan's own text.
|
|
23
|
+
* - Another project's store: both stores name a project, and their
|
|
24
|
+
* root commits differ. The project of each is the one its
|
|
25
|
+
* `store_meta` row records; this store, when it has not been
|
|
26
|
+
* minted, takes the one git reads in its directory. A side that
|
|
27
|
+
* names none, such as a store only an older runtime wrote, cannot
|
|
28
|
+
* be told apart and is merged, and the `merges` row then names no
|
|
29
|
+
* other store.
|
|
30
|
+
*
|
|
31
|
+
* Two more are asked only of a swap, since a dry run reads the stores
|
|
32
|
+
* and deletes what it builds, as `effort migrate`'s is:
|
|
33
|
+
*
|
|
34
|
+
* - From a development build, over a store it does not own, with
|
|
35
|
+
* `refuseUnownedDevelopmentWrite`'s text (`development-build.ts`).
|
|
36
|
+
* What it names is what the swap records: the migrations this store
|
|
37
|
+
* has pending, then `merges`, the table the merge writes its row to.
|
|
38
|
+
* - While a loop record under the store's project reads `running` or
|
|
39
|
+
* `paused` with its pid alive (`liveLoop`, `migrate.ts`): renaming
|
|
40
|
+
* the store under a loop loses what the loop writes. The record
|
|
41
|
+
* whose session id `sessionId` names is passed: it is the run
|
|
42
|
+
* pulling at the end of its own task, between task sessions, so
|
|
43
|
+
* nothing of its own writes into the store under the swap. It is
|
|
44
|
+
* matched by session id, never by pid, so a second live record
|
|
45
|
+
* sharing that pid is still refused.
|
|
46
|
+
*
|
|
47
|
+
* ## The other store
|
|
48
|
+
*
|
|
49
|
+
* It is only ever read. It is opened `{ readonly: true }` when it holds
|
|
50
|
+
* every migration this rafa knows. When it lacks some, it is written
|
|
51
|
+
* out with `VACUUM INTO` (`vacuumInto`, `copy.ts`) to a directory of
|
|
52
|
+
* its own under the temporary directory, that copy is brought forward
|
|
53
|
+
* (`bringForward`, `builtAside`), and the copy is what the union reads;
|
|
54
|
+
* the directory is removed once the merge ends, refused or not. A store
|
|
55
|
+
* logging a migration this rafa does not know adds columns the union
|
|
56
|
+
* refuses (`UnionSchemaMismatch`, `merge-union.ts`), and that refusal
|
|
57
|
+
* is passed on.
|
|
58
|
+
*
|
|
59
|
+
* ## The build
|
|
60
|
+
*
|
|
61
|
+
* The parallel file, `<store>.merge-<stamp>`, is this store written out
|
|
62
|
+
* by `vacuumInto` and brought forward there with `builtAside`, each new
|
|
63
|
+
* log row naming this runtime (`appliedByName`). In one transaction on
|
|
64
|
+
* it, the union (`unionStores`) inserts every unmatched row of each
|
|
65
|
+
* merged table, the conflict step (`settleMatches`,
|
|
66
|
+
* `merge-conflicts.ts`) settles each matched pair and records in
|
|
67
|
+
* `merge_conflicts` what no rule settles, the commit gaps of each commit
|
|
68
|
+
* brought in and of the commit after it are recomputed
|
|
69
|
+
* (`recomputeCommitGaps`, `merge-commit-gaps.ts`), and one `merges` row
|
|
70
|
+
* records the merge: its id, the other store's id, when, and the rows
|
|
71
|
+
* added, skipped and in conflict over every merged table.
|
|
72
|
+
*
|
|
73
|
+
* The live store is then attached, and every table both files hold,
|
|
74
|
+
* the log apart, must hold its live row count plus the rows the merge
|
|
75
|
+
* added to it (`checkedCounts`): the rows added to each merged table,
|
|
76
|
+
* one row in `merges`, and one in `merge_conflicts` per conflict. A
|
|
77
|
+
* merged table's fills and gap rewrites change no count. SQLite's
|
|
78
|
+
* `integrity_check` must answer `ok`, and `planSchema` must find the
|
|
79
|
+
* file one this rafa uses as it is (`refuseUnusable`, `fix-schema.ts`).
|
|
80
|
+
*
|
|
81
|
+
* Under `dryRun` the parallel file is then deleted, and the answer says
|
|
82
|
+
* what the merge would have done. Otherwise the parallel file is
|
|
83
|
+
* swapped in (`swapIn`, `rebuild-aside.ts`): this store is written out
|
|
84
|
+
* to `<store>.before-merge-<stamp>.bak` with `VACUUM INTO`, its identity
|
|
85
|
+
* is carried onto the parallel file (`carryStoreIdentity`,
|
|
86
|
+
* `store-meta.ts`), and the parallel file is renamed into its place.
|
|
87
|
+
*
|
|
88
|
+
* ## The store keeps its id
|
|
89
|
+
*
|
|
90
|
+
* `store_meta` is `local` in `MERGE_RULES`, so the parallel file holds
|
|
91
|
+
* this store's own row and never the other store's. The carry gives it
|
|
92
|
+
* the device, inode and generation the rename brings to this store's
|
|
93
|
+
* path, so the merged store keeps this store's origin, and the rows it
|
|
94
|
+
* writes next carry the same store id. A store whose next write would
|
|
95
|
+
* have minted anyway, being a copy already, is carried nothing and
|
|
96
|
+
* mints on its first write after the swap.
|
|
97
|
+
*
|
|
98
|
+
* The backup holds every row the original held, and restoring it is
|
|
99
|
+
* renaming it back. It is a copy with an inode of its own, so once
|
|
100
|
+
* renamed back over the store its first writing open mints a new
|
|
101
|
+
* origin, as any restored copy must (`store-identity.ts`).
|
|
102
|
+
*
|
|
103
|
+
* ## What the counts leave out
|
|
104
|
+
*
|
|
105
|
+
* An unmatched incoming row the union could not insert, because it
|
|
106
|
+
* collides with a row here on another UNIQUE key, is answered per table
|
|
107
|
+
* as `collided` and kept in the other file only: it is neither added,
|
|
108
|
+
* skipped nor recorded, since `merge_conflicts` names a local row and
|
|
109
|
+
* the union does not know which one it collided with.
|
|
110
|
+
*
|
|
111
|
+
* It opens both stores itself rather than through `withSqliteStore`, so
|
|
112
|
+
* it runs the test guard (`guardTestProcess`, `location.ts`) itself,
|
|
113
|
+
* over both paths, before it reads anything.
|
|
114
|
+
*/
|
|
115
|
+
import type { DevelopmentProbe } from './development-build.js';
|
|
116
|
+
import type { SqliteMigration } from './migrations.js';
|
|
117
|
+
import type { ProjectIdentity } from './store-identity.js';
|
|
118
|
+
import type { StoreBackend } from '../../core/config/sections.js';
|
|
119
|
+
import { RebuildRefusal } from './rebuild-aside.js';
|
|
120
|
+
/** The command that moves an NDJSON project's rows into the SQLite store. */
|
|
121
|
+
export declare const MOVE_TO_SQLITE = "rafa effort move --to=sqlite";
|
|
122
|
+
/** Why a merge was refused before anything was built. */
|
|
123
|
+
export type MergeRefusalReason =
|
|
124
|
+
/** The project's `store` is `ndjson`. */
|
|
125
|
+
'ndjson'
|
|
126
|
+
/** A store file is absent. */
|
|
127
|
+
| 'missing'
|
|
128
|
+
/** The other file is no SQLite database, or fails `integrity_check`. */
|
|
129
|
+
| 'damaged'
|
|
130
|
+
/** A store's schema is one this rafa will not write. */
|
|
131
|
+
| 'schema'
|
|
132
|
+
/** The two stores name different projects. */
|
|
133
|
+
| 'other-project'
|
|
134
|
+
/** A loop records to this store. */
|
|
135
|
+
| 'live-loop';
|
|
136
|
+
/** A merge refused before either store was changed. */
|
|
137
|
+
export declare class MergeRefusal extends RebuildRefusal {
|
|
138
|
+
readonly reason: MergeRefusalReason;
|
|
139
|
+
constructor(reason: MergeRefusalReason, message: string);
|
|
140
|
+
}
|
|
141
|
+
/** What the run did. */
|
|
142
|
+
export type MergeStatus =
|
|
143
|
+
/** Under `dryRun`: the merged store was built, checked and deleted. */
|
|
144
|
+
'would-merge'
|
|
145
|
+
/** The merged store was swapped in and the original kept as a backup. */
|
|
146
|
+
| 'merged';
|
|
147
|
+
/** What the merge did with one merged table's incoming rows. */
|
|
148
|
+
export interface TableMerge {
|
|
149
|
+
readonly table: string;
|
|
150
|
+
/** Rows inserted with a new local `seq`. */
|
|
151
|
+
readonly added: number;
|
|
152
|
+
/** Matched rows left with equal content, fills included. */
|
|
153
|
+
readonly skipped: number;
|
|
154
|
+
/** `merge_conflicts` rows recorded. */
|
|
155
|
+
readonly inConflict: number;
|
|
156
|
+
/** Set-once fields this store took from the other. */
|
|
157
|
+
readonly filled: number;
|
|
158
|
+
/** Unmatched rows another UNIQUE key refused; see the module note. */
|
|
159
|
+
readonly collided: number;
|
|
160
|
+
}
|
|
161
|
+
/** The outcome of one run. */
|
|
162
|
+
export interface MergeResult {
|
|
163
|
+
/** This store's file. */
|
|
164
|
+
readonly path: string;
|
|
165
|
+
/** The other store's file. */
|
|
166
|
+
readonly otherPath: string;
|
|
167
|
+
readonly status: MergeStatus;
|
|
168
|
+
/** The `merges.id` the merge recorded. */
|
|
169
|
+
readonly mergeId: string;
|
|
170
|
+
/** The other store's id, or null when it was never minted. */
|
|
171
|
+
readonly otherStore: string | null;
|
|
172
|
+
/** What the other store's copy was brought forward through; empty when it was read as it is. */
|
|
173
|
+
readonly otherBroughtForward: readonly string[];
|
|
174
|
+
/** One entry per merged table, in `MERGE_RULES` order. */
|
|
175
|
+
readonly tables: readonly TableMerge[];
|
|
176
|
+
readonly rowsAdded: number;
|
|
177
|
+
readonly rowsSkipped: number;
|
|
178
|
+
readonly rowsInConflict: number;
|
|
179
|
+
/** Commit rows whose gap was rewritten. */
|
|
180
|
+
readonly gapsRewritten: number;
|
|
181
|
+
/** Where the original went, for `merged` only. */
|
|
182
|
+
readonly backupPath: string | null;
|
|
183
|
+
}
|
|
184
|
+
/** What one run is handed; the {@link DevelopmentProbe} fields say which build runs and which stores it owns. */
|
|
185
|
+
export interface MergeOptions extends DevelopmentProbe {
|
|
186
|
+
/** This store's file. */
|
|
187
|
+
readonly path: string;
|
|
188
|
+
/** The other store's file, only ever read. */
|
|
189
|
+
readonly otherPath: string;
|
|
190
|
+
/** The project's resolved `store` backend. */
|
|
191
|
+
readonly backend: StoreBackend;
|
|
192
|
+
/** Build and check the merged store, then delete it instead of swapping it in. */
|
|
193
|
+
readonly dryRun: boolean;
|
|
194
|
+
/** Names the parallel and backup files, so two runs never collide. */
|
|
195
|
+
readonly stamp: string;
|
|
196
|
+
/** The catalogue both stores are brought to: this build's unless a test passes another. */
|
|
197
|
+
readonly migrations?: readonly SqliteMigration[];
|
|
198
|
+
/** The clock `merged_at` and new log rows read. */
|
|
199
|
+
readonly now?: () => Date;
|
|
200
|
+
/** A new merge id; `crypto.randomUUID` when absent. */
|
|
201
|
+
readonly newMergeId?: () => string;
|
|
202
|
+
/** The project of the repository holding `dir`, for an unminted store; `readProjectIdentity` when absent. */
|
|
203
|
+
readonly readProject?: (dir: string) => ProjectIdentity;
|
|
204
|
+
/**
|
|
205
|
+
* The session id of the loop run merging, whose own live record the
|
|
206
|
+
* live-loop guard passes; absent or null for a caller that is no run.
|
|
207
|
+
*/
|
|
208
|
+
readonly sessionId?: string | null;
|
|
209
|
+
}
|
|
210
|
+
/** Merges the store at `options.otherPath` into the one at `options.path`. See the module note. */
|
|
211
|
+
export declare function mergeStore(options: MergeOptions): MergeResult;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The union at the heart of a merge: every row of each `merged` table in
|
|
3
|
+
* another store that matches no row here is inserted here, so the two
|
|
4
|
+
* stores' rows end up in this one, one copy of each.
|
|
5
|
+
*
|
|
6
|
+
* ## Matching
|
|
7
|
+
*
|
|
8
|
+
* An incoming row is looked up here in two steps, and the first to find
|
|
9
|
+
* a row wins:
|
|
10
|
+
*
|
|
11
|
+
* 1. **The origin pair.** An incoming row whose `origin_store` and
|
|
12
|
+
* `origin_seq` are both set matches the row here holding the same
|
|
13
|
+
* pair. The pair is unique wherever it is set (`<table>_by_origin`),
|
|
14
|
+
* so at most one row answers.
|
|
15
|
+
* 2. **Identity.** Otherwise the row matches the row here holding the
|
|
16
|
+
* same values in the table's identity columns from `MERGE_RULES`
|
|
17
|
+
* (`merge-rules.ts`): `session_id` for sessions and dispatches, `sha`
|
|
18
|
+
* for commits, `id` for the UUID tables, `(pr, head_sha, read_at)`
|
|
19
|
+
* for `plan_ci`. Two NULLs count as equal, as `IS` compares them.
|
|
20
|
+
*
|
|
21
|
+
* Step 2 is what a row with a NULL origin, on either side, is matched
|
|
22
|
+
* by. It also runs when both rows carry a pair and the pairs differ,
|
|
23
|
+
* because every identity is a UNIQUE key of its table: a commit two
|
|
24
|
+
* devices collected from one pushed history holds one sha under two
|
|
25
|
+
* origins, and the table could not hold both. Such a match, and one on
|
|
26
|
+
* a pair whose two rows differ, is what the merge's conflict step reads
|
|
27
|
+
* the {@link UnionMatch} list for; the union itself compares no content.
|
|
28
|
+
*
|
|
29
|
+
* ## Inserting
|
|
30
|
+
*
|
|
31
|
+
* An unmatched row is inserted with a new local `seq`, named in the
|
|
32
|
+
* insert as `COALESCE(MAX(seq), 0) + 1` for the reason `origins.ts`
|
|
33
|
+
* gives, and every other column as the other store holds it: its origin
|
|
34
|
+
* pair unchanged, NULL staying NULL. Rows go in in the other store's
|
|
35
|
+
* `seq` order, and each lookup reads the rows inserted before it, so two
|
|
36
|
+
* rows of one identity in the other store land as one.
|
|
37
|
+
*
|
|
38
|
+
* An unmatched row can still collide with a row here on one of the
|
|
39
|
+
* table's other UNIQUE keys, such as `findings_by_artifact`, when one
|
|
40
|
+
* report was stored under two UUIDs. Nothing in `MERGE_RULES` says which
|
|
41
|
+
* row that is, so the row is not inserted and is returned as a
|
|
42
|
+
* {@link UnionCollision}; the union goes on with the next row.
|
|
43
|
+
*
|
|
44
|
+
* ## What it refuses
|
|
45
|
+
*
|
|
46
|
+
* Both stores must hold the same columns in every merged table, which
|
|
47
|
+
* the merge sees to by bringing both to the same migrations first. A
|
|
48
|
+
* column one side lacks would be dropped from every row copied or read
|
|
49
|
+
* as NULL, so the union checks every table before it inserts anything
|
|
50
|
+
* and throws {@link UnionSchemaMismatch} naming the columns. The inserts
|
|
51
|
+
* run in one transaction on `local`; `other` is only read.
|
|
52
|
+
*/
|
|
53
|
+
import type { Database } from 'bun:sqlite';
|
|
54
|
+
/** Which of the two lookups found an incoming row's match. */
|
|
55
|
+
export type MatchedBy = 'origin' | 'identity';
|
|
56
|
+
/** An incoming row that matched a row already here, which the union left alone. */
|
|
57
|
+
export interface UnionMatch {
|
|
58
|
+
/** The local row's `seq`. */
|
|
59
|
+
readonly localSeq: number;
|
|
60
|
+
/** The incoming row's `seq` in the other store. */
|
|
61
|
+
readonly incomingSeq: number;
|
|
62
|
+
readonly by: MatchedBy;
|
|
63
|
+
}
|
|
64
|
+
/** An unmatched incoming row the table refused on another of its UNIQUE keys. */
|
|
65
|
+
export interface UnionCollision {
|
|
66
|
+
/** The incoming row's `seq` in the other store. */
|
|
67
|
+
readonly incomingSeq: number;
|
|
68
|
+
/** SQLite's own message, naming the key. */
|
|
69
|
+
readonly reason: string;
|
|
70
|
+
}
|
|
71
|
+
/** What the union did with one merged table. */
|
|
72
|
+
export interface TableUnion {
|
|
73
|
+
readonly table: string;
|
|
74
|
+
/** The local `seq` of each row inserted, in insert order. */
|
|
75
|
+
readonly added: readonly number[];
|
|
76
|
+
readonly matched: readonly UnionMatch[];
|
|
77
|
+
readonly collided: readonly UnionCollision[];
|
|
78
|
+
}
|
|
79
|
+
/** The two stores disagree on the columns of a merged table. */
|
|
80
|
+
export declare class UnionSchemaMismatch extends Error {
|
|
81
|
+
constructor(message: string);
|
|
82
|
+
}
|
|
83
|
+
/** The columns of `table` other than `seq`, in table order. */
|
|
84
|
+
export declare function carriedColumns(db: Database, table: string): string[];
|
|
85
|
+
/**
|
|
86
|
+
* Inserts into `local` every row of each merged table of `other` that
|
|
87
|
+
* matches no row of `local`, as the module note says, and answers what
|
|
88
|
+
* it did with each table, in `MERGE_RULES` order. Throws
|
|
89
|
+
* {@link UnionSchemaMismatch}, having inserted nothing, when the two
|
|
90
|
+
* stores hold different columns in any merged table.
|
|
91
|
+
*/
|
|
92
|
+
export declare function unionStores(local: Database, other: Database): TableUnion[];
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The forward move `rafa effort migrate` runs: a SQLite store's pending
|
|
3
|
+
* migrations, breaking ones included, applied to a copy built beside it,
|
|
4
|
+
* checked, and swapped in behind a whole-file backup.
|
|
5
|
+
*
|
|
6
|
+
* ## What it applies
|
|
7
|
+
*
|
|
8
|
+
* The decision is `planSchema`'s (`schema-plan.ts`), asked for a write
|
|
9
|
+
* by the `migrate` caller, the one caller a pending breaking migration
|
|
10
|
+
* does not refuse. The store's `user_version` and migration log are read
|
|
11
|
+
* on a read-only connection, so a store with nothing to write keeps its
|
|
12
|
+
* bytes.
|
|
13
|
+
*
|
|
14
|
+
* - A store with nothing to adopt or apply is `current`, and left alone.
|
|
15
|
+
* - A store `planSchema` refuses is refused with the plan's own text,
|
|
16
|
+
* which ends with the command to run next. `breaking-pending` never
|
|
17
|
+
* comes back, since this is the caller it names.
|
|
18
|
+
* - Otherwise the store is brought forward aside: adopted when it has
|
|
19
|
+
* no log, then every pending migration applied in catalogue order.
|
|
20
|
+
*
|
|
21
|
+
* ## The build
|
|
22
|
+
*
|
|
23
|
+
* The parallel file, `<store>.migrate-<stamp>`, is the live store written
|
|
24
|
+
* out by `VACUUM INTO` on a read-only connection (`vacuumInto`,
|
|
25
|
+
* `copy.ts`), which keeps `user_version` and the log. It is brought
|
|
26
|
+
* forward there by the log-aware apply (`bringForward`,
|
|
27
|
+
* `bring-forward.ts`) as the `migrate` caller, each row's `applied_by`
|
|
28
|
+
* naming this runtime (`appliedByName`, `development-build.ts`). The live
|
|
29
|
+
* store is then attached, and the row count of every table both files
|
|
30
|
+
* hold, the log apart, must equal the live one (`checkedCounts`): a
|
|
31
|
+
* breaking table rebuild that lost a row is refused there. SQLite's
|
|
32
|
+
* `integrity_check` must answer `ok`, and `planSchema` must find the file
|
|
33
|
+
* one this rafa uses as it is (`refuseUnusable`, `fix-schema.ts`).
|
|
34
|
+
*
|
|
35
|
+
* Under `dryRun` the parallel file is then deleted. Otherwise the live
|
|
36
|
+
* store is written out to `<store>.before-<id>-<stamp>.bak` with
|
|
37
|
+
* `VACUUM INTO`, its identity is carried onto the parallel file
|
|
38
|
+
* (`carryStoreIdentity`, `store-meta.ts`), and the parallel file is
|
|
39
|
+
* renamed into its place. `<id>` is the first migration applied, or
|
|
40
|
+
* `schema_migrations` when the run only adopts the store.
|
|
41
|
+
*
|
|
42
|
+
* The migrated store keeps the live store's origin, so the rows it
|
|
43
|
+
* writes next carry the same store id; a store whose next write would
|
|
44
|
+
* have minted anyway is carried nothing and mints after the swap. The
|
|
45
|
+
* backup holds every row the original held, and restoring it is
|
|
46
|
+
* renaming it back. It is a copy with an inode of its own, so once
|
|
47
|
+
* renamed back over the store its first writing open mints a new
|
|
48
|
+
* origin (`store-identity.ts`).
|
|
49
|
+
*
|
|
50
|
+
* Those steps, from the in-flight refusal to the swap and the removal of
|
|
51
|
+
* the parallel file on any failure, are `rebuild-aside.ts`'s, the same
|
|
52
|
+
* convention `fix-schema.ts` runs with its own file names.
|
|
53
|
+
*
|
|
54
|
+
* ## When the swap is refused
|
|
55
|
+
*
|
|
56
|
+
* Both refusals come before anything is built, and leave the directory
|
|
57
|
+
* as it was. A dry run is asked neither, since it only reads the store
|
|
58
|
+
* and deletes what it writes.
|
|
59
|
+
*
|
|
60
|
+
* - From a development build, unless the store is one it owns: under
|
|
61
|
+
* the temporary directory or `RAFA_EFFORT_DIR`, which is where a copy
|
|
62
|
+
* `rafa effort copy` made sits. The question and its text are
|
|
63
|
+
* `refuseUnownedDevelopmentWrite`'s, the one every open asks.
|
|
64
|
+
* - While a loop record under the store's project root reads `running`
|
|
65
|
+
* or `paused` with its pid alive (`readSessions`,
|
|
66
|
+
* `packages/rafa/src/core/loop/sessions.ts`): renaming the store under a loop loses
|
|
67
|
+
* what it writes. The spec's text names the first breaking migration:
|
|
68
|
+
* `REFUSED — migration <id> breaks older runtimes, and loop <sessionId>
|
|
69
|
+
* (pid <pid>, plan <stub>) is running on this store. Nothing was
|
|
70
|
+
* migrated. Finish or stop that loop, then run it again.` With only
|
|
71
|
+
* additive migrations pending it names the loop and the lost writes
|
|
72
|
+
* instead. A store that is not a project's own `.rafa/effort/` file,
|
|
73
|
+
* a copy, is named by no loop record, since `loop start` refuses to
|
|
74
|
+
* run with `RAFA_EFFORT_DIR` set.
|
|
75
|
+
*
|
|
76
|
+
* It opens the store itself rather than through `withSqliteStore`, so it
|
|
77
|
+
* runs the test guard (`guardTestProcess`, `location.ts`) itself, before
|
|
78
|
+
* it reads or makes anything.
|
|
79
|
+
*/
|
|
80
|
+
import type { DevelopmentProbe } from './development-build.js';
|
|
81
|
+
import type { SqliteMigration } from './migrations.js';
|
|
82
|
+
import type { TableRows } from './rebuild-aside.js';
|
|
83
|
+
import type { LoggedMigration } from './schema-plan.js';
|
|
84
|
+
import type { SessionRecord } from '../../core/loop/index.js';
|
|
85
|
+
/** What the run found, and what it did about it. */
|
|
86
|
+
export type MigrateStatus =
|
|
87
|
+
/** No store file: nothing to migrate, and none is created. */
|
|
88
|
+
'missing'
|
|
89
|
+
/** Nothing to adopt or apply: nothing to migrate. */
|
|
90
|
+
| 'current'
|
|
91
|
+
/** Under `dryRun`: the migrated store was built, checked and deleted. */
|
|
92
|
+
| 'would-migrate'
|
|
93
|
+
/** The migrated store was swapped in and the original kept as a backup. */
|
|
94
|
+
| 'migrated';
|
|
95
|
+
/** The outcome of one run. */
|
|
96
|
+
export interface MigrateResult {
|
|
97
|
+
/** The live store's file. */
|
|
98
|
+
readonly path: string;
|
|
99
|
+
readonly status: MigrateStatus;
|
|
100
|
+
/** The live store's `user_version`, or null when there is no file. */
|
|
101
|
+
readonly storeVersion: number | null;
|
|
102
|
+
/** Whether the live store held a migration log. */
|
|
103
|
+
readonly logged: boolean;
|
|
104
|
+
/** What the run records: `schema_migrations` first when it adopts, then each pending id. */
|
|
105
|
+
readonly applying: readonly string[];
|
|
106
|
+
/** The pending ids that break older runtimes, in catalogue order. */
|
|
107
|
+
readonly breaking: readonly string[];
|
|
108
|
+
/** Logged migrations this rafa does not know; each is one this rafa uses the store through. */
|
|
109
|
+
readonly unknown: readonly LoggedMigration[];
|
|
110
|
+
/** Who the new log rows name as applying them. */
|
|
111
|
+
readonly appliedBy: string;
|
|
112
|
+
/** The `user_version` the migrated store holds, or null when nothing was built. */
|
|
113
|
+
readonly userVersion: number | null;
|
|
114
|
+
/** Every table both files hold, with the rows each holds; empty when nothing was built. */
|
|
115
|
+
readonly counted: readonly TableRows[];
|
|
116
|
+
/** Where the original went, for `migrated` only. */
|
|
117
|
+
readonly backupPath: string | null;
|
|
118
|
+
}
|
|
119
|
+
/** What one run is handed; the {@link DevelopmentProbe} fields say which build runs and which stores it owns. */
|
|
120
|
+
export interface MigrateOptions extends DevelopmentProbe {
|
|
121
|
+
/** The live store's file. */
|
|
122
|
+
readonly path: string;
|
|
123
|
+
/** Build and check the migrated store, then delete it instead of swapping it in. */
|
|
124
|
+
readonly dryRun: boolean;
|
|
125
|
+
/** Names the parallel and backup files, so two runs never collide. */
|
|
126
|
+
readonly stamp: string;
|
|
127
|
+
/** The catalogue to migrate to: this build's unless a test passes a synthetic tail. */
|
|
128
|
+
readonly migrations?: readonly SqliteMigration[];
|
|
129
|
+
/** The clock the new log rows' `applied_at` is read from. */
|
|
130
|
+
readonly now?: () => Date;
|
|
131
|
+
}
|
|
132
|
+
/** A run refused before the live store was changed: the build-aside steps' refusal. */
|
|
133
|
+
export { RebuildRefusal as MigrateRefusal } from './rebuild-aside.js';
|
|
134
|
+
/**
|
|
135
|
+
* The first loop record under the store's project root that reads
|
|
136
|
+
* `running` or `paused`, if any, passing the record whose session id is
|
|
137
|
+
* `ownSessionId`: the run asking, which a pull at the end of its own task
|
|
138
|
+
* names (`merge-store.ts`). A record is passed by its session id alone,
|
|
139
|
+
* never by its pid, so another live record sharing the caller's pid is
|
|
140
|
+
* still answered. Records that cannot be read refuse, the text ending
|
|
141
|
+
* with `nothingDone`, the caller's sentence for what it left undone; the
|
|
142
|
+
* merge asks this too, and only the merge names a session id.
|
|
143
|
+
*/
|
|
144
|
+
export declare function liveLoop(path: string, options: DevelopmentProbe, nothingDone?: string, ownSessionId?: string | null): SessionRecord | undefined;
|
|
145
|
+
/** Migrates the store at `options.path`. See the module note. */
|
|
146
|
+
export declare function migrateStore(options: MigrateOptions): MigrateResult;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** What an older runtime can no longer do once a migration has run. */
|
|
2
|
+
export type MigrationBreak = 'readers' | 'writers';
|
|
3
|
+
/** Why a breaking migration breaks, and what prepared the way for it. */
|
|
4
|
+
export interface MigrationContract {
|
|
5
|
+
/**
|
|
6
|
+
* The id of the additive migration that shipped the new shape in an
|
|
7
|
+
* earlier release, or null when no expand step was needed.
|
|
8
|
+
*/
|
|
9
|
+
readonly expand: string | null;
|
|
10
|
+
/** Why the change cannot be additive. */
|
|
11
|
+
readonly why: string;
|
|
12
|
+
}
|
|
13
|
+
/** A migration as rafa decides about it, whatever backend runs it. */
|
|
14
|
+
export interface MigrationSpec {
|
|
15
|
+
/** Kebab-case and unique for all time: the key a store records it by. */
|
|
16
|
+
readonly id: string;
|
|
17
|
+
/** `[]` for an additive migration; otherwise what it breaks. */
|
|
18
|
+
readonly breaks: readonly MigrationBreak[];
|
|
19
|
+
/** Present exactly when `breaks` is not empty. */
|
|
20
|
+
readonly contract?: MigrationContract;
|
|
21
|
+
}
|
|
22
|
+
/** The one migration type that carries SQL. */
|
|
23
|
+
export interface SqliteMigration extends MigrationSpec {
|
|
24
|
+
/** DDL only, frozen once shipped; `migrations.lock.json` holds its sha256. */
|
|
25
|
+
readonly sql: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The `user_version` a store holds while every migration it has applied
|
|
29
|
+
* is additive: the length of the history 0.23.0 to 0.24.1 hold, and so
|
|
30
|
+
* the last version those releases accept.
|
|
31
|
+
*/
|
|
32
|
+
export declare const LEGACY_GATE_OPEN = 13;
|
|
33
|
+
/**
|
|
34
|
+
* The `user_version` a store holds once a breaking migration is applied:
|
|
35
|
+
* past any version a pre-log release counts to, so every such release
|
|
36
|
+
* refuses the store.
|
|
37
|
+
*/
|
|
38
|
+
export declare const LEGACY_GATE_CLOSED = 1000;
|
|
39
|
+
/**
|
|
40
|
+
* The schema's history, by name. This module's note says why an entry
|
|
41
|
+
* is frozen, what its `breaks` declares, and who still reads the first
|
|
42
|
+
* thirteen by position.
|
|
43
|
+
*/
|
|
44
|
+
export declare const SQLITE_MIGRATIONS: readonly SqliteMigration[];
|
|
45
|
+
/**
|
|
46
|
+
* The length of the history. `migrateSchema` in `sqlite.ts`, which
|
|
47
|
+
* tests plant pre-log stores with, brings a store to this
|
|
48
|
+
* `user_version` and refuses one past it, as the pre-log releases do.
|
|
49
|
+
*/
|
|
50
|
+
export declare const SQLITE_SCHEMA_VERSION: number;
|
|
51
|
+
/**
|
|
52
|
+
* The `user_version` a store is given when it holds `applied`. The
|
|
53
|
+
* value is {@link LEGACY_GATE_OPEN} when every entry declares no
|
|
54
|
+
* `breaks`, and {@link LEGACY_GATE_CLOSED} otherwise. Any word counts
|
|
55
|
+
* as a break, including one this rafa does not know, because a pre-log
|
|
56
|
+
* release can use the store only when nothing in it breaks.
|
|
57
|
+
*/
|
|
58
|
+
export declare function legacyGate(applied: readonly {
|
|
59
|
+
readonly breaks: readonly string[];
|
|
60
|
+
}[]): number;
|
|
61
|
+
/** The sha256 of a migration's SQL, as lowercase hex over its UTF-8 bytes. */
|
|
62
|
+
export declare function migrationChecksum(migration: Pick<SqliteMigration, 'sql'>): string;
|
|
63
|
+
/** The lock file's shape: each migration's sha256, keyed by its id. */
|
|
64
|
+
export type MigrationLock = Readonly<Record<string, string>>;
|
|
65
|
+
/** One way the migrations and their lock disagree. */
|
|
66
|
+
export type LockMismatch =
|
|
67
|
+
/** The entry's SQL hashes to something other than its lock line. */
|
|
68
|
+
{
|
|
69
|
+
readonly kind: 'edited';
|
|
70
|
+
readonly id: string;
|
|
71
|
+
readonly locked: string;
|
|
72
|
+
readonly computed: string;
|
|
73
|
+
}
|
|
74
|
+
/** The entry has no lock line. */
|
|
75
|
+
| {
|
|
76
|
+
readonly kind: 'unlocked';
|
|
77
|
+
readonly id: string;
|
|
78
|
+
readonly computed: string;
|
|
79
|
+
}
|
|
80
|
+
/** The lock line has no entry, so a migration was removed or renamed. */
|
|
81
|
+
| {
|
|
82
|
+
readonly kind: 'missing';
|
|
83
|
+
readonly id: string;
|
|
84
|
+
readonly locked: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Every way `migrations` and `lock` disagree. Entries come first, in
|
|
88
|
+
* array order, followed by the lock lines that no entry matches, in
|
|
89
|
+
* lock order. An empty answer means the lock holds exactly these
|
|
90
|
+
* entries, each with the checksum of its SQL.
|
|
91
|
+
*/
|
|
92
|
+
export declare function lockMismatches(migrations: readonly SqliteMigration[], lock: MigrationLock): readonly LockMismatch[];
|