@open-tomato/rafa 0.44.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.
Files changed (142) hide show
  1. package/README.md +280 -42
  2. package/dist/PROMPT.md +18 -1
  3. package/dist/adapters/logger/settings.d.ts +132 -0
  4. package/dist/bugs/closest.d.ts +19 -0
  5. package/dist/bugs/fenced.d.ts +8 -0
  6. package/dist/bugs/places.d.ts +20 -0
  7. package/dist/bugs/record.d.ts +145 -0
  8. package/dist/bugs/title.d.ts +83 -0
  9. package/dist/bundled/agents/doc-updater.md +17 -3
  10. package/dist/bundled/agents/loop-implementer.md +14 -2
  11. package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +10 -3
  12. package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +5 -0
  13. package/dist/bundled/skills/dev-planner/SKILL.md +120 -24
  14. package/dist/bundled/skills/documentation/SKILL.md +2 -12
  15. package/dist/cli.js +72387 -59411
  16. package/dist/config/entry.d.ts +119 -0
  17. package/dist/config/index.d.ts +36 -0
  18. package/dist/config/index.js +51 -0
  19. package/dist/config/shape.d.ts +232 -0
  20. package/dist/config-registry.d.ts +127 -0
  21. package/dist/config-schema-board-project.d.ts +157 -0
  22. package/dist/config-schema-bugs.d.ts +71 -0
  23. package/dist/config-schema-errors.d.ts +54 -0
  24. package/dist/config-schema-features.d.ts +64 -0
  25. package/dist/config-schema-hub.d.ts +150 -0
  26. package/dist/config-schema-issues.d.ts +95 -0
  27. package/dist/config-schema-logger.d.ts +91 -0
  28. package/dist/config-schema-loop-continue.d.ts +122 -0
  29. package/dist/config-schema-loop-retries.d.ts +94 -0
  30. package/dist/config-schema-release.d.ts +307 -0
  31. package/dist/config-schema-tests.d.ts +170 -0
  32. package/dist/config-schema-triage.d.ts +142 -0
  33. package/dist/config-schema-wrap-up.d.ts +96 -0
  34. package/dist/core/command/command.d.ts +228 -0
  35. package/dist/core/command/confirm.d.ts +67 -0
  36. package/dist/core/command/index.d.ts +112 -0
  37. package/dist/core/command/lazy.d.ts +54 -0
  38. package/dist/core/command/registry-types.d.ts +96 -0
  39. package/dist/core/command/running.d.ts +96 -0
  40. package/dist/core/command/spends.d.ts +39 -0
  41. package/dist/core/command/types.d.ts +92 -0
  42. package/dist/core/command/version.d.ts +4 -0
  43. package/dist/core/config/branch-template.d.ts +20 -0
  44. package/dist/core/config/config-items.d.ts +86 -0
  45. package/dist/core/config/config-load.d.ts +47 -0
  46. package/dist/core/config/config-origins.d.ts +57 -0
  47. package/dist/core/config/config-readers.d.ts +167 -0
  48. package/dist/core/config/config-schema.d.ts +207 -0
  49. package/dist/core/config/config-sections.d.ts +493 -0
  50. package/dist/core/config/config.d.ts +320 -0
  51. package/dist/core/config/core-section.d.ts +39 -0
  52. package/dist/core/config/dangerous.d.ts +58 -0
  53. package/dist/core/config/default-routing.d.ts +16 -0
  54. package/dist/core/config/index.d.ts +141 -0
  55. package/dist/core/config/scope.d.ts +42 -0
  56. package/dist/core/config/section.d.ts +206 -0
  57. package/dist/core/config/sections.d.ts +45 -0
  58. package/dist/core/config/standard-schema.d.ts +27 -0
  59. package/dist/core/git/commit.d.ts +232 -0
  60. package/dist/core/git/gh-runner.d.ts +24 -0
  61. package/dist/core/git/git.d.ts +22 -0
  62. package/dist/core/git/index.d.ts +85 -0
  63. package/dist/core/loop/claude.d.ts +388 -0
  64. package/dist/core/loop/hop-record.d.ts +52 -0
  65. package/dist/core/loop/index.d.ts +179 -0
  66. package/dist/core/loop/session-env.d.ts +20 -0
  67. package/dist/core/loop/session-record-parse.d.ts +63 -0
  68. package/dist/core/loop/sessions.d.ts +408 -0
  69. package/dist/core/loop/suite-failure.d.ts +25 -0
  70. package/dist/core/output/active.d.ts +100 -0
  71. package/dist/core/output/stream.d.ts +20 -0
  72. package/dist/core/output/text.d.ts +55 -0
  73. package/dist/core/plan/block-reader.d.ts +46 -0
  74. package/dist/core/plan/blocks.d.ts +139 -0
  75. package/dist/core/plan/declaration.d.ts +366 -0
  76. package/dist/core/plan/plan-stamp.d.ts +75 -0
  77. package/dist/core/runtime/identity.d.ts +21 -0
  78. package/dist/core/runtime/index.d.ts +46 -0
  79. package/dist/effort/store/bring-forward.d.ts +131 -0
  80. package/dist/effort/store/check-rows.d.ts +31 -0
  81. package/dist/effort/store/commit-stats.d.ts +42 -0
  82. package/dist/effort/store/copy.d.ts +35 -0
  83. package/dist/effort/store/development-build.d.ts +105 -0
  84. package/dist/effort/store/effort-levels.d.ts +13 -0
  85. package/dist/effort/store/findings.d.ts +214 -0
  86. package/dist/effort/store/fix-schema.d.ts +184 -0
  87. package/dist/effort/store/index.js +10 -7
  88. package/dist/effort/store/location.d.ts +44 -0
  89. package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
  90. package/dist/effort/store/merge-conflicts.d.ts +91 -0
  91. package/dist/effort/store/merge-rules.d.ts +82 -0
  92. package/dist/effort/store/merge-store.d.ts +211 -0
  93. package/dist/effort/store/merge-union.d.ts +92 -0
  94. package/dist/effort/store/migrate.d.ts +146 -0
  95. package/dist/effort/store/migrations.d.ts +92 -0
  96. package/dist/effort/store/ndjson-files.d.ts +114 -0
  97. package/dist/effort/store/origins.d.ts +116 -0
  98. package/dist/effort/store/plan-stubs.d.ts +79 -0
  99. package/dist/effort/store/rebuild-aside.d.ts +172 -0
  100. package/dist/effort/store/schema-plan.d.ts +102 -0
  101. package/dist/effort/store/session-row-types.d.ts +64 -0
  102. package/dist/effort/store/settings.d.ts +13 -0
  103. package/dist/effort/store/sqlite.d.ts +340 -0
  104. package/dist/effort/store/store-generation.d.ts +17 -0
  105. package/dist/effort/store/store-identity.d.ts +220 -0
  106. package/dist/effort/store/store-meta.d.ts +305 -0
  107. package/dist/effort/store/store-replaced.d.ts +72 -0
  108. package/dist/effort/store/types.d.ts +223 -0
  109. package/dist/errors/codes.d.ts +56 -0
  110. package/dist/errors/rafa-codes.d.ts +36 -0
  111. package/dist/index-2a7nb3an.js +125 -0
  112. package/dist/{index-57mmzkcn.js → index-39kbcw68.js} +516 -217
  113. package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
  114. package/dist/index-cy98y87p.js +2597 -0
  115. package/dist/index-gqz4jn6j.js +13447 -0
  116. package/dist/index-gvek4r8m.js +710 -0
  117. package/dist/index-j4k2vdz3.js +1125 -0
  118. package/dist/index-jh1r4kcn.js +1067 -0
  119. package/dist/index-ykggr043.js +540 -0
  120. package/dist/index-z1w83f81.js +4 -0
  121. package/dist/index.js +16091 -22798
  122. package/dist/learning/index.js +5 -6
  123. package/dist/plan/index.js +12 -11
  124. package/dist/plan-prompt.md +11 -2
  125. package/dist/ports/index.d.ts +732 -0
  126. package/dist/ports/learning.d.ts +101 -0
  127. package/dist/ports/log-level.d.ts +13 -0
  128. package/dist/ports/spec-review.d.ts +66 -0
  129. package/dist/pr/checks.d.ts +133 -0
  130. package/dist/pr/types.d.ts +422 -0
  131. package/dist/ready-4f8edezt.js +2016 -0
  132. package/dist/release-templates/release-bot-fragment.yml +153 -0
  133. package/dist/release-templates/release-publish.yml +110 -0
  134. package/dist/release-templates/release-settle.yml +122 -0
  135. package/dist/release-templates/release-tag.yml +102 -0
  136. package/dist/report/parse.d.ts +415 -0
  137. package/dist/schema/project-id.d.ts +79 -0
  138. package/dist/schema/tiers.d.ts +107 -0
  139. package/package.json +42 -4
  140. package/dist/index-6q30dtgm.js +0 -6342
  141. package/dist/index-cknayrpc.js +0 -2414
  142. package/dist/index-hf2k497d.js +0 -16
@@ -0,0 +1,214 @@
1
+ /**
2
+ * The findings writer: one row in the store's `findings` table for each
3
+ * entry of a task report's findings list.
4
+ *
5
+ * A task session ends its output with a `rafa:report` block,
6
+ * `parseReport` reads its findings list entry for entry, and
7
+ * {@link writeFindings} stores it. Each row is the `Sighting` a later
8
+ * phase promotes instincts from, and what the loop renders
9
+ * `progress.txt` from at dispatch.
10
+ *
11
+ * ## The row
12
+ *
13
+ * | Column | From |
14
+ * | --- | --- |
15
+ * | `id` | generated, `randomUUID` unless a seam replaces it |
16
+ * | `session_id`, `plan_stub`, `task_line` | the dispatch |
17
+ * | `kind`, `trigger`, `what`, `cause`, `resolution`, `artifact`, `signal` | the report entry, as parsed |
18
+ * | `outcome` | the loop: `done`, `blocked` or `failed` |
19
+ * | `tracker_ref` | null on every row this writer writes; `tracker-refs.ts` sets it |
20
+ * | `collected_at` | the write's time, ISO 8601, one per write |
21
+ * | `origin_store`, `origin_seq` | the store's origin and the row's own `seq`, NULL in both when unminted (`origins.ts`) |
22
+ *
23
+ * `seq` comes first, the append order, as in every table of the store.
24
+ * An entry's `extras` are not stored: the table has a column for each
25
+ * field the report defines, and none for a key it does not.
26
+ *
27
+ * ## Where the table lives, and why it is not a kind
28
+ *
29
+ * In the SQLite store's file, created by the second entry of
30
+ * `SQLITE_MIGRATIONS` and written through `writeSqliteStore`, as every
31
+ * kind's append is. It is written there whichever backend the `store`
32
+ * setting selects: the NDJSON backend has no findings file. The table
33
+ * stays outside the port's row map on purpose, because a finding breaks
34
+ * three things the port holds of a kind:
35
+ *
36
+ * - A kind is deduplicated by one key, which is also what a collector
37
+ * skips by. A finding is deduplicated by one of two keys, and nothing
38
+ * collects it.
39
+ * - A kind's row is stored whole as JSON, so both backends hold it
40
+ * byte for byte. A finding has a column per field, which is what the
41
+ * spec names and what a query filters on.
42
+ * - A kind's row is the same however often it is collected. A finding
43
+ * row carries a generated id and the time it was written.
44
+ *
45
+ * ## Deduplication
46
+ *
47
+ * Within one session, an entry duplicates an earlier one when it carries
48
+ * the same `artifact`, or, carrying none, the same `trigger` and `what`.
49
+ * The earlier row stays, whether it is on disk or earlier in the same
50
+ * write, and nothing of the later entry is kept: two entries differing
51
+ * only in `resolution` are one row.
52
+ *
53
+ * - The match is exact: byte for byte, no trimming, no case folding.
54
+ * `artifact` is the string a recurrence is matched on, and a looser
55
+ * match here would merge findings the report kept apart.
56
+ * - An entry with an artifact never duplicates one without, even with
57
+ * the same trigger and what. Each is keyed by its own rule, and the
58
+ * two keys never meet.
59
+ * - The scope is the SESSION, never the table. Promotion keys on one
60
+ * artifact sighted by different tasks or sessions, and phase 1
61
+ * comments on the issue a recurrence in a later task already filed.
62
+ * A table-wide dedupe would keep one row per artifact ever, and
63
+ * nothing could recur. Within a session, a report written twice
64
+ * writes its rows once.
65
+ *
66
+ * Two partial unique indexes enforce it, one per rule, and the insert
67
+ * names both as conflict targets. Measured with bun 1.3.14 on SQLite
68
+ * 3.51.0:
69
+ *
70
+ * - A target has to repeat its index's `WHERE`. Without it the insert
71
+ * throws `ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE
72
+ * constraint`.
73
+ * - Only those two conflicts are absorbed. An id generated twice
74
+ * throws `UNIQUE constraint failed: findings.id` and rolls the whole
75
+ * write back, where a bare `ON CONFLICT DO NOTHING` would drop the
76
+ * second entry as if it were a duplicate.
77
+ *
78
+ * ## What is refused, and at which level
79
+ *
80
+ * The loop's own inputs refuse the WHOLE write: an empty session id, an
81
+ * outcome outside {@link FINDING_OUTCOMES}, or dispatch text holding a
82
+ * lone UTF-16 surrogate. The write throws before the store is opened,
83
+ * so it leaves no file behind and changes no byte of one that exists.
84
+ * Code supplies these, and a wrong one is a bug to surface, not a row to
85
+ * store. The check runs even when the findings list is empty.
86
+ *
87
+ * A report ENTRY is refused alone, and the rest are written. A model
88
+ * wrote the entries, and one it got wrong must not cost the findings it
89
+ * got right. That is where this writer departs from the port, which
90
+ * refuses a whole batch for one keyless row. A refused entry is never
91
+ * stored, and is answered in {@link FindingsWriteResult.rejected}:
92
+ *
93
+ * - `unstorable-field`: a value `parseReport` never answers. A text
94
+ * field that is not a string, is blank, or holds a lone UTF-16
95
+ * surrogate, or a kind or signal outside its closed set. A blank key
96
+ * would merge every blank one, and a lone surrogate is bound lossily
97
+ * (see `sqlite.ts`), merging distinct keys.
98
+ * - `unkeyed`: no artifact, and no trigger or no what. Nothing could
99
+ * deduplicate the row, so every rewrite would add it again.
100
+ *
101
+ * The table refuses those rows itself, through its CHECK constraints, so
102
+ * a row written from outside cannot hold what this writer rejects. The
103
+ * kind and signal sets are spelled in the migration, which never changes
104
+ * once shipped: widening either is a new migration, and the suite pins
105
+ * the migration's sets to `FINDING_KINDS` and `FINDING_SIGNALS`.
106
+ *
107
+ * `outcome` has no CHECK. The spec adds the CI verdict to it once one is
108
+ * known, and SQLite cannot change a CHECK in place: measured, `ALTER
109
+ * TABLE ... ADD CHECK` is a syntax error, so a widened set would mean
110
+ * rebuilding the table. The closed set is enforced here instead.
111
+ *
112
+ * ## Writing nothing writes nothing, and still checks the schema
113
+ *
114
+ * A write left with no entry to insert, because the list was empty or
115
+ * every entry was refused, inserts no row. On a store that does not
116
+ * exist it opens nothing, and creates no file and no directory. On one
117
+ * that exists it still opens the store, through `writeSqliteStore`, so
118
+ * the schema check runs as it does for a write with rows: a store past
119
+ * this rafa's version is refused with no byte changed, one below it is
120
+ * brought forward, and one already at this version changes no byte.
121
+ */
122
+ import type { ReportFinding } from '../../report/parse.js';
123
+ /**
124
+ * What the loop made of a task, as its findings rows record it. The
125
+ * report's own `status` is the session's claim; this is the loop's.
126
+ */
127
+ export declare const FINDING_OUTCOMES: readonly ["done", "blocked", "failed"];
128
+ /** One of the loop's outcomes. */
129
+ export type FindingOutcome = (typeof FINDING_OUTCOMES)[number];
130
+ /** The dispatch a report came from. */
131
+ export interface FindingsDispatch {
132
+ /** The task session's id. Non-empty: it scopes deduplication. */
133
+ readonly sessionId: string;
134
+ /** The plan's stub, or null when the dispatch resolved none. */
135
+ readonly planStub: string | null;
136
+ /** The task line, as the dispatch quoted it. */
137
+ readonly taskLine: string;
138
+ }
139
+ /** One write: a report's findings, where they came from, and the outcome. */
140
+ export interface FindingsWrite {
141
+ readonly dispatch: FindingsDispatch;
142
+ readonly outcome: FindingOutcome;
143
+ /** The report's findings list, as `parseReport` answers it. */
144
+ readonly findings: readonly ReportFinding[];
145
+ }
146
+ /** Seams for the two values a write generates. */
147
+ export interface FindingsWriterSeams {
148
+ /** The write's time. Called once per write. Defaults to the clock. */
149
+ readonly now?: () => Date;
150
+ /** One row's id. Called once per stored entry. Defaults to `randomUUID`. */
151
+ readonly newId?: () => string;
152
+ }
153
+ /** Why one entry was not written. */
154
+ export type FindingRejectionReason =
155
+ /** A field holds a value `parseReport` never answers. */
156
+ 'unstorable-field'
157
+ /** No artifact, and no trigger or no what, to deduplicate it by. */
158
+ | 'unkeyed';
159
+ /** One entry that was not written, and why. */
160
+ export interface FindingRejection {
161
+ /** The entry's index in the findings list. */
162
+ readonly index: number;
163
+ readonly reason: FindingRejectionReason;
164
+ /** The field at fault, by its report key, or null for `unkeyed`. */
165
+ readonly field: string | null;
166
+ /** One sentence for an operator to read. */
167
+ readonly text: string;
168
+ }
169
+ /** What one write did. */
170
+ export interface FindingsWriteResult {
171
+ /** The store's file, whether or not anything was written to it. */
172
+ readonly path: string;
173
+ /** Rows written. */
174
+ readonly appended: number;
175
+ /** Entries the session already held, on disk or earlier in the write. */
176
+ readonly skipped: number;
177
+ /**
178
+ * Entries refused, in list order. With these, `appended + skipped +
179
+ * rejected.length` is the findings list's length.
180
+ */
181
+ readonly rejected: readonly FindingRejection[];
182
+ }
183
+ /**
184
+ * A value as a refusal quotes it. Never serialises an object. Exported
185
+ * for the triage writer, so its refusals quote a value the same way.
186
+ */
187
+ export declare function describeValue(value: unknown): string;
188
+ /**
189
+ * Why a text value cannot be stored, or null when it is null or can be.
190
+ * Exported for the triage writer, whose text fields are held to it.
191
+ */
192
+ export declare function textProblem(value: unknown): string | null;
193
+ /**
194
+ * Throws unless a dispatch and an outcome can be stored on every row of
195
+ * one write. `subject` names the write in the refusal: `findings` here.
196
+ *
197
+ * Exported for the triage writer, so every table one report fills
198
+ * refuses a dispatch by the same rule and in the same words. A writer
199
+ * whose table has no outcome column passes null, and only the dispatch
200
+ * is checked; `changes.ts` is the one that does.
201
+ */
202
+ export declare function checkDispatch(subject: string, dispatch: FindingsDispatch, outcome: FindingOutcome | null): void;
203
+ /**
204
+ * Writes one report's findings for one dispatch, one row per entry the
205
+ * session does not already hold.
206
+ *
207
+ * Throws, having opened nothing, when the dispatch or the outcome cannot
208
+ * be stored. Throws the store's own refusal of a schema past this rafa's
209
+ * version whenever the store exists, even with nothing to insert. An
210
+ * entry that cannot be stored is left out and answered in `rejected`,
211
+ * and the others are written. See the module note for the dedupe rule
212
+ * and each refusal.
213
+ */
214
+ export declare function writeFindings(repoRoot: string, write: FindingsWrite, seams?: FindingsWriterSeams): FindingsWriteResult;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The repair `rafa effort fix-schema` runs: a SQLite store this rafa
3
+ * refuses, rebuilt at the migrations this rafa knows beside the live
4
+ * file, checked, and swapped in with the original kept as a backup.
5
+ *
6
+ * ## Which stores it repairs
7
+ *
8
+ * The decision is `planSchema`'s (`schema-plan.ts`), asked for a write
9
+ * from an ordinary open, as `rafa effort schema` asks it: a runtime that
10
+ * reads and writes the store is refused whatever a read alone would be.
11
+ * The store's `user_version` and migration log are read on a read-only
12
+ * connection, so a store that needs no repair keeps its bytes.
13
+ *
14
+ * - A store the plan lets this rafa use is left alone: `current` when
15
+ * nothing is to be adopted or applied, `behind` when the next
16
+ * ordinary open brings it forward. A store logging migrations this
17
+ * rafa does not know, each additive, is one of these, since this
18
+ * rafa already reads and writes through them.
19
+ * - Five refusals are repaired by a rebuild ({@link REPAIRABLE_REASONS}):
20
+ * `pre-log-unreleased`, a store a branch build migrated past the
21
+ * legacy entries with no log; `gate-mismatch`, a logged store a
22
+ * build older than the log migrated; `edited`, a logged checksum
23
+ * this rafa holds otherwise; and `unknown-breaks-readers` and
24
+ * `unknown-breaks-writers`, a logged migration this rafa does not
25
+ * know which breaks it.
26
+ * - The other two, `breaking-out-of-order` and `breaking-pending`, are
27
+ * about migrations this rafa knows and has not applied, which a
28
+ * rebuild at those same migrations does not answer. They are refused
29
+ * with the plan's own text, which ends with the command to run next.
30
+ *
31
+ * Installing a rafa that knows the store's migrations comes first
32
+ * wherever it can: a rebuild leaves what only the newer schema holds in
33
+ * the backup.
34
+ *
35
+ * ## The rebuild
36
+ *
37
+ * A parallel file, `<store>.fix-<stamp>`, is brought to this rafa's
38
+ * migrations by the log-aware apply (`bringForward`, `bring-forward.ts`),
39
+ * so it holds exactly the tables this rafa writes and a migration log
40
+ * with a row for every id this rafa knows, each `applied_by` naming this
41
+ * runtime ({@link appliedByName}: its version, with `+dev:<checkout>`
42
+ * for a development build). The live store is attached, and every table
43
+ * the parallel file holds, the log apart, is filled from the live table
44
+ * of the same name over the columns the parallel table has. A table or
45
+ * column this rafa does not know is not copied, and is reported as left
46
+ * behind with how many rows or values it held. So are the unknown
47
+ * migrations the live log names: they stay in the backup only, since an
48
+ * object copied into the rebuild with no log row would make the release
49
+ * that ships its migration fail on `CREATE TABLE`. The rebuild is
50
+ * refused when the live store lacks a table or a column this rafa
51
+ * knows, since copying around it would lose what this rafa writes.
52
+ *
53
+ * The parallel file is checked before anything else happens: the row
54
+ * count of every table copied must equal the live one, SQLite's
55
+ * `integrity_check` must answer `ok`, and `planSchema` must find it a
56
+ * store this rafa uses as it is. Under `dryRun` it is then deleted, so a
57
+ * dry run can be repeated and leaves the directory as it found it.
58
+ * Otherwise the live store is written out to
59
+ * `<store>.v<user_version>-<stamp>.bak` with `VACUUM INTO`, its identity
60
+ * is carried onto the parallel file (`carryStoreIdentity`,
61
+ * `store-meta.ts`), and the parallel file is renamed into its place.
62
+ * The rebuild keeps the live store's origin, so the rows it writes next
63
+ * carry the same store id; a store whose next write would have minted
64
+ * anyway is carried nothing and mints after the swap. The backup holds
65
+ * the whole original, left-behind data included, and restoring it is
66
+ * renaming it back. It is a copy with an inode of its own, so once
67
+ * renamed back over the store its first writing open mints a new
68
+ * origin (`store-identity.ts`).
69
+ *
70
+ * The live file is only ever read. A rollback journal or write-ahead log
71
+ * beside it means a write in flight or interrupted, so the repair is
72
+ * refused while either is there. Any failure while building removes the
73
+ * parallel file and leaves the live one untouched.
74
+ *
75
+ * Those steps, from the in-flight refusal to the swap, are the
76
+ * build-aside convention of `rebuild-aside.ts`, which this module runs
77
+ * with its own file names and its own build. `SchemaFixRefusal` is that
78
+ * module's `RebuildRefusal` under the name the command catches.
79
+ *
80
+ * ## A development build never swaps
81
+ *
82
+ * A development build (`readRuntimeIdentity`,
83
+ * `packages/rafa/src/core/runtime/identity.ts`) builds, checks and deletes a rebuild
84
+ * under `dryRun`, and is refused the swap before anything is built: its
85
+ * migrations may be a branch's, which no installed runtime knows. The
86
+ * parallel file is brought forward with `builtAside`, so the open's own development-build question, which
87
+ * would refuse a file in the project's store directory, is not asked of
88
+ * it; this refusal is that question, asked of the swap.
89
+ *
90
+ * It opens the store itself rather than through `withSqliteStore`, so it
91
+ * runs the test guard (`guardTestProcess`, `location.ts`) itself too,
92
+ * before it reads or makes anything.
93
+ */
94
+ import type { SqliteMigration } from './migrations.js';
95
+ import type { TableRows } from './rebuild-aside.js';
96
+ import type { LoggedMigration, StoreSchema } from './schema-plan.js';
97
+ import type { RuntimeIdentity } from '../../core/runtime/index.js';
98
+ import { Database } from 'bun:sqlite';
99
+ /** What the repair found, and what it did about it. */
100
+ export type FixSchemaStatus =
101
+ /** No store file: nothing to repair, and none is created. */
102
+ 'missing'
103
+ /** This rafa uses it as it is: nothing to repair. */
104
+ | 'current'
105
+ /** This rafa uses it once the next ordinary open brings it forward: nothing to repair. */
106
+ | 'behind'
107
+ /** Refused for a reason a rebuild repairs, under `dryRun`: the rebuild was made, checked and deleted. */
108
+ | 'would-rebuild'
109
+ /** Refused for a reason a rebuild repairs: the rebuild was swapped in and the original kept as a backup. */
110
+ | 'rebuilt';
111
+ /** The `planSchema` refusals a rebuild at this rafa's migrations repairs, in the order they are tried. */
112
+ export declare const REPAIRABLE_REASONS: readonly ['pre-log-unreleased', 'gate-mismatch', 'edited', 'unknown-breaks-readers', 'unknown-breaks-writers'];
113
+ /** A refusal a rebuild repairs. */
114
+ export type RepairReason = typeof REPAIRABLE_REASONS[number];
115
+ /** A table the rebuild copies, with the rows it holds. */
116
+ export type KeptTable = TableRows;
117
+ /** A table only the newer schema knows, with the rows the backup keeps. */
118
+ export interface LeftTable {
119
+ readonly table: string;
120
+ readonly rows: number;
121
+ }
122
+ /** A column only the newer schema knows, with its non-null values the backup keeps. */
123
+ export interface LeftColumn {
124
+ readonly table: string;
125
+ readonly column: string;
126
+ readonly values: number;
127
+ }
128
+ /** The outcome of one repair. */
129
+ export interface FixSchemaResult {
130
+ /** The live store's file. */
131
+ readonly path: string;
132
+ readonly status: FixSchemaStatus;
133
+ /** The live store's `user_version`, or null when there is no file. */
134
+ readonly storeVersion: number | null;
135
+ /** Whether the live store holds a migration log. */
136
+ readonly logged: boolean;
137
+ /** The refusal a rebuild repairs, or null when the store needs none. */
138
+ readonly reason: RepairReason | null;
139
+ /** That refusal's text without its way out, since the repair is that way out, or null. */
140
+ readonly refusal: string | null;
141
+ /** The ids this rafa knows, in catalogue order: the ones a rebuild logs. */
142
+ readonly known: readonly string[];
143
+ /** Who a rebuild's log rows name as applying them. */
144
+ readonly appliedBy: string;
145
+ /** What the next ordinary open records, for `behind`: the log first when it adopts, then each pending id. */
146
+ readonly pending: readonly string[];
147
+ /**
148
+ * Logged migrations this rafa does not know. For `current` and
149
+ * `behind` each is one this rafa uses the store through; for a
150
+ * rebuild they stay in the backup only.
151
+ */
152
+ readonly unknown: readonly LoggedMigration[];
153
+ /** Every table copied; empty unless a rebuild was made. */
154
+ readonly kept: readonly KeptTable[];
155
+ readonly leftTables: readonly LeftTable[];
156
+ readonly leftColumns: readonly LeftColumn[];
157
+ /** Where the original went, for `rebuilt` only. */
158
+ readonly backupPath: string | null;
159
+ }
160
+ /** What one repair is handed. */
161
+ export interface FixSchemaOptions {
162
+ /** The live store's file. */
163
+ readonly path: string;
164
+ /** Build and check the rebuild, then delete it instead of swapping it in. */
165
+ readonly dryRun: boolean;
166
+ /** Names the parallel and backup files, so two runs never collide. */
167
+ readonly stamp: string;
168
+ /** The migrations to rebuild at: this build's unless another list is passed. */
169
+ readonly migrations?: readonly SqliteMigration[];
170
+ /** Which build runs; `readRuntimeIdentity()` by default. */
171
+ readonly identity?: RuntimeIdentity;
172
+ /** The clock a rebuild's `applied_at` is read from. */
173
+ readonly now?: () => Date;
174
+ }
175
+ /** A repair refused before the live store was changed: the build-aside steps' refusal. */
176
+ export { RebuildRefusal as SchemaFixRefusal } from './rebuild-aside.js';
177
+ /** The user tables one attached schema holds, by name, the migration log apart. */
178
+ export declare function tableNames(db: Database, schema: string): string[];
179
+ /** The file's `user_version` and migration log, read on a read-only connection. */
180
+ export declare function readOnlySchema(path: string): StoreSchema;
181
+ /** Refuses a rebuild `planSchema` would not let this rafa use as it is. */
182
+ export declare function refuseUnusable(db: Database, parallelPath: string, migrations: readonly SqliteMigration[]): void;
183
+ /** Repairs the store at `options.path`. See the module note. */
184
+ export declare function fixStoreSchema(options: FixSchemaOptions): FixSchemaResult;
@@ -1,7 +1,5 @@
1
1
  // @bun
2
2
  import {
3
- EFFORT_KEY_PROJECTIONS,
4
- TRUSTED_PERMISSIONS,
5
3
  WIRE_FORMAT,
6
4
  WIRE_VERSION,
7
5
  WireExportRefusal,
@@ -12,14 +10,19 @@ import {
12
10
  materialiseWirePayload,
13
11
  mergeStore,
14
12
  openNdjsonStore,
15
- openSqliteStore,
16
13
  selectEffortStore
17
- } from "../../index-6q30dtgm.js";
14
+ } from "../../index-jh1r4kcn.js";
15
+ import {
16
+ EFFORT_KEY_PROJECTIONS,
17
+ TRUSTED_PERMISSIONS,
18
+ openSqliteStore
19
+ } from "../../index-cy98y87p.js";
20
+ import"../../index-2a7nb3an.js";
21
+ import"../../index-gvek4r8m.js";
18
22
  import {
19
23
  STORE_BACKENDS
20
- } from "../../index-cknayrpc.js";
21
- import"../../index-nv34ptht.js";
22
- import"../../index-hf2k497d.js";
24
+ } from "../../index-ykggr043.js";
25
+ import"../../index-z1w83f81.js";
23
26
  export {
24
27
  selectEffortStore,
25
28
  openSqliteStore,
@@ -0,0 +1,44 @@
1
+ /** The variable that moves the store of one command line. */
2
+ export declare const EFFORT_DIR_VARIABLE = "RAFA_EFFORT_DIR";
3
+ /** The variable that marks a process as a test's, whatever its entry. */
4
+ export declare const TEST_PROCESS_VARIABLE = "RAFA_TEST";
5
+ /** An environment, as `process.env` holds one. */
6
+ export type StoreEnvironment = Readonly<Record<string, string | undefined>>;
7
+ /** What the test guard reads about the process it runs in. */
8
+ export interface TestProbe {
9
+ /** The entry file, `Bun.main` by default. */
10
+ readonly main: string;
11
+ /** The environment, `process.env` by default. */
12
+ readonly env: StoreEnvironment;
13
+ /** The temporary directory, `tmpdir()` by default. */
14
+ readonly tempDir: string;
15
+ }
16
+ /**
17
+ * The directory `RAFA_EFFORT_DIR` names, or null when it is unset or
18
+ * empty. Throws when it is relative, or names the store under
19
+ * `repoRoot`; see the module note.
20
+ */
21
+ export declare function readEffortDirOverride(repoRoot: string, env?: StoreEnvironment): string | null;
22
+ /**
23
+ * The directory both backends keep the store under `repoRoot` in:
24
+ * `RAFA_EFFORT_DIR` when it is set, and `<root>/.rafa/effort` otherwise.
25
+ * Throws as {@link readEffortDirOverride} does.
26
+ */
27
+ export declare function effortStoreDir(repoRoot: string, env?: StoreEnvironment): string;
28
+ /**
29
+ * True for a test process: an entry file ending in `.test.ts`, or
30
+ * `RAFA_TEST` set to exactly `1`.
31
+ */
32
+ export declare function isTestProcess(main?: string, env?: StoreEnvironment): boolean;
33
+ /**
34
+ * True when `path` is the temporary directory `tempDir` or lies under
35
+ * it, as spelled or with both resolved to their real paths; a `path`
36
+ * not made yet resolves through its nearest existing ancestor.
37
+ */
38
+ export declare function isUnderTempDir(path: string, tempDir?: string): boolean;
39
+ /**
40
+ * Throws when a test process opens a store at `path` outside the
41
+ * temporary directory, and does nothing otherwise. Called before any
42
+ * file or directory is made; see the module note.
43
+ */
44
+ export declare function guardTestProcess(path: string, probe?: TestProbe): void;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The merge's commit gaps: once the union (`merge-union.ts`) has brought
3
+ * another store's commits in, each one's `minutesSincePrevious` and that
4
+ * of the commit right after it in time are recomputed from the
5
+ * timestamps the store holds.
6
+ *
7
+ * ## Why only those rows
8
+ *
9
+ * A commit's gap is the minutes since the commit before it in time
10
+ * (`assignElapsedMinutes`, `src/effort/commits.ts`), measured with
11
+ * {@link minutesBetween}. A commit brought in between two commits here
12
+ * changes exactly two gaps: its own, which the other store measured
13
+ * against its own neighbours, and that of the commit after it, which now
14
+ * follows it rather than the commit before it. Every other gap keeps its
15
+ * predecessor. So each commit brought in touches at most two rows, and a
16
+ * row two of them name is written once. Where the store held the gaps a
17
+ * full recompute over its commits gives, it holds them over the union
18
+ * afterwards too; a gap a collection measured over a narrower range,
19
+ * such as the null of an incremental run's first commit, is left as it
20
+ * was unless a commit brought in lands right before it.
21
+ *
22
+ * There is no git call: the order and every gap come from the
23
+ * `timestamp` in each row's `row_json`.
24
+ *
25
+ * ## The order
26
+ *
27
+ * Commits are ordered by `Date.parse` of their timestamp, as
28
+ * `assignElapsedMinutes` orders them, and two at the same instant by
29
+ * sha, so a merge run from either side orders a tie alike. A row whose
30
+ * `row_json` does not parse, or whose timestamp does not, is left out of
31
+ * the order as `assignElapsedMinutes` leaves it out: it is no one's
32
+ * predecessor, and one brought in takes a null gap. The first commit in
33
+ * the order takes null.
34
+ *
35
+ * ## The write
36
+ *
37
+ * The gap lives inside `row_json`, so a recompute rewrites that column,
38
+ * `JSON.stringify` of the row with the one key replaced, which keeps the
39
+ * row's other keys and their order. A row whose gap already reads the
40
+ * recomputed value is not written. `MERGE_RULES` (`merge-rules.ts`)
41
+ * declares `commits.row_json` edited under {@link RECOMPUTED} for this
42
+ * statement, and `merge-conflicts.ts` compares two rows of one commit
43
+ * without the gap through {@link withoutGap}.
44
+ */
45
+ import type { Database } from 'bun:sqlite';
46
+ /** The key of `commits.row_json` a merge recomputes. */
47
+ export declare const GAP_FIELD = "minutesSincePrevious";
48
+ /** One commit row whose gap the recompute rewrote. */
49
+ export interface GapRewrite {
50
+ readonly seq: number;
51
+ readonly sha: string;
52
+ readonly before: number | null;
53
+ readonly after: number | null;
54
+ }
55
+ /**
56
+ * `row_json` without its gap, for comparing two rows of one commit:
57
+ * the text unchanged when it is not a JSON object.
58
+ */
59
+ export declare function withoutGap(rowJson: string): string;
60
+ /**
61
+ * Recomputes, in `db`, the gap of each commit whose local `seq` is in
62
+ * `inserted` and of the commit right after it in time, as the module
63
+ * note says, in one transaction. Answers each row it rewrote, in time
64
+ * order; a row whose gap was already right, or whose `row_json` is not
65
+ * a JSON object, is not written and not answered. A `seq` no commit
66
+ * holds is ignored.
67
+ */
68
+ export declare function recomputeCommitGaps(db: Database, inserted: readonly number[]): GapRewrite[];
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The merge's second step: each pair of rows the union matched
3
+ * (`merge-union.ts`) is compared, an edited field the two stores
4
+ * disagree on is settled by its rule, and any difference no rule settles
5
+ * is recorded in `merge_conflicts`, both rows kept.
6
+ *
7
+ * ## Comparing a pair
8
+ *
9
+ * The two rows are compared on every column but `seq` and the origin
10
+ * pair. `seq` is local order, and the pair may differ in a match on
11
+ * identity by design: a row with a NULL origin on one side, or a commit
12
+ * two devices collected under an origin each, is the same row.
13
+ *
14
+ * - **Equal** content: nothing is written, and the pair is skipped.
15
+ * - **Differing only in edited fields** (`MERGE_RULES`, `merge-rules.ts`):
16
+ * each field follows its rule. Under {@link SET_ONCE} a filled value
17
+ * beats NULL from either side: NULL here takes the other store's value,
18
+ * and a value here is kept against NULL there; the pair is then
19
+ * skipped. Two different filled values mean one finding was filed
20
+ * twice: the value here is kept, and the incoming row is recorded with
21
+ * `field` naming the column. Under {@link RECOMPUTED} the two values
22
+ * are compared without the part the merge recomputes, through the
23
+ * field's entry in {@link RECOMPUTED_COMPARISONS}: `commits.row_json`
24
+ * without its `minutesSincePrevious` (`merge-commit-gaps.ts`). Equal
25
+ * so, the pair is skipped and the row here kept; the gap of a commit
26
+ * brought in is the merge's to rewrite, not a matched one's. Unequal,
27
+ * the rows differ outside every edited field, below.
28
+ * - **Any other difference**: the incoming row is recorded with `field`
29
+ * NULL, and the row here is left as it is, edited fields included. The
30
+ * same `(origin_store, origin_seq)` holding different content can only
31
+ * come from a copy the open did not catch, such as a cloned disk, and
32
+ * one UUID holding different content is as unexplained.
33
+ *
34
+ * A recorded conflict keeps the incoming row as the JSON of all its
35
+ * columns, its `seq` in the other store and its origin pair included,
36
+ * beside the local row's `seq`. Nothing blocks on it: the next pair is
37
+ * compared. A merge run again meets the same pair and records it again
38
+ * under its own merge id.
39
+ *
40
+ * ## The fill statements
41
+ *
42
+ * A fill is an `UPDATE` of a merged table, which `merge-rules.sweep.test.ts`
43
+ * reads from source and holds to `MERGE_RULES`; it can only read a
44
+ * statement whose table and column are spelled out. So each set-once
45
+ * field has its statement in {@link SET_ONCE_FILLS}, and a set-once
46
+ * field `MERGE_RULES` gains without one there makes the settling throw.
47
+ *
48
+ * The union's collisions, rows it refused on another UNIQUE key, are
49
+ * not matches and are not read here.
50
+ */
51
+ import type { TableUnion } from './merge-union.js';
52
+ import type { Database } from 'bun:sqlite';
53
+ /** The merge these conflicts are recorded under. */
54
+ export interface ConflictTrail {
55
+ /** The `merges.id` of the merge. */
56
+ readonly mergeId: string;
57
+ /** When the conflicts are recorded, as an ISO timestamp. */
58
+ readonly recordedAt: string;
59
+ }
60
+ /** An edited field this store took from the other store. */
61
+ export interface FieldFill {
62
+ readonly localSeq: number;
63
+ readonly field: string;
64
+ }
65
+ /** A pair recorded in `merge_conflicts`. */
66
+ export interface RecordedConflict {
67
+ readonly localSeq: number;
68
+ /** The incoming row's `seq` in the other store. */
69
+ readonly incomingSeq: number;
70
+ /** The edited field whose two values differ, or null when the rows differ outside every edited field. */
71
+ readonly field: string | null;
72
+ }
73
+ /** What the conflict step did with one merged table's matched pairs. */
74
+ export interface TableSettlement {
75
+ readonly table: string;
76
+ /** The local `seq` of each pair left with equal content, before or after its fills. */
77
+ readonly skipped: readonly number[];
78
+ readonly filled: readonly FieldFill[];
79
+ readonly conflicts: readonly RecordedConflict[];
80
+ }
81
+ /** The statement filling one set-once field of a row left NULL, by `<table>.<column>`. */
82
+ export declare const SET_ONCE_FILLS: Readonly<Record<string, string>>;
83
+ /** The comparable form of one recomputed field's value, by `<table>.<column>`. */
84
+ export declare const RECOMPUTED_COMPARISONS: Readonly<Record<string, (value: string) => string>>;
85
+ /**
86
+ * Settles every pair `unions` matched, as the module note says: fills
87
+ * the set-once fields `local` holds NULL in, and records each conflict
88
+ * in `local`'s `merge_conflicts` under `trail`, in one transaction.
89
+ * `other` is only read. Answers one entry per union entry, in its order.
90
+ */
91
+ export declare function settleMatches(local: Database, other: Database, unions: readonly TableUnion[], trail: ConflictTrail): TableSettlement[];