Commit 8f504e3
test: Parquet merge pipeline verification suite (#6369)
* test: add proptest for planner maturity filtering + shared test helpers
Property test verifies that no merge task ever contains a split that
should have been filtered: mature by ops (>= max_merge_ops), mature by
size (>= target), time-matured (created_at + maturation_period <= now),
or missing a window. Generates random splits across the maturity
boundary and tests at the actor level.
Also makes test helpers pub(super) so sibling test modules (sketch,
crash, multi-round) can reuse them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add sketch split integration test for Parquet merge pipeline
Verifies that sketch splits dispatch to the correct metastore RPCs
(stage_sketch_splits, publish_sketch_splits) and that the merged output
has ParquetSplitKind::Sketches with correct metadata.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add crash/restart and multi-round merge integration tests
Crash/restart test:
- Injects publish failure on 2nd call → pipeline detects failure,
kills actors, respawns
- Verifies list_metrics_splits called on respawn (re-seeding)
- Verifies pipeline generation >= 2 (respawn occurred)
- Verifies original splits eventually replaced (no data loss)
Multi-round merge test:
- 4 input splits → 2 round-1 merges → 1 round-2 merge
- Verifies num_merge_ops=2 on final output
- Verifies all original + intermediate splits replaced
- Verifies MC-1: total rows preserved across lifecycle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* spec: TLA+ specification for merge pipeline shutdown drain protocol
Models the two-phase shutdown: DisconnectMergePlanner breaks feedback,
RunFinalizeMergePolicyAndQuit drains cold windows, then in-flight
merges complete.
Safety invariants:
- NoSplitLoss: every merge input is published or in-flight
- NoDuplicateMerge: no split in multiple concurrent merges
- FinalizeWithinBound: at most MaxFinalizeOps finalize operations
- ShutdownOnlyWhenDrained: shutdown requires empty in-flight set
Liveness:
- ShutdownEventuallyCompletes under weak fairness
Two configs: _small (hundreds of states) and full (larger space).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add Stateright DST model for merge pipeline with crash/restart
Model checks 5 properties across ingest, merge, crash, and restart:
- MC-1 lifecycle: total rows preserved (no loss/duplication)
- Bounded WA: merge_ops never exceeds max_merge_ops
- No duplicate merge: no split in multiple concurrent merges
- No orphan after restart: all immature splits re-seeded
- MP-1: level homogeneity (by construction)
Small model (~instant), full model (~seconds, gated behind #[ignore]).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: nightly rustfmt import ordering across all new test files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: update TLA+ CLAUDE.md with macOS setup and spec catalog
- Document the actual tla2tools.jar path from brew cask tla+-toolbox
- Add "Run All Specs" one-liner for quick verification
- Add spec catalog with state counts and invariant names
- Remove stale references to brew formula (doesn't exist)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace match with `?` operator in CompleteMerge handler
CI clippy lint (`clippy::question-mark`, -D warnings) rejected the
explicit match-and-return-None pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* spec: extend MergePipelineShutdown to cover crash/restart and multi-lifetime
Phase A of the verification-gap closure (PR #6369). The original spec
only modeled orderly two-phase shutdown — no Crash action, no Restart,
no row-conservation claim. The four invariants were either trivially
true by construction or static set-membership, so the spec proved
nothing about the failure modes that motivated it.
This rewrite:
- Adds Crash and Restart actions. Crash invalidates in-memory state at
any point in the pipeline; uploaded-but-unpublished merge outputs
become orphan_outputs. Restart re-seeds cold_windows from durable
published_splits (models fetch_immature_splits).
- Splits CompleteMerge into UploadMergeOutput then PublishMergeAndFeedback
so a Crash between the two phases is reachable. This is what catches
the "leaked but invisible" failure mode — output blob in S3 with no
metastore reference. The model proves these are object-store-only
garbage (LeakIsObjectStoreOnly), not durable data loss.
- Adds split_rows / split_merge_ops / split_window ghost functions so
RowsConserved expresses the strong "no data loss, no duplication"
property: total ingested rows equal sum of rows in published_splits
in every reachable state.
- Adds NoOrphanWhenConnected (state invariant), RestartReSeedsAllImmature
(action property) and NoPersistentOrphan (liveness leads-to). Together
these capture: while connected the planner sees every immature split;
every Restart correctly recovers all immature splits; in any run with
restart budget remaining, orphans always eventually clear.
- Extends Restart to fire after graceful shutdown too (multi-lifetime),
so the cross-process recovery claim is explicit. Bounded by MaxRestarts.
The replaced NoOrphanAfterRestart invariant fired during initial TLC
runs — the failing trace was a *legitimate* state in production
(publish during shutdown disconnect leaves the output untracked by the
planner until the next process invocation). The fix wasn't to remove
the invariant but to split the safety claim from the recovery claim:
NoOrphanWhenConnected for the steady state, RestartReSeedsAllImmature
for the recovery transition, NoPersistentOrphan for cross-lifetime
liveness. Recorded as a sesh-mode rule: never silently weaken an
invariant that produced a counter-example.
Single config drops the small/full split — full config now runs in
~12s on a workstation (217,854 distinct states, 11 invariants + 3
temporal properties verified). The states/ directory dropped by TLC
is now gitignored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* spec: add merge-chain config alongside multi-lifetime primary
The primary `MergePipelineShutdown.cfg` is now optimized for multi-lifetime
exercise (MaxIngests=3, MaxRestarts=2) — 15,732 states in ~1s — covering
the cross-process recovery claim added in the previous commit.
The new `MergePipelineShutdown_chains.cfg` keeps MaxIngests=4 with
MaxRestarts=1 for deeper merge-chain coverage (217,854 states, ~10s):
exercises level-0 → level-1 → level-2-mature compaction so BoundedWriteAmp
is checked across the full chain and concurrent in-flight merges interact.
Combined run is ~11s. Both share the same invariant + property set; only
the constants differ. The TLA+ run-all loop in CLAUDE.md now globs every
.cfg and resolves the matching .tla, supporting any number of configs
per spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: share state and predicates between Stateright model and invariants
Phase B of the verification-gap closure (PR #6369). Eliminates drift
between the merge-pipeline model and any production-side checks by
making them evaluate the *same* Rust functions on the *same* state.
quickwit-dst/src/invariants/merge_pipeline.rs (new):
- MergePipelineState struct mirroring the TLA+ VARIABLES block
(planner_alive, in_flight_merges, cold_windows, published_splits,
splits, orphan_outputs, lifecycle counters, total_ingested_rows ghost)
- Pure-function predicates corresponding 1:1 to TLA+ invariants:
rows_conserved, bounded_write_amp, no_split_loss, no_duplicate_merge,
no_orphan_in_planner, no_orphan_when_connected, leak_is_object_store_only,
mp1_level_homogeneity, restart_re_seeds_all_immature
- Helper orphan_set mirrors the TLA+ OrphanSet operator
- 17 unit tests covering passing/failing paths for each predicate
quickwit-dst/src/invariants/registry.rs:
- Adds MP4..MP11 invariant IDs (preserved short-code naming convention)
- Each maps to a TLA+ invariant from MergePipelineShutdown.tla
quickwit-dst/src/models/merge_pipeline.rs:
- State type is now invariants::merge_pipeline::MergePipelineState
(literal sharing — no parallel definition, no conversion layer)
- CompleteMerge split into UploadMergeOutput + PublishMergeAndFeedback
to match the TLA+ multi-phase merge completion. A Crash between phases
orphans the upload (orphan_outputs) without losing data.
- Restart re-seeds cold_windows from durable published_splits (mirrors
fetch_immature_splits) and resets shutdown_complete / finalize state,
modelling a fresh process invocation. Bounded by max_restarts.
- DrainComplete added so graceful-shutdown is a distinct terminal
action (matching TLA+).
- properties() calls the shared predicate functions instead of inline
closures — model and runtime checks evaluate identical Rust code.
- Three configs: small (fast iteration), multi_lifetime (3 lifetimes,
matches MergePipelineShutdown.cfg), deep_chains (level 0→1→2 mature,
matches MergePipelineShutdown_chains.cfg).
- Restart's next_state debug_asserts MP-11 (restart_re_seeds_all_immature)
on its post-state, encoding the action property as a runtime check.
All 50 tests pass under `cargo nextest run -p quickwit-dst --features
model-checking` (33 invariant unit tests + 4 merge-pipeline model
configs + 13 other models). Multi-lifetime checks in 0.1s, deep-chains
in 0.6s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: wire merge-pipeline invariant checks into production code
Phase C of the verification-gap closure (PR #6369). Production code now
evaluates the *same* invariant predicates that the Stateright model and
TLA+ spec verify, with results forwarded to the existing DogStatsD
recorder (already wired in quickwit-cli/src/logger.rs). Every check
emits `pomsky.invariant.checked{invariant=MP-X}` and, on failure,
`pomsky.invariant.violated{invariant=MP-X}` — usable directly as
Datadog alert criteria.
ParquetMergeExecutor (post-merge, pre-uploader):
- MP-2 (HasMinimumSplits): merge has >= 2 inputs
- MP-1 (LevelHomogeneity): all inputs share num_merge_ops level
- MP-3 (ScopeCompatibility): all inputs share sort_fields and window
- MP-4 (RowsConserved): sum of input num_rows == sum of output num_rows.
Empty-output path checks input rows are all zero (otherwise data was
silently dropped). The non-empty path checks the strong row-preservation
property — the same MC-1 / RowsConserved invariant from the TLA+ spec.
ParquetMergePlanner::new (post-restart re-seed):
- MP-11 (RestartReSeedsAllImmature): every split the merge policy
classifies as still-immature (and which has a window for compaction)
is recorded in scoped_young_splits and known_split_ids. Filters out
mature splits, time-expired splits, and pre-Phase-31 splits before
comparing — these are intentional drops, not the failure mode MP-11
protects against.
Cargo.toml: quickwit-indexing now depends on quickwit-dst for the
shared invariant module and check_invariant! macro.
Also fixes a pre-existing rustdoc warning: the parquet_merge_planner
docs referenced `[`MergePlanner`]` as an intra-doc link, but the type
lives in another crate and can't resolve. Changed to plain backticks.
All 52 metrics_pipeline tests pass; clippy + nightly fmt + machete +
cargo doc clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: trace conformance — replay production events through the formal model
Phase D of the verification-gap closure (PR #6369). The TLA+ spec and
Stateright model are now backed by *production code that actually emits
events at every modeled transition*, and a test replays those events
through the same predicates the model verifies. A divergence between
production and the model surfaces here as a predicate failure on real
production state.
This closes the loop: TLA+ → Stateright (Phase A+B) → production checks
(Phase C) → trace conformance (Phase D).
quickwit-dst/src/events/merge_pipeline.rs (new):
- MergePipelineEvent enum with one variant per modeled action, using
production types (String split IDs, Range<i64> windows). Notably no
Crash variant — process death cannot emit anything; crashes are
inferred during replay by the absence of events between actions and a
subsequent Restart.
- Pluggable observer (fn pointer) following the existing
set_invariant_recorder pattern. No-op single-atomic-load when no
observer is installed; production hot path stays cheap.
Production emission sites (always-on, no feature flag):
- ParquetMergePipeline::spawn_pipeline → Restart, after fetch_immature_splits
- ParquetMergePipeline::FinishPendingMergesAndShutdownPipeline →
DisconnectMergePlanner and RunFinalizeAndQuit
- ParquetMergePlanner::send_merge_ops → PlanMerge
- ParquetUploader::handle (post-upload, pre-publish) → UploadMergeOutput
for merge outputs
- Publisher::handle (Parquet publish path) → IngestSplit (when no
replaced_split_ids) or PublishMergeAndFeedback (when replacing)
quickwit-indexing/.../parquet_merge_pipeline_trace_conformance_test.rs (new):
- Two scenarios:
1. Normal happy path: 4 splits → 2 merges → 1 mature output. Replay
verifies MP-1, MP-4..MP-10 hold at every state. ~0.6s.
2. Crash mid-cascade: publish failure injected on the 2nd merge
forces respawn. Trace covers Restart → PlanMerge → Upload → crash →
Restart → re-seed → PlanMerge → Upload → Publish. ~1.1s.
- StateMirror reconstructs MergePipelineState from events using a
production-id → model-u32 interner, applies the same predicate
functions used by the Stateright model.
- The mock metastore tracks `staged` and `published` separately so
list_metrics_splits returns only what real metastores would (not
staged-but-failed). Earlier test-side bug here would have masked
divergences; the trace test caught it immediately.
What this catches that prior tests do not:
- The crash test only verified re-seeding *happened*. The trace test
verifies that the post-restart state preserves all formal safety
invariants (no orphans the planner can never reclaim, no row drift,
no level mixing on re-merge, etc.).
- A divergence between production order/atomicity and the model's
atomic actions surfaces as either a state-mirror error ("event can't
be applied") or a predicate failure with the offending event in the
panic message.
Both tests pass with the current production code: production behavior
matches the TLA+ spec for the scenarios exercised. The test
infrastructure is now ready to surface real divergences if they exist,
and to be extended with adversarial scenarios (multiple back-to-back
crashes, finalize-during-crash, concurrency stress).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: add TODO for CS-3 invariant when compaction_start_time lands
CS-3 ("splits before compaction_start_time are never compacted") is
defined in the TLA+ model and the InvariantId registry, but the
production `ParquetMergePolicyConfig` does not yet expose a
`compaction_start_time` field. Adding a runtime check now would have
nothing to filter against.
Records the gap as a planner-level TODO so the check lands alongside
the feature when it's implemented (filter splits in
`record_splits_if_necessary` + verify via `check_invariant!`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: rename metrics_pipeline → parquet_pipeline, update CODEOWNERS
The actors under quickwit-indexing/src/actors/metrics_pipeline/ are
the parquet-based ingest+compaction pipeline. They currently process
metrics, but logs and traces will move onto the same pipeline in
later phases — the metrics-specific name has become misleading.
Pure rename:
- git mv metrics_pipeline → parquet_pipeline
- update mod declaration + import paths (metrics_pipeline:: →
parquet_pipeline::)
- update CODEOWNERS path
Function names that incidentally contain "metrics_pipeline"
(spawn_metrics_pipeline, spawn_log_or_metrics_pipeline,
test_metrics_pipeline_e2e) are NOT renamed in this commit — they
describe pipeline kind ("the metrics-flavored pipeline") rather than
the module, and would benefit from their own pass that also addresses
the eventual log/trace generalization.
CODEOWNERS also adds /docs/internals/ to the byoc-metrics path list:
the architecture and verification docs there are domain-aligned with
quickwit-parquet-engine + quickwit-dst (already in the list) and
should be approvable by the same team.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(sesh-mode): generalize don't-weaken rule to user requirements
The "STOP, Don't Weaken" section already covered formal verification
specs (TLA+ invariants, Stateright properties, DST assertions). It
didn't explicitly cover the spec→plan→implementation translation step,
which is where silent weakening tends to slip in for English-language
user requirements.
Restructure the section under one umbrella ("Specs are Load-Bearing")
with two subsections:
- "Verification check fails" — original content verbatim
- "User requirement seems hard or out-of-scope" — five concrete
silent-weakening failure modes (granularity downgrade, constraint
dropping, strength reduction, MUST→SHOULD, scope reframing), the
protocol that mirrors the verification case, and the
plan-approval-is-not-spec-approval meta-trap
Lesson surfaced from PR-4: the original plan I wrote substituted
column-chunk granularity for the page-level streaming the user had
asked for, and got plan-document approval rather than spec approval.
The new section flags that failure mode by name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(fmt): re-sort actors/mod.rs entries after parquet_pipeline rename
The metrics_pipeline → parquet_pipeline rename moved the module name
past `packager` in the alphabetical ordering rustfmt's
imports_granularity pass enforces. Swap the two entries (both the
`mod` declarations and the `pub use` re-exports) to restore order.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(codeowners): open docs/internals/ and Cargo.lock to any-write-access
Last-matching-rule wins. A path line with no team after it removes
the codeowner requirement, so any user with write access can
approve PRs that only touch those paths.
- `/docs/internals/`: shared architecture + verification docs that
any team working on the codebase may need to update. Gating on a
single team blocks unrelated work.
- `/quickwit/Cargo.lock`: churns on routine dependency bumps and
doesn't carry domain-specific review value.
Per CODEOWNERS semantics, /docs/internals/ no longer falls under
byoc-metrics (which the previous commit had assigned it to).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(codeowners): docs/internals and Cargo.lock approvable by either team
Per the GitHub docs, multiple owners on the same line means approval
from any ONE of them satisfies the codeowner requirement. Listing
both `quickwit-core` and `byoc-metrics` on `/docs/internals/` and
`/quickwit/Cargo.lock` lets either team approve PRs scoped to those
paths.
Replaces the previous commit's "no required owner" formulation,
which would have allowed any user with write access. The intent is
to keep the review gate but distribute it across both teams that
work in this repo, not to remove the gate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent a3c79c9 commit 8f504e3
41 files changed
Lines changed: 4185 additions & 62 deletions
File tree
- .claude/skills/sesh-mode
- .github
- docs/internals/specs/tla
- quickwit
- quickwit-dst/src
- events
- invariants
- models
- quickwit-indexing
- src/actors
- parquet_pipeline
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
47 | 129 | | |
48 | 130 | | |
49 | 131 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
4 | | - | |
5 | | - | |
6 | | - | |
| 3 | + | |
| 4 | + | |
7 | 5 | | |
8 | 6 | | |
9 | 7 | | |
| |||
13 | 11 | | |
14 | 12 | | |
15 | 13 | | |
16 | | - | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
11 | | - | |
| 10 | + | |
| 11 | + | |
12 | 12 | | |
13 | | - | |
14 | | - | |
15 | | - | |
16 | | - | |
| 13 | + | |
| 14 | + | |
17 | 15 | | |
18 | | - | |
19 | | - | |
20 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
28 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
29 | 33 | | |
30 | | - | |
31 | | - | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
32 | 37 | | |
33 | | - | |
34 | | - | |
| 38 | + | |
| 39 | + | |
35 | 40 | | |
36 | 41 | | |
37 | | - | |
38 | | - | |
39 | | - | |
| 42 | + | |
40 | 43 | | |
41 | 44 | | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
47 | 65 | | |
48 | 66 | | |
49 | | - | |
| 67 | + | |
50 | 68 | | |
51 | | - | |
52 | | - | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
53 | 74 | | |
54 | | - | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
55 | 84 | | |
56 | 85 | | |
57 | 86 | | |
58 | 87 | | |
59 | | - | |
60 | | - | |
61 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
62 | 92 | | |
63 | 93 | | |
64 | 94 | | |
| |||
69 | 99 | | |
70 | 100 | | |
71 | 101 | | |
| 102 | + | |
| 103 | + | |
72 | 104 | | |
73 | 105 | | |
74 | 106 | | |
| |||
81 | 113 | | |
82 | 114 | | |
83 | 115 | | |
84 | | - | |
| 116 | + | |
85 | 117 | | |
86 | 118 | | |
87 | 119 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
0 commit comments