pi-longhaul 0.0.0-stage → 1.0.1

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 (98) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/CONTEXT.md +129 -0
  3. package/LICENSE +21 -0
  4. package/README.md +427 -2
  5. package/config.example.json +22 -0
  6. package/docs/adr/0001-state-over-summary.md +23 -0
  7. package/docs/adr/0002-code-writes-facts-model-writes-diff.md +21 -0
  8. package/docs/adr/0003-default-deny-summarizer-routing.md +20 -0
  9. package/docs/adr/0004-verified-pi-versions.md +16 -0
  10. package/docs/adr/0005-learned-values-credential-filter.md +19 -0
  11. package/docs/adr/0006-summarizer-named-values.md +35 -0
  12. package/docs/evaluation.md +338 -0
  13. package/docs/how-it-works.md +193 -0
  14. package/docs/security.md +408 -0
  15. package/docs/setup-with-an-agent.md +147 -0
  16. package/fixtures/setup-check-session.jsonl +19 -0
  17. package/package.json +82 -4
  18. package/pi-compat.json +89 -0
  19. package/src/carry.ts +142 -0
  20. package/src/cold.ts +17 -0
  21. package/src/compaction-line.ts +114 -0
  22. package/src/config.ts +154 -0
  23. package/src/fact-shapes.ts +49 -0
  24. package/src/files-render.ts +42 -0
  25. package/src/index.ts +226 -0
  26. package/src/intent.ts +345 -0
  27. package/src/kept-window.ts +110 -0
  28. package/src/learned-secrets.ts +127 -0
  29. package/src/learned.ts +266 -0
  30. package/src/ledger/handoff-commands.ts +189 -0
  31. package/src/ledger/handoff-errors.ts +81 -0
  32. package/src/ledger/handoff-observe.ts +142 -0
  33. package/src/ledger/handoff-render.ts +91 -0
  34. package/src/ledger/handoff-workspace.ts +201 -0
  35. package/src/ledger/handoff.ts +200 -0
  36. package/src/log-safe.ts +90 -0
  37. package/src/log.ts +112 -0
  38. package/src/message-text.ts +8 -0
  39. package/src/narrative/call.ts +109 -0
  40. package/src/narrative/fit.ts +194 -0
  41. package/src/narrative/merge.ts +20 -0
  42. package/src/narrative/message-labels.ts +146 -0
  43. package/src/narrative/named-values.ts +176 -0
  44. package/src/narrative/parse.ts +291 -0
  45. package/src/narrative/policy.ts +44 -0
  46. package/src/narrative/prepare.ts +113 -0
  47. package/src/narrative/prompt.ts +209 -0
  48. package/src/narrative/reasoning.ts +39 -0
  49. package/src/narrative/route-config.ts +188 -0
  50. package/src/narrative/route-health.ts +79 -0
  51. package/src/narrative/routing.ts +120 -0
  52. package/src/narrative/run.ts +340 -0
  53. package/src/narrative/summarizer-headers.ts +80 -0
  54. package/src/narrative/timings.ts +122 -0
  55. package/src/narrative/transport-retry.ts +190 -0
  56. package/src/narrative/turns/conversation.ts +148 -0
  57. package/src/narrative/turns/digest.ts +78 -0
  58. package/src/narrative/turns/labels.ts +81 -0
  59. package/src/narrative/turns/policy.ts +59 -0
  60. package/src/narrative/turns/run.ts +214 -0
  61. package/src/narrative/turns/section.ts +166 -0
  62. package/src/narrative/turns/sections.ts +165 -0
  63. package/src/narrative/turns/transcript.ts +116 -0
  64. package/src/narrative/types.ts +125 -0
  65. package/src/narrative/usage.ts +81 -0
  66. package/src/narrative/validate.ts +261 -0
  67. package/src/narrative/warm.ts +75 -0
  68. package/src/notices.ts +47 -0
  69. package/src/one-line.ts +5 -0
  70. package/src/pass.ts +367 -0
  71. package/src/pi-compat.ts +72 -0
  72. package/src/prune.ts +107 -0
  73. package/src/recall-tool.ts +55 -0
  74. package/src/remember-tool.ts +79 -0
  75. package/src/render.ts +171 -0
  76. package/src/report-command.ts +75 -0
  77. package/src/report.ts +131 -0
  78. package/src/select.ts +39 -0
  79. package/src/session.ts +22 -0
  80. package/src/store/capfit.ts +45 -0
  81. package/src/store/close.ts +39 -0
  82. package/src/store/commit.ts +38 -0
  83. package/src/store/events.ts +275 -0
  84. package/src/store/lift-replacement.ts +67 -0
  85. package/src/store/lift-vocabulary.ts +118 -0
  86. package/src/store/lift-words.ts +354 -0
  87. package/src/store/log.ts +173 -0
  88. package/src/store/near-duplicate.ts +33 -0
  89. package/src/store/output.ts +141 -0
  90. package/src/store/project.ts +204 -0
  91. package/src/store/recall.ts +137 -0
  92. package/src/store/verify.ts +218 -0
  93. package/src/tasks.ts +98 -0
  94. package/src/tool-context.ts +42 -0
  95. package/src/tool-names.ts +11 -0
  96. package/src/tool-text.ts +53 -0
  97. package/src/world-render.ts +121 -0
  98. package/src/world.ts +134 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,128 @@
1
+ # Changelog
2
+
3
+ All notable changes to Longhaul are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html) over the contract the README defines
6
+ (*Versioning*): `longhaul.json` keys, environment variables, tool names and parameters, persisted
7
+ entries and log event types. Rendered state text, prompt wording and section layout are not part of it.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [v1.0.1] - 2026-10-10
12
+
13
+ ### Added
14
+
15
+ - A cross-version smoke (`npm run cross-version`), run by CI on every push and by the nightly job with the newest
16
+ pi: one transcript handed through every pi in `pi-compat.json`, oldest to newest and back, with a Longhaul
17
+ compaction by each. Every step must compact with an accepted diff, and the finished transcript must read back
18
+ with one span per compaction and no anomaly.
19
+
20
+ ### Changed
21
+
22
+ - Tested with pi 0.87.1, 0.99.0, 0.99.1, 0.99.2, 1.0.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 and 1.1.0; 0.99.0, 0.99.2 and
23
+ 1.0.3 are newly recorded in `pi-compat.json`. No code changes to the extension.
24
+ - Published from the release workflow through npm trusted publishing, so this version carries a provenance
25
+ attestation linking it to its source commit. v1.0.0 was published by hand and has none.
26
+
27
+ ## [v1.0.0] - 2026-10-10
28
+
29
+ ### Added
30
+
31
+ - The Objective keeps the session's opening request as its Goal line once the Request moves to a later one (an
32
+ active `/goal` takes the line instead), and the summarizer is told code keeps it. Before, the opening request
33
+ was shown only while it was one of the two newest earlier requests, and over the six eval sessions compacted in
34
+ a chain it was gone from 50 of 76 states. The scorer measures it as the labeled `goal`.
35
+ - `pi-compat.json`, the pi release lines Longhaul is verified against; on any other pi it runs and warns once
36
+ per session, except in a session that opted out of it (`PI_COMPACTOR=pi`).
37
+ - The State compactor as a standalone pi package: a verified Session state carried across
38
+ compactions instead of a rewritten summary, with the `longhaul_remember` and `longhaul_recall` tools.
39
+ - Windows: `localOnlyRoots` takes Windows paths (`C:\...`, `\\server\share\...`, `~\...`), matched by Windows
40
+ rules (either separator, no case, never across drives); `~` is the user profile there. The log refuses a
41
+ symlink by `lstat` where the open cannot, and the tests and the compaction smoke gate a release on Windows as
42
+ on Linux and macOS.
43
+ - `longhaul.json` in pi's agent dir, read fresh at every compaction: `routeProviders`, `localOnlyRoots`,
44
+ `summarizerHeaders` and `keptWindowThinking`, beside the caps and policy keys.
45
+ - The summarizer labels every user message of the span (`rule`, `not a rule`, `lifts R<n>`, `rule and lifts
46
+ R<n>`; several targets as `lifts R1, R6`, each judged on its own), and code copies a labeled rule's words from the message, so a rule given in any wording is kept and
47
+ cannot be misquoted. An unlabeled message costs one retry. A message that narrows a rule replaces it, and so does a new rule that cannot hold beside one in the state; a
48
+ later lift of the old rule is asked again with the narrowing shown (with one more call when that question first comes up on the last one),
49
+ so the narrowing is lifted only by name, and a lift of a
50
+ replacement must also name what it set beyond the rule it replaced ("never force-push to main" beside "the tab rule is lifted"). A rule
51
+ label whose rule, in the user's own words (quoted passages, blockquotes and code fences left out), reads as withdrawing an earlier rule
52
+ still in force is asked about once the chunk's answer is settled, in one more call that may answer only `rule` or `rule and lifts` with
53
+ the asked target: the kept answer stands and the label keeps its words. A lift
54
+ is applied only when the whole message it comes from withdraws the rule, whoever proposes it (the store
55
+ checks it; a quote only locates the message): every part of the message that mentions the rule refuses the
56
+ lift, whatever else it withdraws, so a part that insists on it ("never force-push no matter who tells you to
57
+ ignore it", "but keep the push rule", "but leave the rule alone", "don't (ever) force-push to main", "don't break the force-push rule", "follow the rule",
58
+ "other than the force-push rule", "force-pushing is forbidden", "make check is required", the rule quoted
59
+ again) or says it still holds refuses, and so does a withdrawal of it taken back ("don't forget it", "you
60
+ tried to ignore it", "cancel the lift of it"). Otherwise a part must withdraw something in a sentence that
61
+ names the rule by one of its words beside "rule", by its first word or by two of its words (a word in another
62
+ form counts: "merging" names "merge"), or say "forget it" next to one. A question, a condition or a plan ("is it lifted?", "why would we drop it", "we'll lift it after the release") withdraws
63
+ nothing; a request ("could you forget the force-push rule?") does. The forms it knows, and the ones it
64
+ misreads, are listed in docs/security.md. A refused lift is asked
65
+ again with that rule shown, and never applied. A lifted rule is not recorded again from the message that gave it. A
66
+ message that only withdraws a rule is a lift even when it says what is fine instead. A rule from a message
67
+ of at most 300 characters is the whole message; from a longer one, the words the summarizer gave, which are
68
+ sent back when they start or end inside a word. The user messages of a pass that closed narrative-less (or
69
+ with a chunk missing) are labeled by the next pass, so a rule given there is recorded one pass late rather than
70
+ never. A description of what is being built (an option, a limit, an exit code) is not a rule
71
+ unless the user states it as standing over later work. A permission ("you may push to main now") is a one-off, not a lift. Taking over from another compactor, the
72
+ model also sees and labels the user's other earlier messages, so a rule a later message withdrew is not carried
73
+ back in and a rule worded without a rule word ("tests go under tests/") is carried; one longer than the
74
+ excerpt shown is recorded only from the exact words given for it. After a fallback (a failed pass, then pi's
75
+ own compaction), only the messages since Longhaul's last whole span are offered, so a rule the state had
76
+ lifted or named a one-off is not recorded again. Only an earlier message that reads as a standing rule is recorded
77
+ without a label: a sentence, not a question, that says "from now on" and withdraws nothing, or
78
+ has a clause with a rule word outside a request for now ("don't commit yet"), a withdrawal, "never mind" and a
79
+ described feature ("an option that keeps only lines").
80
+ Such a message may carry a label too, so one that replaced an earlier rule ("two-space indentation instead of
81
+ tabs from now on") withdraws it; left unlabeled it is recorded
82
+ in message order and a replacement it makes is asked about. A value the earlier summary records is kept when an
83
+ earlier turn shows it verbatim, checked as a span's value is; the summary itself is never a source.
84
+ - The summarizer moves the objective only forward: quoting the objective's own message again changes nothing
85
+ (or attaches the done-when it lacked), and a move back to an earlier message is refused; an explicit
86
+ `longhaul_remember` may still point back. The scorer counts a move by the message quoted, and backward moves
87
+ apart (`objectiveBack`).
88
+ - Learned values the summarizer names: a timing, a total or a port a turn reported, with a name and the turn
89
+ it came from, kept only when it has a value's shape (a number, alone or with a unit, a version, a short
90
+ hash, a path or file name, `<placeholders>` allowed in a name format, a host and port, a date), stands
91
+ verbatim in that turn, and no part of it stood on a credential line (a short-code key whose value
92
+ holds a digit, such as `ADMIN_PIN=`, `AdminPIN=`, `adminPin=` or a JSON `"otp_code":` (escaped quotes too), a
93
+ base32 one-time-password seed under `TOTP=` or `totp_key:`, grouped or not, or `oathtool -b` / an `oathtool` key / `ykman oath accounts add` / `sshpass -p` / `htpasswd -b` / `openssl -pass pass:` or `-k` / `keytool -storepass` / `redis-cli -a` / a prefixed flag such as `--db-password` / `--token-code` / `--authentication-code1`, included), after a credential word in prose ("the PIN is ...", where three characters with a digit are
94
+ enough) or in a `user:pass@` (a URL password holding `/` or `:` included) anywhere in the span (ADR 0006).
95
+ Placeholders a value code picks from tool output make one file name, never a path. It renders as
96
+ `- 166 ms (u10; the first benchmark time)`: the name is the summarizer's label, at most 40 characters. Code no longer picks identifiers or dotted names read from source
97
+ files, which the agent can read again.
98
+ - A model whose reasoning cannot be turned off is asked for its lowest level, with room for it; every
99
+ output budget is capped at the model's own output limit. The level asked for is in the log record. An
100
+ answer cut off at the limit with nothing usable is retried with twice the budget (at least 8,192 tokens).
101
+ - The kept window's old thinking is left out of later requests where that is verified safe.
102
+ - The workspace snapshot runs git with hooks, fsmonitor, external diff and textconv off, ignores submodules,
103
+ and skips a repository whose own config defines a filter driver, and a partial clone, whose on-demand fetch
104
+ would run the transport the repository names, and does not run at all when `GIT_CONFIG` is set. It needs
105
+ git 2.26 or newer.
106
+ - A Compaction line under pi's compaction block in the transcript: how long the compaction took and what the
107
+ pass cost, or that it fell back to pi's own compaction and why (`compaction-state.line`, display only).
108
+ `@earendil-works/pi-tui` joins the `*` peer dependencies pi provides.
109
+ - `/longhaul-report`: a bug-report digest written to a private file, with no names, paths or message
110
+ text; error text is kept with the home directory, URLs and IP addresses masked.
111
+ - `npm run score`: the paired scorer, with pi's own compaction as the `pi-default` baseline arm. It replays each
112
+ compaction with the recording's `keepRecentTokens` (`SCORE_KEEP_RECENT_TOKENS`) and scores only those whose
113
+ rebuilt cut matches the recorded one; anything it leaves out is named in a `not scored:` line. The replay is
114
+ chained by default: each arm continues from its own earlier compactions, so a session runs on one compactor
115
+ throughout (`SCORE_CHAIN=0` replays each compaction on its recorded path). Labeled measures score the rules,
116
+ lifts and values written by hand in `eval/sessions/labels.json` (`SCORE_LABELS`), beside the word-list ones.
117
+ `reused` counts tool-output values the next turns need again, and `factsWithRecall` credits `longhaul_recall`
118
+ beside the text-only `facts`.
119
+ - Docs: a setup guide written for coding agents, the security model, the evaluation method and its first
120
+ results (one local 27B model, chained and takeover runs, every measure including those pi's own wins), and
121
+ contributor guides (AGENTS.md, CONTRIBUTING.md, SECURITY.md, issue and pull request templates).
122
+ - CI: tests on Linux, macOS and Windows, a compaction smoke that loads the packed package
123
+ into a real pi with a fake model, a secret scan, a workflow security audit (zizmor), Dependabot updates and a package-contents check; a nightly run against the newest pi; npm publishing
124
+ by trusted publishing, then a GitHub Release carrying the tag's changelog notes.
125
+ - `docs/how-it-works.md`: a compaction step by step, the log records, and the tools for looking at a state.
126
+ - `eval/` (repository only): six synthetic scenarios, and the recorder and checker that make the
127
+ evaluation sessions; `eval/matrix.ts`, one model-matrix cell: a real compaction on a given provider and
128
+ model, then whether the session still knows a rule and a value from before it.
package/CONTEXT.md ADDED
@@ -0,0 +1,129 @@
1
+ # Longhaul glossary
2
+
3
+ The words Longhaul's code and docs use, and what each one means. When code and this file disagree, this
4
+ file wins and the code is renamed.
5
+
6
+ ## The state
7
+
8
+ **State compactor**:
9
+ A compactor that carries a **Session state** forward instead of rewriting a summary. Longhaul is one; its
10
+ product name is always given with the tagline "a state compactor for pi".
11
+ _Avoid_: summarizer (that is the model call inside it)
12
+
13
+ **Session state**:
14
+ What survives a compaction: the **Objective**, the user's standing corrections in their own words, decisions and
15
+ dead ends with evidence, the **World**, the files, **Learned values**, one work-log line per **Span**, and Now.
16
+ It is kept as an append-only log on the transcript and rendered under fixed caps.
17
+ _Avoid_: summary, memory
18
+
19
+ **Span**:
20
+ The stretch of conversation between two compactions. Each compaction closes one span.
21
+
22
+ **Pass**:
23
+ What Longhaul does at one compaction: record the facts, fold the state so far, ask for the **Diff**, verify
24
+ it, and close the span.
25
+
26
+ **Diff**:
27
+ The model's whole contribution to a pass: the corrections and lifts it found, decisions, dead ends, a
28
+ narrative line and Now, as a small JSON answer that code verifies before accepting any of it.
29
+ _Avoid_: summary
30
+
31
+ **Compaction line**:
32
+ The line Longhaul shows in pi's transcript under each compaction it ran, or fell back from: how long the
33
+ compaction took, and what the pass cost or why it fell back. Display only: it is never sent to the model.
34
+ _Avoid_: compaction notice (a notice is pi's transient status message)
35
+
36
+ **Narrative-less pass**:
37
+ A pass whose diff failed entirely. The span still closes from what code recorded.
38
+
39
+ **Ledger**:
40
+ Every command the session ran with its exit code, and every error, each with an id (`C12`, `E3`) that
41
+ evidence can cite.
42
+
43
+ **Objective**:
44
+ The state's first section: the **Goal line**, the **Request**, when the work counts as done, and the
45
+ **Earlier requests**.
46
+
47
+ **Request**:
48
+ The user's request the work is answering now, quoted verbatim. The summarizer moves it to a later message when
49
+ the user moves to a different request, never back to an earlier one.
50
+ _Avoid_: task, objective (the section, not the quote)
51
+
52
+ **Goal line**:
53
+ What the session is for, kept by code and never moved by the summarizer: the active pi-goal `/goal`, else the
54
+ session's **Opening request** once the Request has moved away from it.
55
+ _Avoid_: objective, project goal
56
+
57
+ **Opening request**:
58
+ The first Request the session had. Outside a `/goal` it becomes the Goal line, so the session's purpose survives
59
+ however often the Request moves.
60
+
61
+ **Earlier requests**:
62
+ The newest Requests the summarizer moved away from, other than the Goal line; they may already be done.
63
+
64
+ **World**:
65
+ The workspace at the moment of compaction: branch, commit, whether the tree is clean, the last result of each
66
+ test, build or lint command, open errors, background tasks still running. Newer than anything the model wrote.
67
+
68
+ **Correction**:
69
+ A standing rule the user gave, kept verbatim with the message it came from. A **Lift** is the user withdrawing
70
+ one; the correction stays visible as history.
71
+ _Avoid_: preference, constraint (pi's own summary heading, not this concept)
72
+
73
+ **Learned values**:
74
+ Values a later turn may need again, verbatim, with where each came from: those the summarizer names from a turn
75
+ (a timing, a total, a port), kept only when they stand in that turn, and those code picks from local tool output
76
+ that the session used again (a hash, a version, a long number).
77
+ _Avoid_: facts (the scorer's measure of a related but different set)
78
+
79
+ ## Configuration
80
+
81
+ **Agent dir**:
82
+ pi's own configuration directory: `~/.pi/agent`, or whatever `PI_CODING_AGENT_DIR` names. Longhaul keeps
83
+ its configuration and its log there, never next to its own code.
84
+
85
+ **Longhaul config**:
86
+ The file `longhaul.json` in the **Agent dir**. Every key is optional and falls back on its own; a file that
87
+ is missing or is not a JSON object means the defaults. It is read again at every compaction, so an edit
88
+ needs no restart.
89
+ _Avoid_: config.json, settings
90
+
91
+ **Summarizer route**:
92
+ The model that writes the diff for a compaction when it is not the session's own model. Chosen in order:
93
+ the `PI_COMPACT_STATE_MODEL` environment variable, then a **Local-only root**, then the **Longhaul config**,
94
+ then none (the session's model).
95
+ _Avoid_: compaction model, routing model
96
+
97
+ **Route provider**:
98
+ A provider named in the **Longhaul config**'s `routeProviders`. Only a session whose model comes from a
99
+ route provider may use a **Summarizer route**; every other session summarizes on its own model, so its
100
+ conversation never reaches a provider the user did not choose for it. None by default.
101
+ _Avoid_: allowed provider, trusted provider
102
+
103
+ **Local-only root**:
104
+ A directory named in the **Longhaul config**'s `localOnlyRoots`. A session working in it or beneath it
105
+ summarizes on its own model whatever route is configured, because its conversation must not leave the
106
+ machine through a summarizer. Only the environment override beats it. None by default.
107
+ _Avoid_: pinned root, local summarization root
108
+
109
+ **Summarizer headers**:
110
+ HTTP headers the **Longhaul config** adds to the summarizer's own model calls, per provider. pi lets an
111
+ extension add headers to the requests its agent loop makes, but not to a compaction's, so a provider that
112
+ routes by a header would otherwise get the summarizer's request without it. A header the request already
113
+ carries wins. None by default.
114
+ _Avoid_: affinity headers (one use of them, not the concept)
115
+
116
+ **Kept window**:
117
+ The recent messages a compaction leaves unsummarized, which pi sends again after the state on every later request.
118
+ Their old reasoning is left out of those requests where that is known to be safe, so a model re-reads the state
119
+ and recent tool input and output, not its earlier thinking. An active tool turn is never cut on an API that needs
120
+ its thinking back.
121
+ _Avoid_: kept messages, tail
122
+
123
+ ## Models
124
+
125
+ **Must-reason model**:
126
+ A model whose reasoning cannot be turned off: pi's model entry marks reasoning off as unsupported. The diff
127
+ needs no reasoning, so every other model is asked for none; a must-reason model is asked for its lowest
128
+ level instead, with a larger output budget so its reasoning cannot crowd out the answer.
129
+ _Avoid_: thinking model, reasoning model (most reasoning models can turn it off)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Justin Barlow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.