Repository navigation
Commit 965a1bf
Part of #15953
Clause-②: yes
## What this is
Slice 4 of #15953: the D half of the classification table's row 12
(comment 6115219467), plus the four acceptance notes slice 3's review
left (6128105426). It adds the parent-derived write reach family to
`@objectstack/verify`, runs it from `os verify --parent-write-reach`
(opt-in), and documents it. Row 12's K residue (`sales_manager`'s grant)
stays in hotcrm; its R residue (the spec gate on a hierarchy scope)
belongs to `packages/spec` and is not in this PR. #15953 stays open for
its remaining rows.
**Property, for any app.** ADR-0055 section 3 and its 2026-09-07
amendment: editing, deleting or creating a detail of a
`controlled_by_parent` object requires edit access to its master, walked
hop by hop to the first master that governs its own rows; the detail's
own object permission is the first door. For a principal P, a detail
record D under a master record M, and a verb (edit, delete, create), the
write is expected admitted exactly when both halves hold:
- P holds the verb on D's object;
- P can edit M.
## The family (`packages/verify/src/parent-write-reach.ts`)
- **Population.** Every object the spec's own parse
(`ObjectSchema.safeParse`) declares `sharingModel:
'controlled_by_parent'`, with exactly one `master_detail` field
(ADR-0055 section 1), read through `referenceCarrierOf`. The chain is
walked through each master's own parse until a master that governs its
own rows.
- **Published sources each expectation is read from.**
- The detail half: the server's effective object permissions for P (`GET
/auth/me/permissions`, the one merge of the sets P holds, ADR-0124 D4),
folded by `objectPermissionGrants` (`@objectstack/spec/security`).
Nothing re-merges permission sets here.
- The master half: MEASURED, never re-derived. An empty update of M as
P, which runs every write gate M has (the object gate, the record gate,
and for a master that is itself controlled by its parent the master gate
above it) and changes no business value. Measured at REST first: an
empty `PATCH` is refused by the object gate (no edit grant), by the
record gate (edit grant, another owner, no all-records grant: `You do
not have access to this record`) and by the master gate, and admitted on
the caller's own record.
- The rule: ADR-0055 section 3 and the amendment. No sharing, ownership,
hierarchy or chain resolution is copied: the master half is the engine's
own answer on M.
- **Door.** The engine's write as the signed-in principal: the handle's
`hooks.run` with `as: token`, the `insert`, `update` by id and `delete`
by id the REST data ingress makes, with the caller's context, the
security middleware and the bound hooks included. See Deviations for why
not the REST route itself. The system door bypasses authorization by
design and is out of this family's population.
- **Principals.**
- Three verifier personas whose permission sets the family authors from
the app's own objects, registered at boot by
`parentWriteReachSecurity(config)` and granted by name (ADR-0131 D3/D4,
the RLS family's probe-persona path):
- `master-all-records`: the root master by the all-records grant
(`modifyAllRecords`), every verb on the details;
- `detail-only`: the root master readable (`viewAllRecords`) but not
editable, every verb on the details;
- `master-all-records-no-delete`: the all-records grant, no delete on
the details.
- One persona per declared position, through the RLS family's exported
`provisionRlsPositionPersona` and `declaredPositionNames`: its whole
capability is what the app binds to the position.
- The permission sets each persona holds are reported from the server's
own answer.
- **Fixtures.** Reused `deriveCrudCases` / `fillRelationalRefs` and
slice 3's required-fields fixture shape, through the seed door. Per
persona, two master chains: one another principal owns (so the
all-records grant, not ownership, is what is exercised) and one the
persona owns. Under each, a detail another principal owns and one the
persona owns.
- **Probes.** Per persona and master chain: the master read (a chain the
persona cannot read is outside its reach, counted, not probed: a write
to a record the caller cannot read is the RLS family's subject), the
master's empty update, then edit and delete of each detail and one
create.
- **Attribution.** A refusal holds only as `PERMISSION_DENIED` with
status 403 whose `details` name the detail's object and the probed
operation (`update`, `delete`, `insert`). Any other refusal (validation,
a hook's own code, a refusal on another object) is `unattributed`. A
write the door admits is checked on the record (an edit moves
`updated_at`, a delete removes the row, a create holds the master), and
a refusal whose record shows the write is contradicted.
- **Exclusions, with reasons, counted.** No `master_detail` field (the
runtime would still derive through a required lookup, its own
precedence, not compared here); two of them; a master the app does not
declare; a cycle; a chain reaching the write gate's depth bound (eight
controlled-by-parent masters, ADR-0055's amendment). Not derivable: the
detail or a master in its chain cannot be synthesized.
- **Report.** Per object `proven` / `contradicted` / `failed` /
`inconclusive` / `excluded` / `not-derivable`; per persona, run or not
run with the reason. `summary.hardFailures` is `contradicted + failed`
objects plus personas that could not be provisioned.
`os verify --parent-write-reach` boots its own fresh stack with
`parentWriteReachSecurity(config)`, adds
`parentWriteReach.summary.hardFailures` to the exit status, and emits
`parentWriteReach` under `--json`. Without the flag, nothing changes.
## Finding: today's engine refuses every write to a detail two levels
below its root master
On this tree's engine, a detail whose master is itself
`controlled_by_parent` (a quote line under a quote under an account) is
refused every edit, delete and create for every non-admin principal,
with `403 PERMISSION_DENIED`, `master 'X' not editable by this user
(row-level security)`, while the same principal's update of that middle
master is admitted. This holds when the principal owns the whole chain
and when it holds `modifyAllRecords` on both masters.
- Measured through REST on the fixture app: `PATCH
/api/v1/data/pwr_quote/ID` with an empty body answers 200 and `PATCH
/api/v1/data/pwr_quote_line/ID` answers 403, as the same principal.
- On hotcrm's real chain (`crm_quote_line_item` under `crm_quote` under
`crm_account`), hotcrm's pinned 17.7.0 engine admits all 15 such writes
for `sales_manager` and `sales_rep`; this tree's engine refuses all of
them. **Corrected after #23093's analysis (`6133315184`):** this is not
a regression. The released 17.7.0 engine refuses the same writes for an
organization member. Run A admitted them because 17.7.0's `bootStack`
ran organization-less personas (removed by #22186). The maintainer ruled
A on #23093 (`6133922339`): the middle hop's ownership floor is lifted,
so `contradicted` is the family reading today's engine against the
contract as ruled.
- The seam: ADR-0055's amendment says a detail whose whole chain is
reachable stays writable; the hop runs `assertMasterRowEditable`
(`packages/plugins/plugin-security/src/security-plugin.ts`) on the
middle master, whose write filter re-read under the caller's context
answers no row.
No engine change here, per the slice's ruling: the family reports it
(`contradicted`), the fixture suite pins what the engine answers today
(it flips to proven when the chain walk agrees with the middle master's
own write), and the README and `cli.mdx` state that an app declaring
such a chain fails `--parent-write-reach` until then. The report on
#15953 carries it for filing.
## Evidence
**Fixture app** (`packages/verify/src/parent-write-reach.test.ts`,
`Tests 10 passed (10)`):
- Population (no boot): every exclusion with its reason, the chain hops,
and the depth bound (eight controlled-by-parent masters above an object:
excluded; seven: walked).
- Booted with `parentWriteReachSecurity`: 3 objects (two single-level,
one two-level), 5 personas run (3 verifier, 2 position), 27 reaches
probed, 3 outside the rep's read reach, 135 writes (54 edit, 54 delete,
27 create), 104 held. Both single-level objects proven for every persona
under both master chains. The two-level object contradicted on 31 writes
(the finding above), its refusals still held where the conjunction
refuses.
- hotcrm's contract shape: the manager edits another principal's
contract through the account by the all-records grant (detail another's
and its own), is refused the delete (`PERMISSION_DENIED`, `details`
`delete` on the contract, with the master's edit measured admitted, so
the refusal is the contract's own permission), creates one; the rep
reads none of another principal's accounts (outside its reach) and on
its own account edits the master and is still refused every contract
write (the object gate).
- **Negative controls** (no engine change):
- a detail hook refusing every edit and delete with the platform's own
envelope where the master is editable: contradicted on 4 of 4 writes per
chain for `master-all-records`, held for `detail-only`;
- a master hook refusing every edit of the master that way, so the
master reads not editable while nothing stops its details: every detail
write admitted and contradicted, for `master-all-records` on both
chains.
- **Attribution control:** a refusal naming another object
(`PERMISSION_DENIED`, `details` `update` on the account) and an app's
own code (`RECORD_LOCKED`, 409) are `unattributed`, never held, never
contradicted.
- A stack booted without `parentWriteReachSecurity` reports the three
verifier personas not run, with the reason, counted in `hardFailures`;
the position personas still run.
**CLI** (`packages/cli/test/verify-parent-write-reach.test.ts`, spawned,
`Tests 3 passed (3)`): without the flag no `parentWriteReach` key and
exit 0; with it the contract proven by 4 personas and exit 0; with a
hook refusing the contract's edits with the platform envelope,
contradicted, `hardFailures` 1, exit 1.
**Ablation of the family's own comparison** (`judge`), through
`scripts/ablation-replace.mjs` (wrap mode) inside a scratch script with
`trap restore EXIT INT TERM` on absolute paths, at `8b4a5802f0` (first
taken at `fa8171ea2c` with the same result). No build involved: the test
imports the module by a relative path.
- **A.** `if (probe.expected === 'refused') return { outcome: 'held' };`
replaced by an unconditional `held` plus the marker
`ABLATION_15953_S4_A`. On disk: anchor 1 to 0, marker 0 to 1, blob
`560c6d69` to `a6218f46`. RED: `Tests 4 failed | 6 passed (10)`: both
negative controls, the two-level pin and the count that reads it.
Restored: blob `560c6d69` equals HEAD, `git diff HEAD` empty.
- **B.** The same on `if (probe.expected === 'allowed') …` (marker
`ABLATION_15953_S4_B`). On disk: anchor 1 to 0, blob `560c6d69` to
`53cb38f1`. RED: `Tests 1 failed | 9 passed (10)`, exactly the second
negative control. Restored to HEAD's blob, empty diff.
- Predicted direction for both: red. Observed: red.
**hotcrm, the first consumer.** Read-only clone at `1d7148bf`, fetched
first: `main` is at `0f84c9c7`, and `test/contract-write-depth.test.ts`
and every `controlled_by_parent` / permission declaration are unchanged
there. The clone's `git status` stayed empty. `os verify` cannot boot
hotcrm on this tree (protocol `^17.7.0` versus 18), so the family ran
directly over slice 3's artifact (hotcrm's own CLI 17.7.0) and its `os
migrate meta --from 17` snapshot (only `engines.protocol` moved to
`^18`, and the 76 `email_template.category` tombstones the migration
lists for hand application removed in memory).
| run | engine, hooks | personas run | proven | contradicted |
inconclusive | excluded | writes (edit / delete / create) | held |
|---|---|---|--:|--:|--:|--:|---|--:|
| A | hotcrm's pinned 17.7.0 `bootStack` over its own artifact, hooks
live | 12 position (the 3 verifier personas not run: 17.7.0 has no
`permission_set` grant column) | 4 | 0 | 1 | 3 | 70 / 70 / 35 | 137 |
| B | this tree, the snapshot with the artifact's 44 hook bodies grafted
by name | 15 (3 verifier, 12 position) | 2 | 1 | 2 | 3 | 130 / 130 / 65
| 224 |
| C | this tree, the snapshot with hooks inert | 15 | 4 | 1 | 0 | 3 |
130 / 130 / 65 | 294 |
- **Proven:** `crm_contact` (A, B, C), `crm_quote` (A, B, C),
`crm_opportunity_line_item` (A, C), `crm_contract` (C),
`crm_quote_line_item` (A only: hotcrm's pinned engine).
- **Contradicted:** `crm_quote_line_item` on this tree, 31 writes in B
and in C, for every persona that edits the quote (the finding above).
- **Inconclusive, with the app's own reasons:** `crm_contract` in A and
B (hotcrm's `contract_validation` refuses a fixture whose term does not
match its dates); `crm_opportunity_line_item` in B (its
`opportunity_line_item_price_fill` hook refuses four creates on another
object: `unattributed`, never read as the detail's own).
- **Excluded:** `crm_campaign_member`, `crm_event_attendee`,
`crm_article_feedback` (no `master_detail` field; see the acceptance
notes).
- 85 persona and master-chain pairs outside a persona's read reach in
each run, counted, not probed.
**Against what row 12's D half asserted** (run C, every hotcrm hook
inert; run B has the same verdicts where its fixtures could be written):
- "the manager changes the value of a contract standing in a rep's name"
and "terminates it": `position:sales_manager` on a contract another
principal owns, under an account another principal owns, reached by the
all-records grant alone: edit allowed, held.
- "and still edits their OWN contract": the same, on a contract the
manager owns: held.
- "the rep still cannot edit the contract they own — the object gate,
unchanged": `position:sales_rep` edits its own account (measured
admitted) and is refused every contract write with `PERMISSION_DENIED`
naming the contract and the operation: held.
- "DELETE stays refused": the manager's delete refused with
`PERMISSION_DENIED`, `details` `delete` on `crm_contract`, while the
manager's update of the account is measured admitted, so what refuses it
is the contract's own permission lacking delete: held.
- 0 contradictions with what the file asserted. Its "does not leak to
another object" describe (`crm_case`, not controlled by its parent) is
outside this family.
## Carried acceptance notes (slice 3's review, 6128105426)
- **a. Attribution tightening** (`591513f0f5`). `judge` in
`unique-scope.ts` reports a field-less unique refusal `unattributed`
when a unique `indexes[]` entry covers the probed column, for insert and
update. The control: a field whose own index cannot be created (the
ADR-0120 D4 two-boot shape) beside a unique composite `indexes[{ fields:
[name, kind], unique: 'organization' }]`. Before the change it read
`proven`, 4 of 4 writes held with field-less refusals and admitted
twins; after, `inconclusive`, 4 of 4 `unattributed`. The README says so,
and the release note carries it as a change in this PR's own changeset
(patch round 1, `deb5c6ecc9`). The second twin was not built: the
entry's other columns may be of any type, and `unattributed` is the safe
reading.
- **b. `bootStack`'s input** (`a35a11217c`). One docblock sentence in
`harness.ts` and one README sentence: the config is `defineStack`
output; a raw object literal registers raw fields (an `autonumber` gets
no unique and no index). No behaviour change.
- **c. The membership-policy sentence** (`aff59344f9`): the README names
the `auth.membership_policy` setting beside `OS_AUTH_MEMBERSHIP_POLICY`.
- **d. The second-organization wording**: "a sign-up that creates its
own" is `signUp`, then `POST /auth/organization/create`. It was first
edited into slice 3's unreleased note (`42d15b2d57`). Under the
maintainer's ruling B (`6133232470`), that note is restored byte for
byte (`347895565e`), and the correction is carried in this PR's own
changeset (`deb5c6ecc9`). The same two sentences in `cli.mdx`'s `os
verify` section take the a and d corrections too (`06ed55ddbe`).
## Verification
At head `8b4a5802f0`: this branch with `origin/main` `038d4c9588`
merged; 13 files and 1954 changed lines (`+1937 / -17`) against that
merge base. Readings below are at that head unless another commit is
named.
**Suites.**
- **`@objectstack/verify`:** `pnpm --filter @objectstack/verify exec
vitest run --maxWorkers=2` reads `Test Files 35 passed (35)` / `Tests
281 passed (281)`, lock verdict `command-exit 0`. The family file alone
reads `Tests 10 passed (10)`; `unique-scope.test.ts` reads `Tests 13
passed (13)`.
- **Typechecks:** `pnpm --filter @objectstack/verify typecheck` and
`pnpm --filter @objectstack/cli typecheck` exit 0,
`check:test-typecheck: OK` for both. `tsc --listFilesOnly -p
tsconfig.test.json` shows the family module and its test in verify's
program (2 hits) and `verify-parent-write-reach.test.ts` in the CLI's (1
hit; control `verify-unique-scopes.test.ts`, 1 hit).
- **CLI integration tier** over the seven `verify-*` files, this PR's
new one included: `Test Files 7 passed (7)` / `Tests 21 passed (21)`.
The rest of the integration tier is declared to CI.
- **CLI unit tier** at `0cc4adcb16`: `Test Files 283 passed (283)` /
`Tests 4270 passed (4270)`. No `packages/cli` file changed after it.
- **Core `repo` project** (the `#22661` second-object read pin among its
6 files): `Test Files 6 passed (6)` / `Tests 83 passed (83)`, with no
`CALLERS` row: the family never calls the reference-target arbiter.
- **`pnpm check:verify-stand-in`:** before the rows exit 1, `2
problem(s)` (`runParentWriteReachProofs`,
`formatParentWriteReachReport`); after, exit 0, `OK
check-verify-stand-in-erasure: 2 stand-in check(s) guarded, 15
candidate(s) explicitly exempt, 10 call site(s) reached, 0 asserted
driver arguments`. `--self-test` OK.
**Gates.**
- **Derivation:** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, no paths, at `8b4a5802f0`: 118 commands (67
`pnpm`, 51 direct `node`).
- **Results:** all 118 run, each exit code captured before any pipe. 117
exit 0. `node scripts/check-empty-changeset.mjs --base origin/main`
exited 1 at `8b4a5802f0`, by design (see the next section). It exits 0
at `7e11653135`, after patch round 1.
- **Patch round 1, at `7e11653135`** (the merge of `origin/main`
`2d21b9c891`):
- `@objectstack/verify`: 35 files, 281 tests.
- `@objectstack/cli` unit: 285 files, 4280 tests.
- Both typechecks are OK.
- 118 gates derived, 118 run, 0 NOT MEASURED.
- `check-empty-changeset` exits 0. `--ran` reads `Run reconciliation —
118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN.`
- A first battery at `0cc4adcb16` recorded 3 `PREREQUISITE NOT MET`
(exit 3: `check:skill-examples`, `check:dual-build-cjs-loads`,
`check:i18n-coverage`, each reading packages outside the built
closures). After `turbo run build --filter=!@objectstack/docs` (73
tasks, 66 cached) the final battery measured all three, exit 0.
- `origin/main` moved 4 commits after the merge (to `0aef788816`), none
on this PR's paths. One of them makes `/auth/me/permissions` answer its
closed body on a fault, the endpoint this family reads; the family reads
an answer naming no permission set as no grants to read (`63d869dc02`).
- Every command with its printed verdict line is in the report comment
on #15953.
**Lint, by proven narrowing** at `8b4a5802f0`: `eslint
--no-inline-config --format json` over the 9 touched source files.
1. **Population, read from eslint's own config:** `--print-config`
resolves for 9 of 9.
2. **Counts, read from the JSON:** 9 files, 0 errors, 0 warnings.
3. **Invariance:** `eslint.config.mjs` never enables type-aware linting
(no `parserOptions.project`, no typed `@typescript-eslint` rules, its
own comment at `:327-328`), so this diff cannot move any untouched
file's verdict.
The `.md` / `.mdx` files are outside eslint's population.
## The pending release note (ruled B; resolved in patch round 1)
`check-empty-changeset` refuses this PR by design: it changes
`.changeset/15953-verify-unique-scope-family.md`, which is on the merge
base and was not added here. At `8b4a5802f0` this was its DELIBERATE
CORRECTION class, not a collision:
- the note is slice 3's own, for the same card, and still unreleased
(`pre.json` lists no consumed changeset);
- this PR changes the behaviour it describes (carried note a): its
attribution sentence said a field-less refusal is the field's once the
twin is admitted, which is no longer true where a unique `indexes[]`
entry covers the field;
- and it takes carried note d (`signUp`, then `POST
/auth/organization/create`).
Both edits were ordered by the dispatch for this slice. Superseded in
patch round 1: the maintainer ruled B (`6133232470`), so this note is
restored byte for byte from the merge base (`347895565e`), both
corrections are carried as sentences in this PR's own changeset
(`deb5c6ecc9`), and `check-empty-changeset` now exits 0.
## Acceptance notes
Noted, not filed (none is a reproducible defect through a public door, a
contract violation or an authoring trap):
- The runtime and the lint accept a required `lookup` as a
`controlled_by_parent` master (the third tier of `resolveCbpRelation` in
plugin-security and of the lint's master candidates), which ADR-0055
section 1 and the spec's `sharingModel` docblock do not state. The
family follows the spec and names those objects excluded: hotcrm's
`crm_campaign_member`, `crm_event_attendee` and `crm_article_feedback`,
3 of its 8. Carrier: none.
- A generic fixture cannot satisfy every app rule: with hotcrm's hooks
live, `contract_validation` refuses a contract whose term does not match
its dates, so `crm_contract` reads inconclusive there and is proven with
the hooks inert. Carrier: none.
- `fixtureBody` and its helpers are now copied in three family modules
(delete-behavior, unique-scope, this one), each module-private. A shared
one would live in `derive.ts`, outside this claim. Carrier: none.
- `provisionRlsPositionPersona` writes the reason "minted by
`objectstack verify --rls`" on the position row it creates, whichever
family calls it. Stored on an ephemeral stack only. Carrier: none.
## Deviations
- **The door.** PM mechanism assumption 5 named the REST data route;
assumption 4 requires a refusal to name the detail object and the
operation. Measured, the REST envelope cannot: it names the ROUTE's
object on every refusal (an app hook refusing with another object named
comes back naming the route's) and carries no operation. On hotcrm with
hooks live that turned `crm_opportunity_line_item`'s
`opportunity_line_item_price_fill` hook, refused on another object, into
four false contradictions at REST. The family therefore writes through
the engine as the signed-in principal, the three calls the REST ingress
makes with the caller's context, where the refusal keeps its own
`details`; there the same four read `unattributed`. The two-level
finding was measured at REST as well.
- **Assumption 2's object-gate half** is read from the server's
effective permissions (`/auth/me/permissions`), not re-merged from the
declared sets: it is the one merge of the sets the principal holds
(ADR-0124 D4), and a second copy of it is what ADR-0124 refuses.
- Conditional surfaces: two `NOT_A_STAND_IN` rows (the gate asked); the
`#22661` `CALLERS` row was not needed (the family never calls the
reference-target arbiter; the core `repo` project is green without one).
- `origin/main` (`038d4c9588`, one commit, not on this surface) was
merged before the PR, per AGENTS.md section 10; the build was refreshed
and every suite and gate here ran on the merged tree.
- Commits carry the model-free trailer pair per AGENTS.md, and this body
ends with its session-URL footer rather than the harness's own
attribution form.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 252a582 commit 965a1bf
12 files changed
Lines changed: 1941 additions & 15 deletions
File tree
- .changeset
- content/docs/deployment
- packages
- cli
- src/commands
- test
- verify
- src
- scripts
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1925 | 1925 | | |
1926 | 1926 | | |
1927 | 1927 | | |
| 1928 | + | |
1928 | 1929 | | |
1929 | 1930 | | |
1930 | 1931 | | |
| |||
1972 | 1973 | | |
1973 | 1974 | | |
1974 | 1975 | | |
1975 | | - | |
1976 | | - | |
1977 | | - | |
| 1976 | + | |
| 1977 | + | |
| 1978 | + | |
| 1979 | + | |
1978 | 1980 | | |
1979 | 1981 | | |
1980 | 1982 | | |
| |||
1985 | 1987 | | |
1986 | 1988 | | |
1987 | 1989 | | |
| 1990 | + | |
| 1991 | + | |
| 1992 | + | |
| 1993 | + | |
| 1994 | + | |
| 1995 | + | |
| 1996 | + | |
| 1997 | + | |
| 1998 | + | |
| 1999 | + | |
| 2000 | + | |
| 2001 | + | |
| 2002 | + | |
| 2003 | + | |
| 2004 | + | |
| 2005 | + | |
| 2006 | + | |
| 2007 | + | |
| 2008 | + | |
| 2009 | + | |
| 2010 | + | |
| 2011 | + | |
| 2012 | + | |
| 2013 | + | |
| 2014 | + | |
| 2015 | + | |
| 2016 | + | |
| 2017 | + | |
1988 | 2018 | | |
1989 | 2019 | | |
1990 | 2020 | | |
| |||
2014 | 2044 | | |
2015 | 2045 | | |
2016 | 2046 | | |
2017 | | - | |
2018 | | - | |
| 2047 | + | |
| 2048 | + | |
| 2049 | + | |
2019 | 2050 | | |
2020 | 2051 | | |
2021 | 2052 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
24 | 27 | | |
25 | 28 | | |
26 | 29 | | |
27 | 30 | | |
28 | 31 | | |
| 32 | + | |
29 | 33 | | |
30 | 34 | | |
31 | 35 | | |
| |||
99 | 103 | | |
100 | 104 | | |
101 | 105 | | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
102 | 111 | | |
103 | 112 | | |
104 | 113 | | |
105 | 114 | | |
106 | 115 | | |
107 | | - | |
| 116 | + | |
108 | 117 | | |
109 | 118 | | |
110 | 119 | | |
| |||
113 | 122 | | |
114 | 123 | | |
115 | 124 | | |
| 125 | + | |
116 | 126 | | |
117 | 127 | | |
118 | 128 | | |
| |||
136 | 146 | | |
137 | 147 | | |
138 | 148 | | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
139 | 153 | | |
140 | 154 | | |
141 | 155 | | |
| |||
216 | 230 | | |
217 | 231 | | |
218 | 232 | | |
| 233 | + | |
219 | 234 | | |
220 | 235 | | |
221 | 236 | | |
| |||
359 | 374 | | |
360 | 375 | | |
361 | 376 | | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
362 | 390 | | |
363 | 391 | | |
364 | 392 | | |
| |||
387 | 415 | | |
388 | 416 | | |
389 | 417 | | |
390 | | - | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
391 | 422 | | |
392 | 423 | | |
393 | 424 | | |
394 | 425 | | |
395 | 426 | | |
396 | | - | |
| 427 | + | |
397 | 428 | | |
398 | 429 | | |
399 | 430 | | |
400 | 431 | | |
401 | 432 | | |
402 | 433 | | |
| 434 | + | |
403 | 435 | | |
404 | 436 | | |
405 | 437 | | |
| |||
0 commit comments