Repository navigation
Commit e1790fd
Fixes #21790
Clause-②: yes
## What was wrong
`ObjectSchema.fields` is a name-keyed map, and the spec declares its
traversal order to be the field order. `canonicalize` sorted every map,
so a pure reorder of `fields` hashed equal to the published row.
`SysMetadataRepository.put` treats an equal hash as a no-op and skipped
the write. `promoteDraft` then drained the draft anyway. So the publish
answered success, the served object kept the old order, and the draft
was gone. That is the card's reproduction. Under the reverse
verification below, the new dogfood pin reproduces it: it reads `title,
amount, due` back after publishing `due, title, amount`.
## The fix
- **`@objectstack/metadata-core`**: `canonicalize(value, type?)` and
`hashSpec(value, type?)` take the metadata type. A per-type table names
the maps whose key order is content. Today it has one row, `object` maps
to `fields`, and the table's docblock cites the spec text behind it.
That map keeps its insertion order in the canonical form. Everything
else stays key-order independent: the keys around `fields`, the keys
inside each field definition, and every map of every other type. Without
a type, the output is byte-identical to before. The table is exported
read-only as `orderedMapKeys(type?)`. `InMemoryRepository` and the
shared contract suite hash as `ref.type`, and invariant 4 is restated as
`item.hash === hashSpec(item.body, item.ref.type)`.
- **`@objectstack/metadata-protocol`**: `SysMetadataRepository` hashes
as `ref.type` (in `put`, and in the `rowToItem` fallback for a null
checksum). For a type with an ordered map, its no-op short-circuit
compares content: it re-hashes the stored body under the current rule
rather than trusting the stored stamp. A skipped write returns the
stored stamp, which is the version every reader already holds (see H4).
- **`@objectstack/metadata-fs`**: `FileSystemRepository` hashes as
`ref.type` at all four sites (get, put, boot scan, watcher).
Landing: the dispatch named `canonicalize.ts` and the `hashSpec` call
site, and measurement kept it there. The type has to reach
`canonicalize`, so the two other repository implementations that call
`hashSpec` (`InMemoryRepository`, `FileSystemRepository`) pass it too.
Otherwise the shared repository contract would hold two hash rules. No
`packages/spec/src/**` edit: the order is already declared in spec prose
(H2), and the table implements that declaration.
`@objectstack/metadata-core` is graded `minor`: two exports widen (a new
optional parameter and a new function). That follows the "WHICH LEVEL"
rule in `pr-automation.yml`, under which a fix that widens an index is
`minor`. Because the diff widens that published surface, the declaration
is `Clause-②: yes`. The claim predicted `no` before the diff existed,
and the seat's review corrected it. No payload key, accepted value or
error code moves. `metadata-protocol` and `metadata-fs` are `patch`.
## H1: every place a content hash or stored checksum decides "unchanged"
(at `18c2ddc1ec`)
| site | compares | what equality drops |
|---|---|---|
| `metadata-protocol/src/sys-metadata-repository.ts:620` (`put`) |
stored `checksum` vs `hashSpec(incoming)` | the row write, the history
row and the watch event; it re-yields the stored item. **The defect
site.** |
| `sys-metadata-repository.ts:886` / `:972` (`promoteDraft`) | delegates
to `put` above with `parentVersion` = the stored active stamp | the
publish (through `:620`); the draft drain still runs, which is why "the
draft is gone" |
| `sys-metadata-repository.ts:613` / `:786` (`put` / `delete` lock) |
caller `parentVersion` vs stored `checksum` | nothing; inequality throws
`ConflictError` (a lock, not a no-op) |
| `sys-metadata-repository.ts:1945` (`rowToItem`) | none; re-derives
only when `checksum` is null | n/a (the read returns the stored stamp) |
| `metadata-protocol/src/protocol.ts:16901` (`storedParentForToken`) |
caller `If-Match` token vs keyed digest of the stored head | nothing;
inequality answers 409 |
| `protocol.ts:14082` (`getMetaItem` ETag) | `simpleHash` of the served
JSON (order-sensitive already) vs `If-None-Match` | the body (304); not
a write decision |
| `metadata-core/src/in-memory-repository.ts:107` (`put`) | current head
hash vs `hashSpec(incoming)` | the write and the event |
| `metadata-fs/src/repository.ts:362` (`put`) / `:1006` (watcher) | head
index (re-derived at boot) vs `hashSpec(body)` | the file write / the
external-change event |
| `metadata/src/loaders/database-loader.ts:1414` (`save`) / `:728`
(history) | its own key-sorted `calculateChecksum`, a separate
vocabulary | the write / the history row. Reached only through
`MetadataManager.register` / `save`; the Studio save and publish path
does not call them (no caller of `MetadataManager.register` in
`packages/*/src`). Unchanged; see Acceptance notes. |
| `objectql` | `src/` has no comparison.
`scripts/dry-run-hash-compat.ts` compares stored `checksum` with
type-blind `hashSpec(body)` in an offline audit probe | nothing; it
reports. Unchanged; see Acceptance notes. |
## H2: maps a stored metadata body declares ordered
| map | declared ordered? | in the table |
|---|---|---|
| `object.fields` | **yes**. `ObjectFieldGroupSchema`
(`data/object.zod.ts` ~1080-1084): "the in-group display order equals
the traversal order of `ObjectSchema.fields`", and ungrouped fields keep
"their field declaration order". `fieldGroups` (~2090-2093): fields are
displayed "in their `ObjectSchema.fields` declaration order". The
`record` field type it is authored through (`data/field.zod.ts:90`,
`object.form.ts:114`): "Insertion order = display order".
`data/display-name.ts:50`: declaration order is `Object.keys` order, and
the first title-eligible field "by declaration order" is picked | yes |
| `object.listViews` | no order statement (rendered as tabs, keyed by
view name) | no |
| `ObjectExtension.fields` | no statement, and not a stored metadata
type (code-only `defineObjectExtension`) | no |
| nested `fields` records (`security/permission.zod.ts:877`,
`system/translation.zod.ts:96/206`, flow node configs) | no statement |
no |
| inline locale maps (`I18nLabel`) | the resolver breaks ties by key
insertion order (`ui/i18n-label-resolver.ts:44`, limbs 3 and 6,
mirroring objectui's `pickLocalized`). That is a tie-break rule, not a
declared authoring order, and these maps sit at arbitrary depths | no
(raised in the report) |
| `AnalyticsQuery.order` / `DatasetSelection.order` | declared ("applied
by key in insertion order"), but these are request bodies, not stored
metadata. Reports store `order` as an array because "JSON object key
order is not a contract" (`ui/report.zod.ts:48`) | n/a |
The only row is `object.fields`, which the spec already declares. No new
spec declaration was needed.
## H3: where the order is kept
`canonicalize` takes the metadata type. That is the least surface that
keeps one hash rule. A pre-pass in `hashSpec`'s caller would leave
`hashSpec(body)` disagreeing with the repository's hash, which breaks
contract invariant 4, and every repository implementation would need its
own copy. The type is in hand at every call site (`ref.type`), and an
omitted type is exactly the old rule. Guarantees 5 (idempotence) and 7
(serialized-form identity, #7856) hold for an ordered map because
`JSON.stringify` writes own keys in `Object.keys` order and `JSON.parse`
recreates them in that order. Both are pinned as properties under the
`object` type.
## H4: stored hashes after the change (measured)
- **Does a stored checksum stop matching the recomputed hash?** Yes, for
an object whose `fields` are not in sorted key order. Objects with
sorted `fields` hash byte-identically (pinned).
- **Read / ETag / If-Match / conflict:** unaffected. `rowToItem` returns
the stored stamp. The ETag hashes the served document, not the checksum.
`If-Match` tokens are keyed digests of the stored stamp, and the
repository lock compares against the stored stamp. Nothing re-derives a
stamp to judge a lock, so upgrading raises no conflict. Pinned end to
end: a receipt token taken over a row with an old stamp is honoured by
`If-Match`.
- **Misfire 1 (handled):** with only the type-aware hash, an identical
re-save of an old row would compare current hash against old stamp, see
a difference, and write a history row recording no change.
`storedBodyUnchanged` re-hashes the stored bytes under the current rule.
The save is a no-op, and the stamp stays as written.
- **Misfire 2 (handled):** a reorder into sorted key order hashes, under
the current rule, to exactly the old order-blind stamp. A stamp
comparison therefore drops it: the original defect, surviving on exactly
the rows stored before the fix. The content comparison writes it.
- **Boot re-derivation:** `SysMetadataRepository` re-derives nothing.
`FileSystemRepository` re-derives its head index at boot under the new
rule, and the watcher and resync compare new against new, so there are
no spurious events. Its JSONL log keeps pre-change hashes, so provenance
on `get` / `list` for such an object falls back to defaults until the
item next changes. That is measured but not handled; see Acceptance
notes.
## H5: the negative
Pinned: swapping the keys around `fields`, the keys inside a field
definition, and a map inside a field definition all hash equal under
`object`. A property test holds every non-`fields` key
order-independent. A `fields` map under a type with no row (`view`), and
under no type, hashes equal across a reorder.
## Pins
1. **Dogfood**
(`packages/qa/dogfood/test/object-designer-field-reorder.dogfood.test.ts`,
showcase, real HTTP). It creates and publishes an object, saves a
reorder as a draft, publishes it, and reads the new order back through
`GET /api/v1/meta/object/:name` twice (the designer reload), plus the
stored row. Then it rewinds the row to the order-blind stamp. An
identical save records no history row and keeps the stamp. That save's
receipt token is honoured by `If-Match`, and a reorder into sorted order
is published.
2. **Hash** (`packages/metadata-core/test/canonicalize.test.ts`): the
order half and the unordered half (H5), side by side.
3. **Guarantees 5 and 7** with the ordered map: properties under
`object`, plus `toJSON` inside `fields` and on `fields` itself.
4. **H4 at the repository**
(`packages/metadata-protocol/src/sys-metadata-repository.contract.test.ts`,
on that file's existing engine double; no new double). The
draft-and-publish reorder through `promoteDraft`. Over an old-stamp row:
read, identical re-save, draft-and-publish reorder (`previous_checksum`
= the old stamp), and the reorder into sorted order.
## Reverse verification (both run from committed state; every mutation
through `scripts/ablation-replace.mjs`, `dist/` proven with
`scripts/ablation-dist-preflight.mjs`)
**A. Order-blind `fields` restored** (the `object` row deleted from the
table). Pre-mutation `dist/` reading: marker `Object.freeze(["fields"])`
present in 2 files. Mutate: anchor 1 to 0, blob `c2483ce615dd` to
`169e16f653bf`. `metadata-core` rebuilt, preflight `--absent` green.
- pin 2: **5 red / 38 green**. Red: the table row, "hashes a reordered
`fields` map differently", "keeps the declared field order", "a reorder
INTO sorted order is still a change", and the `toJSON`-on-`fields` case
(it asserts the kept order). Green: every H5 case, both byte-identical
cases, and both guarantee properties.
- pin 1: **3 red / 2 green**. The reorder publish reads back `title,
amount, due`, the card's defect. The stale-stamp precondition and the
sorted-order publish are also red. The declared-order create and the
identical save stay green.
- pin 4: **5 red / 50 green** (every order-dependent case).
- Restore: blob back to `c2483ce615dd`, `git diff HEAD` empty, rebuilt,
marker present again, tree clean.
**B. Content comparison reverted to a stamp comparison**
(`storedBodyUnchanged`), with the type-aware hash kept. Mutate: anchor 1
to 0, plant 0 to 1, blob `013ffb59ed3d` to `195cf07493ae`.
- pin 4: **2 red / 53 green**. Exactly "an identical re-save: no
conflict, no history row" and "a reorder INTO sorted order is written".
- pin 1, after `metadata-protocol` was rebuilt: **1 red / 4 green** ("an
identical save records no change"). The type-check of the deliberately
ill-typed mutation failed the d.ts step after the JS was emitted, and
preflight proved the marker in 2 built JS files.
- Restore: blob back to `013ffb59ed3d`, `git diff HEAD` empty, rebuilt,
marker absent, tree clean.
## Local verification
All readings were taken at `071959e91e` (this branch merged with
`origin/main` `75ddcd1b41`) on a shared 4-core box. Lock seconds are
shared-box figures.
- **Build**: `turbo run build --filter=@objectstack/dogfood^...` plus
the eight packages `check:dual-build-cjs-loads` reads: 71/71 tasks.
- **`@objectstack/metadata-core`**: `test` 389/389 (18 files);
`typecheck` exit 0.
- **`@objectstack/metadata-fs`**: `test` 70/70 (10 files); `typecheck`
exit 0.
- **`@objectstack/metadata-protocol`**: `typecheck` exit 0, and the pin
file 55/55. The full suite ran before the merge, at `bc6102dddb`: 19959
passed, 19 skipped (213 files passed, 3 skipped). Of this PR's packages,
the merge touched only `metadata-core/src/protocol-handshake.ts`, a file
this diff does not touch.
- **`@objectstack/dogfood`**: the new file 5/5; `typecheck` exit 0. The
rest of the dogfood suite is CI's.
- **`@objectstack/objectql`** `src/dry-run-hash-compat.test.ts`, the one
other `hashSpec` caller: 17/17.
- **ESLint, narrowed to the 10 changed `.ts` files**: `eslint
--no-inline-config --format json` read 10 files and reported 0 errors, 0
warnings and none ignored. Population: the config's `**/*.{ts,…}`
blocks, which contain all 10. Invariance: the config enables no
type-aware linting (no `parserOptions.project`), so this diff cannot
move a verdict on any untouched file.
- **Gates**: `dispatch-gates --commands`, re-derived at this head, gave
67 commands. Its artifact-roster block added 54 and the four
symbol-anchor sweeps 4: 124 run, 121 exit 0. The `--ran` reconciliation
reads 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. The 3 non-zero exits
are exit 2, from gates that read pull-request context and judged nothing
locally: `check-closing-target-claim`, `check-partof-closing-keyword`
and `check-single-claim-paths`. Re-run with this body as `PR_BODY`,
`check-partof-closing-keyword` is green. In that run,
`check-changeset-no-major --event` (over the body as it then read) found
no major and read `Clause-②: no`. See the re-run below for the corrected
declaration. `check:engine-double-contract`: OK, no new double.
- **Re-run at `6fe110968c`**, after the declaration was corrected to
`Clause-②: yes` in this body and all three changesets, and `origin/main`
`18c7dfd2e6` was merged (docs/qa only): `check-changeset-no-major --base
origin/main --event` over this body exits 0. It finds no major, and its
level axis reads that the PR declares clause-② `yes` and grades
`@objectstack/metadata-core` `minor`; `metadata-fs` and
`metadata-protocol` are `patch` and not refused.
`check-adr-0087-registration` exits 0, reporting no declared-breaking
changeset. `check:adr-symbol-anchors`, `check:scripts-symbol-anchors`,
`check:spec-docblock-symbol-anchors` and `check:adr-anchors` all exit 0.
The `metadata-core`, `metadata-protocol` and `metadata-fs` typechecks
all exit 0. No source changed between `071959e91e` and this head, so the
test readings above stand.
- **NOT MEASURED here (CI's)**: `pnpm lint` over the repo, the workspace
type-check lane, the Test Core shards, the rest of the Dogfood
Regression Gate, and Temporal Conformance.
## Acceptance notes
- **Not changed, observed:** `DatabaseLoader` (`packages/metadata`)
keeps its own key-sorted `calculateChecksum` and skips a save on
equality. It is a second hash vocabulary over the same `sys_metadata`
table. It is reached only through `MetadataManager.register` / `save`,
which no Studio save or publish path calls (no caller in
`packages/*/src`). Carrier: none.
- **Not changed, observed:** the offline probe
`packages/objectql/scripts/dry-run-hash-compat.ts` (PR-10d.1) hashes
type-blind. On an object row stamped by the new rule with unsorted
`fields`, its `checksum_drift` arm fires. It is not published (`files`
is `dist` only) and has no runner beyond its own test. Carrier: none.
- **Measured, not handled:** `FileSystemRepository`'s JSONL log keeps
pre-change hashes. For an object with unsorted `fields`, provenance on
`get` / `list` falls back to defaults: no parent hash, seq `0`, the
filesystem actor and the epoch timestamp. This lasts until the item next
changes. Production consumer census: `MetadataManager.getRepository()`
has no caller in `packages/*/src`, and the manager consumes only
`watch()` events, which are unaffected. Stated in the `metadata-fs`
changeset.
- **Residue, filed as #21821:** `revertCommit`'s restore limb passes the
commit item's stored type to `put` unfolded, so a legacy row stored
under a manifest plural such as `objects` reaches the repository under
that spelling. The ordered-`fields` rule applies only to the canonical
`object` spelling. A reorder-only restore of such a row is therefore
skipped as unchanged. Only rows written before the #4432 boundary fold
are affected; every `/meta` door folds today.
- `FileSystemRepository` takes the metadata type from the directory name
verbatim (`parseItemPath`, no validation). An `objects/` directory would
hash order-blind. Its `put` has no production caller; it is attached
only as an event source.
- **Carried over from this thread.** The seat's REWORK (comment
5988361846): the declaration is corrected to `Clause-②: yes` here and in
the three changesets, and the plural-spelling measurement it asked for
is answered in the os-dev-report on #21790. The docs drift check
(comment 5988314939): `content/docs/concepts/metadata-lifecycle.mdx:149`
says the hash is a sorted-keys serialization of the body. After this PR
that is true except for `object.fields`, whose order is kept. Done at
`046c425d62`: that paragraph now says the canonical form sorts keys
except in the maps the spec declares ordered (today an `object`'s
`fields`), that `put()` still short-circuits on an unchanged body, and
that a row written before this release keeps its stored hash as its
version token until its content next changes. `dispatch-gates
--commands` for that path derives 41 gates. All 41 exit 0,
`check:doc-authoring` included, and `--ran` reconciles 41 derived, 41
run, 0 NOT-MEASURED. `check:skill-examples` first answered PREREQUISITE
NOT MET because `client-react` was unbuilt; it passed once that package
was built. `origin/main` moved to `ba57588665` (service-settings and the
spec crypto-provider contract) after the pre-push check, so this head
does not merge it.
- A reorder into sorted order over an old-stamp row is written with
`checksum` equal to its `previous_checksum`: the two hashes are taken
under different rules. `getByHash` on that value can find either history
row. Objects are not `executionPinned`, so no pinned execution resolves
through it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 088428f commit e1790fd
14 files changed
Lines changed: 713 additions & 34 deletions
File tree
- .changeset
- content/docs/concepts
- packages
- metadata-core
- src
- test
- metadata-fs/src
- metadata-protocol/src
- qa/dogfood/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
149 | | - | |
| 149 | + | |
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
40 | 41 | | |
41 | 42 | | |
42 | 43 | | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
43 | 69 | | |
44 | 70 | | |
45 | 71 | | |
| |||
54 | 80 | | |
55 | 81 | | |
56 | 82 | | |
57 | | - | |
58 | | - | |
| 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 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
59 | 132 | | |
60 | | - | |
| 133 | + | |
61 | 134 | | |
62 | 135 | | |
63 | 136 | | |
| |||
83 | 156 | | |
84 | 157 | | |
85 | 158 | | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
86 | 165 | | |
87 | | - | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
88 | 172 | | |
89 | 173 | | |
90 | 174 | | |
| |||
107 | 191 | | |
108 | 192 | | |
109 | 193 | | |
110 | | - | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
111 | 197 | | |
112 | 198 | | |
113 | | - | |
| 199 | + | |
| 200 | + | |
114 | 201 | | |
115 | 202 | | |
116 | 203 | | |
117 | | - | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
118 | 207 | | |
119 | | - | |
| 208 | + | |
120 | 209 | | |
121 | 210 | | |
122 | 211 | | |
| |||
129 | 218 | | |
130 | 219 | | |
131 | 220 | | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
132 | 225 | | |
133 | | - | |
134 | | - | |
| 226 | + | |
| 227 | + | |
135 | 228 | | |
136 | 229 | | |
137 | 230 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
204 | 204 | | |
205 | 205 | | |
206 | 206 | | |
207 | | - | |
| 207 | + | |
208 | 208 | | |
209 | 209 | | |
210 | 210 | | |
| |||
227 | 227 | | |
228 | 228 | | |
229 | 229 | | |
230 | | - | |
| 230 | + | |
231 | 231 | | |
232 | 232 | | |
233 | 233 | | |
234 | 234 | | |
235 | | - | |
| 235 | + | |
236 | 236 | | |
237 | 237 | | |
238 | 238 | | |
| |||
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
270 | | - | |
| 270 | + | |
271 | 271 | | |
272 | 272 | | |
273 | 273 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
104 | | - | |
| 104 | + | |
| 105 | + | |
105 | 106 | | |
106 | 107 | | |
107 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
25 | 30 | | |
26 | 31 | | |
27 | 32 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | | - | |
| 94 | + | |
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
100 | | - | |
| 100 | + | |
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
| |||
0 commit comments