@mnstry/atelier 0.2.0-alpha.13 → 0.2.0-alpha.14
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.
- package/CHANGELOG.md +233 -0
- package/README.md +22 -5
- package/docs/blocks/capability-stewardship.md +1 -1
- package/docs/blocks/learning-harnesses.md +1 -1
- package/docs/capability-stewardship.md +2 -4
- package/docs/ingestion.md +3 -3
- package/docs/inquiry-harnesses.md +2 -3
- package/docs/install.md +3 -3
- package/docs/learning-harnesses.md +2 -3
- package/docs/local-services.md +42 -3
- package/docs/obsidian-contract.md +23 -0
- package/docs/obsidian-plugin.md +214 -3
- package/docs/obsidian.md +271 -11
- package/docs/personal-workspace.md +13 -4
- package/docs/release-engineering.md +2 -1
- package/docs/repo-boundary-guard.md +26 -3
- package/docs/semantic-operation.md +4 -3
- package/docs/upgrade.md +174 -2
- package/fixtures/templates/local-library.v1.json +2 -1
- package/package.json +2 -1
- package/plugins/obsidian/main.js +57 -8
- package/plugins/obsidian/manifest.json +1 -1
- package/src/boundary/content-rules.mjs +532 -67
- package/src/boundary/policy.mjs +135 -56
- package/src/cli/run.mjs +3 -2
- package/src/commands/obsidian.mjs +23 -7
- package/src/graph/graph.mjs +13 -5
- package/src/graph/knowledge-graph.mjs +19 -1
- package/src/ingestion/optional/graphify/graphify_supplier.py +5 -0
- package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
- package/src/ingestion/optional/graphify/native-pins.json +306 -0
- package/src/knowledge-health/rule-identity.mjs +14 -9
- package/src/personal-workspace/index.mjs +1 -1
- package/src/personal-workspace/selection.mjs +17 -1
- package/src/project/path-match.mjs +2 -1
- package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
- package/src/projection/obsidian/personal-workspace.mjs +87 -4
- package/src/projection/obsidian/proposals/adapter.mjs +46 -23
- package/src/projection/obsidian/proposals/observation.mjs +40 -2
- package/src/projection/obsidian/publication/publisher.mjs +72 -5
- package/src/projection/project.mjs +3 -2
- package/src/runtime/obsidian/engine.mjs +293 -55
- package/src/runtime/obsidian/lifecycle.mjs +17 -12
- package/src/runtime/obsidian/login-item.mjs +4 -1
- package/src/runtime/obsidian/observation.mjs +36 -4
- package/src/runtime/obsidian/opening.mjs +3 -2
- package/src/runtime/obsidian/personal-selection.mjs +125 -0
- package/src/runtime/obsidian/pipeline.mjs +44 -3
- package/src/runtime/obsidian/private-lock.mjs +39 -4
- package/src/runtime/obsidian/service-main.mjs +58 -12
- package/src/runtime/obsidian/service.mjs +66 -6
- package/src/runtime/obsidian/view-permission.mjs +334 -0
- package/src/ui/control-system.mjs +201 -0
- package/src/ui/html-primitives.mjs +3 -0
- package/src/upgrade/closure-diagnostics.mjs +135 -0
- package/src/upgrade/closure.mjs +649 -0
- package/src/upgrade/upgrade.mjs +19 -7
- package/templates/external-project-workspace/pack-lifecycle.json +2 -1
package/docs/obsidian-plugin.md
CHANGED
|
@@ -32,7 +32,24 @@ is `src/projection/obsidian/plugin-bridge/channel.mjs`; the service's half is
|
|
|
32
32
|
a window with the view, its state and the reason in words, the committed and
|
|
33
33
|
the prepared generation, when freshness was last checked, the held, retained
|
|
34
34
|
and pending edits, the service address and state, and the plugin and app
|
|
35
|
-
versions.
|
|
35
|
+
versions. While the service is unreachable, current generations, check time
|
|
36
|
+
and edit counts are unknown. After a connection refusal or timeout on the
|
|
37
|
+
same channel, the window also shows the last authenticated state, reason,
|
|
38
|
+
generation and check under "Last observed" labels. Those rows are historical
|
|
39
|
+
and never establish current freshness, service health or permission.
|
|
40
|
+
History stays only in memory. It is cleared on a channel change, missing or
|
|
41
|
+
unsupported setup, authentication refusal, an untrusted answer, a missing or
|
|
42
|
+
malformed view, a report for another scope, a disabled view or unload.
|
|
43
|
+
A connection reset or any other failure outside connection refusal and
|
|
44
|
+
timeout also clears it. Without a prior usable report, "Last observed
|
|
45
|
+
status" says "not available". After startup admits the channel, a republish
|
|
46
|
+
that names a different channel (host, port, scope or key) resets the display
|
|
47
|
+
to `connecting` before its next answer; this reset does not repeat an
|
|
48
|
+
unchanged notice or lose the transition back to current. A republish of the
|
|
49
|
+
same channel keeps the display and history, and unusable data shows
|
|
50
|
+
`not set up`.
|
|
51
|
+
Republish keeps an unsupported-app or unavailable-Node setup label and its
|
|
52
|
+
channel closed. Plugin 1.1.2 adds this status continuity.
|
|
36
53
|
- A notice appears when the view moves into a state that needs a person (held,
|
|
37
54
|
stale, service unreachable, app too old) and when it is current again.
|
|
38
55
|
`updating` is not announced: it is what every change looks like.
|
|
@@ -336,6 +353,124 @@ the vault's key without sending it, and the service proves it first
|
|
|
336
353
|
| `POST /plugin/release` | `sessionId`, `counter`, `mac` | sealed: whether the session was released |
|
|
337
354
|
| `POST /plugin/status` | `sessionId`, `counter`, `mac` | sealed: the view's state, reason, verification, committed and prepared generation, held notes (counted, not named), retained edits, open pending edits, and the service's own state |
|
|
338
355
|
|
|
356
|
+
A status answer carries the view and its pending edits only while the project
|
|
357
|
+
allows that view at the moment of the request, not as of the last tick
|
|
358
|
+
(`src/runtime/obsidian/view-permission.mjs`): its Obsidian settings are
|
|
359
|
+
enabled and declare the view, and its workspace pointer still names the
|
|
360
|
+
workspace this service runs for, with its private state where the service
|
|
361
|
+
keeps it. Otherwise the answer is still sealed and still carries the service's
|
|
362
|
+
own state, but `view` and `pendingEdits` are `null`, which the plugin already
|
|
363
|
+
reads as nothing known about the view. That covers a view turned off or taken
|
|
364
|
+
out of the settings before the next tick, and the freshness a later tick
|
|
365
|
+
writes for it, which keeps its generations and counts. It also covers anything
|
|
366
|
+
that cannot be established: settings that refuse, a project that does not
|
|
367
|
+
load, a pointer that cannot be read. The plugin then shows `Atelier: stale`
|
|
368
|
+
with the reason "no freshness recorded", keeps its session and asks again at
|
|
369
|
+
its next round; it is not told why, and it remembers nothing of the view to
|
|
370
|
+
show later.
|
|
371
|
+
|
|
372
|
+
Loading the project runs Git in child processes, which would otherwise happen
|
|
373
|
+
every two seconds for every open vault, so the project is kept:
|
|
374
|
+
|
|
375
|
+
- **What decides it.** The files the engine observes as configuration,
|
|
376
|
+
compared at every request by a digest of what they hold, never by their
|
|
377
|
+
times. Each file holds where it really is and the bytes there: its path
|
|
378
|
+
resolved as the system resolves it when the loader opens it (`realpath(3)`,
|
|
379
|
+
through a link anywhere on the way, a `..` after a link included), so a
|
|
380
|
+
configuration kept in a dotfiles repository and linked into the project is
|
|
381
|
+
kept like any other, and a link pointed elsewhere, the file's own or one to
|
|
382
|
+
a folder above it, is a change even where every byte is the same. A folder
|
|
383
|
+
in a file's place, a link that leads nowhere or to a folder, and a file
|
|
384
|
+
that cannot be read vouch for nothing: then nothing is kept, and the
|
|
385
|
+
project is loaded at every request. Where the system resolves no path for a
|
|
386
|
+
regular file that is there and is not a link (some volumes on Windows
|
|
387
|
+
answer `EISDIR`; a C library that needs `/proc` and has none), the file is
|
|
388
|
+
compared by its own path as given and its bytes, so the project is still
|
|
389
|
+
kept; a link above such a file, pointed elsewhere, is not seen there. A
|
|
390
|
+
link the system does not resolve vouches for nothing. The pointer and the
|
|
391
|
+
place of the private state are read at every request.
|
|
392
|
+
- **When it is kept.** Two loads in a row, each started after those files
|
|
393
|
+
were read and found to hold the same, must return the same project as far
|
|
394
|
+
as the decision reads one: the views it enables, where its pointer is, the
|
|
395
|
+
data root its local overlay prefers and the roots it protects. A field that
|
|
396
|
+
differs from load to load, such as a time or a personal-workspace binding
|
|
397
|
+
kept under a symbol key, does not keep a project from being kept. One
|
|
398
|
+
load is not enough: a file changed and changed back while it ran leaves the
|
|
399
|
+
same bytes around a project read from other ones. So three loads keep the
|
|
400
|
+
project when the service has just started, and two after a change.
|
|
401
|
+
- **For how long.** A kept project is loaded again once it is two and a half
|
|
402
|
+
minutes old, and is never used once it is five, the interval of the
|
|
403
|
+
engine's full reconciliation. Whatever no file here names (an overlay named
|
|
404
|
+
by `MNSTRY_ATELIER_LOCAL_CONFIG` that did not exist at the last load, for
|
|
405
|
+
instance) is seen by then.
|
|
406
|
+
- **A loader that answers a promise** is never waited for, because the plugin
|
|
407
|
+
gives a request a second and a half. Its view is told nothing until two of
|
|
408
|
+
its loads have agreed; one load runs at a time, and a kept project goes on
|
|
409
|
+
deciding, within its time, while it is loaded again. A load that has not
|
|
410
|
+
answered within two and a half minutes is given up and its later answer
|
|
411
|
+
ignored, whether a request or that answer is the first to come after the
|
|
412
|
+
deadline. A service bound to a personal home (`--personal-home`) has such a
|
|
413
|
+
loader: it starts from the home alone, and its plugins are told about a view
|
|
414
|
+
once two of the loads that compose the confirmed generation have agreed. A
|
|
415
|
+
service given only a loader that answers a promise still refuses to start
|
|
416
|
+
(see "Personal workspaces" in [obsidian.md](obsidian.md)).
|
|
417
|
+
- **A load that fails** (it throws, it is refused, it is given up, or a file
|
|
418
|
+
vouches for nothing under a loader that answers a promise) ends whatever
|
|
419
|
+
was kept, and is asked for again after two seconds, then four, up to
|
|
420
|
+
thirty, while the files hold the same. A change to them is tried at once,
|
|
421
|
+
with two exceptions. Until a load has answered and named the files, a
|
|
422
|
+
corrected configuration waits for the next try. And while one of the files
|
|
423
|
+
vouches for nothing there is no digest to change, so after a load that
|
|
424
|
+
failed then (one that answers a promise, or one that answers at once and
|
|
425
|
+
threw) a change to another file waits for the next try as well.
|
|
426
|
+
- **What the service shows of these loads.** Its status document counts
|
|
427
|
+
them (`statusLoads`: `answered`, `failed`, `givenUp`, `slow`, `notKept` and
|
|
428
|
+
`longestMs`). Its log gets a `status-project-loaded` line, with the
|
|
429
|
+
milliseconds, the outcome, flags and counts and never a path, only for a
|
|
430
|
+
load somebody has to see, each kind on its own row, written when the row is
|
|
431
|
+
1, 2, 4, 8 and so on long (from 2 for loads after which nothing is kept):
|
|
432
|
+
- a load that failed or was given up;
|
|
433
|
+
- a load that answered at once and took a second or more (`slow`). A second
|
|
434
|
+
is most of the second and a half the plugin gives a request, spent on the
|
|
435
|
+
listener's own event loop; a load that answers a promise holds nothing;
|
|
436
|
+
- a load after which nothing is kept although it named the files it
|
|
437
|
+
started over (`notKept`, from the second in a row, with `vouched` false
|
|
438
|
+
while a file vouches for nothing): a load at every request. A load that
|
|
439
|
+
named other files than it started over (the first, which started over
|
|
440
|
+
none, or the one after a change that names other files, such as a new
|
|
441
|
+
confirmed selection) neither counts nor ends that row, so loads that
|
|
442
|
+
each name other files than the one before are never counted. A start and
|
|
443
|
+
a change each have one load that names the files it started over and has
|
|
444
|
+
nothing to agree with yet, so neither writes such a line; each is
|
|
445
|
+
counted, so a healthy service shows one `notKept` at its start and one
|
|
446
|
+
after each change. A change that lands while a load reads the files,
|
|
447
|
+
names the same files, and changes the views or what the workspace is
|
|
448
|
+
resolved from, makes that load keep nothing as well, and so writes one
|
|
449
|
+
line and counts two. Any other change that lands then counts one, as a
|
|
450
|
+
change does, and writes nothing.
|
|
451
|
+
|
|
452
|
+
A row of failures goes on through loads that answer between them, and ends
|
|
453
|
+
at the first load that answers a minute or more after its last failure; a
|
|
454
|
+
line is written then (`afterFailures`). That line comes with the first load
|
|
455
|
+
after the minute, which under a kept project is the next reload, two and a
|
|
456
|
+
half minutes after the load that kept it, and none comes if no load
|
|
457
|
+
follows. A row of slow
|
|
458
|
+
loads ends the same way, at a load that answers in time a minute or more
|
|
459
|
+
after the last slow one. So, while loads come less than a minute apart (a
|
|
460
|
+
load at every request, or the retries after a failure), a loader that
|
|
461
|
+
fails, or is slow, every other load or every third keeps one row, written
|
|
462
|
+
less and less often, instead of a line per load. While a project is kept it
|
|
463
|
+
is loaded again every two and a half minutes, so there a row of failures
|
|
464
|
+
ends at the next reload that answers: a loader that answers at least one
|
|
465
|
+
reload between two failures writes each failure and the load that ends its
|
|
466
|
+
row, as before, and one that fails at every reload and answers only at its
|
|
467
|
+
retry keeps one row. Each slow load is written there, as before. A row
|
|
468
|
+
of loads after which nothing is kept ends at the next load that keeps the
|
|
469
|
+
project and named the files it started over. A healthy service writes none
|
|
470
|
+
of these lines. The log is cut over only when it is opened, so a line per
|
|
471
|
+
load, every two seconds while a file vouches for nothing, would have grown
|
|
472
|
+
it without bound.
|
|
473
|
+
|
|
339
474
|
Refusals, before anything else is looked at: `Host` other than the listener's
|
|
340
475
|
literal loopback authority, a cross-site `Origin` or `Sec-Fetch-Site`, a path
|
|
341
476
|
other than one of the nine exactly (a query included), or another method (the
|
|
@@ -414,7 +549,11 @@ is authenticated, so they only ever decide what is shown.
|
|
|
414
549
|
directory changed) and one HMAC per vault; at most four handshakes wait per
|
|
415
550
|
view, and recorded challenges can keep them filled for at most about
|
|
416
551
|
thirty seconds after a restart, since a challenge is answered only within
|
|
417
|
-
thirty seconds of the time it names.
|
|
552
|
+
thirty seconds of the time it names. A status request of a session costs a
|
|
553
|
+
read of the project's configuration files and of its workspace pointer; the
|
|
554
|
+
project itself is loaded again when one of those files changed, every two
|
|
555
|
+
and a half minutes, or at every request while one of them vouches for
|
|
556
|
+
nothing (see "The channel").
|
|
418
557
|
- Egress. `plugins/` is in the egress scan, which fails a request whose target
|
|
419
558
|
is not a literal loopback address, and the release audit requires the three
|
|
420
559
|
plugin files in the package and scans them in the tarball.
|
|
@@ -488,6 +627,72 @@ note open in the editor.
|
|
|
488
627
|
|
|
489
628
|
## Evidence
|
|
490
629
|
|
|
630
|
+
- `test/obsidian-plugin-status-continuity.test.mjs` exercises transient history
|
|
631
|
+
and the shipped request flow through a finite, socket-free `http.request`
|
|
632
|
+
stand-in. Canonical channel computations seal the invented replies. It
|
|
633
|
+
checks refused and timed-out lease/status requests, rejected leases followed
|
|
634
|
+
by interrupted handshakes, listener proof and answer authentication,
|
|
635
|
+
republished data, notice deduplication and recovery, and late answers after
|
|
636
|
+
a channel change or unload. It opens no service or app and writes no plugin
|
|
637
|
+
data. These tests complement the real-listener and real-app checks below.
|
|
638
|
+
- `test/obsidian-view-permission.test.mjs` asks whether the project allows a
|
|
639
|
+
view the way the service asks before a status answer, over real project
|
|
640
|
+
files, the real loader and the real workspace resolution, with no listener,
|
|
641
|
+
with time a number the test moves: every way a view or its
|
|
642
|
+
workspace is withdrawn and given back; the workspace's identity compared as
|
|
643
|
+
well as its place; two loads per change however often a view is asked
|
|
644
|
+
about; a change that keeps a file's size and modification time; a change
|
|
645
|
+
that lands during a load; a file the project comes to name; a configuration
|
|
646
|
+
file that is a link, retargeted, or a link to a link; a folder, a link that
|
|
647
|
+
leads nowhere and a link to a folder in a file's place; a kept project's
|
|
648
|
+
time; a loader that fails, with its backoff; and a loader that answers a
|
|
649
|
+
promise: one load at a time, a change undone and done again while it runs,
|
|
650
|
+
through a link, a load that never answers, one whose answer comes after its
|
|
651
|
+
deadline before any request, and one refused again and again; a link whose
|
|
652
|
+
target has a `..` after a link, and a link to a folder above a file pointed
|
|
653
|
+
at a byte-for-byte copy; two loads that differ in a time and a symbol-keyed
|
|
654
|
+
binding, or in a protected root; how long each load took and how it ended;
|
|
655
|
+
and the service refusing to start on a loader that answers a promise, a
|
|
656
|
+
refused promise or a thenable. Mutations of the module (a link that vouches
|
|
657
|
+
for nothing, a project kept from one load whose files are read again when
|
|
658
|
+
it answers, no deadline, no backoff, no identity check, the files not taken
|
|
659
|
+
from the load, no time limit, a late answer taken, JavaScript's realpath, no
|
|
660
|
+
real path for a regular file, the whole project compared, no protected
|
|
661
|
+
roots compared) and a service that does not refuse each fail at least one
|
|
662
|
+
of them. It also covers: a late refusal after the deadline; which loads the
|
|
663
|
+
log report writes (a row of failures or of slow loads kept through loads in
|
|
664
|
+
time between them until a minute without one, so a loader that fails every
|
|
665
|
+
other or every third load writes one row; loads after which nothing is
|
|
666
|
+
kept, from the second in a row, and loads that named other files, which
|
|
667
|
+
neither count nor end that row) and that a line holds no path; a load at
|
|
668
|
+
every request written while a file vouches for nothing and while loads
|
|
669
|
+
never agree, and none at a change that names other files; each input of the
|
|
670
|
+
workspace resolution (pointer folder, data-root preference, protected
|
|
671
|
+
repositories) keeping two loads apart; a system that resolves no path, for
|
|
672
|
+
a regular file and for a link; and, through the test service entry in a
|
|
673
|
+
child process, a start under `--startup` on a promise, a refused promise,
|
|
674
|
+
a thenable (its `then` called once) and a refused promise with a `then` of
|
|
675
|
+
its own that throws (never called), which asks the loader once, records the
|
|
676
|
+
refusal and ends with 0; and one on a loader that throws `undefined`, a
|
|
677
|
+
value with no prototype or one whose `code` cannot be read, or answers a
|
|
678
|
+
project whose `then` cannot be read, which logs `untyped-error` with the
|
|
679
|
+
text of what was thrown (its message, or the value itself, `undefined` for
|
|
680
|
+
`undefined`, and `unprintable` when it cannot be made text) and ends
|
|
681
|
+
with 1. `test/obsidian-personal-selection.test.mjs` covers a locator that
|
|
682
|
+
answers a thenable (its `then` called once) or throws `undefined`. Ten more
|
|
683
|
+
mutations (the loader asked twice at such a start, its
|
|
684
|
+
promise not let go, every load or every failure written, no fallback for a
|
|
685
|
+
regular file or one for a link too, a late refusal not given up, and each
|
|
686
|
+
of the three inputs dropped) each fail at least one case, and so do a row
|
|
687
|
+
of failures or of slow loads ended at any load in time, a load after which
|
|
688
|
+
nothing is kept written from the first in a row, the first load counted
|
|
689
|
+
among those, a kept project that does not end that row, a load that named
|
|
690
|
+
other files counted or ending it, and the files taken as vouching when they
|
|
691
|
+
did not.
|
|
692
|
+
`test/obsidian-personal-workspace.test.mjs` keeps a bound personal
|
|
693
|
+
workspace from the loader that composes in a worker: its views once two
|
|
694
|
+
compositions agree, nothing while its generation is refused, and the next
|
|
695
|
+
generation's views once that is bound.
|
|
491
696
|
- `test/obsidian-plugin.test.mjs` runs the shipped `main.js` in a stand-in app
|
|
492
697
|
against a real listener on an ephemeral loopback port: parity of the
|
|
493
698
|
plugin's constants and of every proof and MAC with the channel contract (a
|
|
@@ -517,7 +722,13 @@ note open in the editor.
|
|
|
517
722
|
drift left for the person asked about once),
|
|
518
723
|
and an upgrade; the service publishing the plugin and the plugin it
|
|
519
724
|
published holding the view; qualification from a plugin report, with one
|
|
520
|
-
launch or two;
|
|
725
|
+
launch or two; `status` and `open` reporting it; a view the project turns
|
|
726
|
+
off or no longer declares, told nothing of what the service stored about
|
|
727
|
+
it, before the next tick and after it, and told as before once it is
|
|
728
|
+
allowed again; a project whose pointer is pointed at another workspace,
|
|
729
|
+
told nothing until it names this one again; and a loader that answers a
|
|
730
|
+
promise, never waited for, with the plugin keeping its session meanwhile.
|
|
731
|
+
A spawn guard refuses
|
|
521
732
|
any child of the suite that could reach an Obsidian with the developer's
|
|
522
733
|
own `HOME`.
|
|
523
734
|
- The same file has three real-app tests, skipped unless
|
package/docs/obsidian.md
CHANGED
|
@@ -538,9 +538,19 @@ earlier one, so two installations used on one workspace (a global and a
|
|
|
538
538
|
project-local one, say) do not replace each other's service on every open:
|
|
539
539
|
`open` answers `service-unavailable` / `service-other-release`, and `service
|
|
540
540
|
start` answers that it runs, with `release: later`; run `atelier obsidian
|
|
541
|
-
service stop`, then open again, or open with the later release. A service
|
|
542
|
-
|
|
543
|
-
|
|
541
|
+
service stop`, then open again, or open with the later release. A service that
|
|
542
|
+
does not answer health in time, in a stretch of a tick that does not yield, is
|
|
543
|
+
not stopped (`open` answers `busy`), and a listener that does not prove itself
|
|
544
|
+
this workspace's service is never touched. While a view is being prepared and
|
|
545
|
+
published the service does answer: `open` asks it for a tick that starts after
|
|
546
|
+
the one in flight and waits for that tick, two minutes at most unless
|
|
547
|
+
`atelier obsidian open --wait-ms MS` sets another bound, before it reports the
|
|
548
|
+
view as it is (one that read `current` is reported `updating`, with the reason
|
|
549
|
+
`tick-still-running`, while that tick has not ended); and `service stop` is
|
|
550
|
+
accepted and ends the
|
|
551
|
+
service after the tick in flight or after its shutdown grace, whichever comes
|
|
552
|
+
first. A publication cut short that way is settled by the publisher's restart
|
|
553
|
+
recovery at the next one.
|
|
544
554
|
|
|
545
555
|
The first time Obsidian opens a view's vault it asks "Do you trust the author
|
|
546
556
|
of this vault?", because every vault Atelier publishes carries Atelier's own
|
|
@@ -862,8 +872,9 @@ generationId })` in `src/projection/obsidian/personal-workspace.mjs`. Both
|
|
|
862
872
|
arguments are explicit; neither is discovered from the environment or the
|
|
863
873
|
working directory. A loader uses `loadBoundProject` with the same arguments,
|
|
864
874
|
or `loadBoundProjectOffThread`, which composes in a worker thread; the engine
|
|
865
|
-
awaits its loader.
|
|
866
|
-
|
|
875
|
+
awaits its loader. The command, the service and the login item bind a personal
|
|
876
|
+
home through `--personal-home`, which follows the generation the person
|
|
877
|
+
confirmed there (see "A confirmed selection", below).
|
|
867
878
|
|
|
868
879
|
- **One graph.** The binding is attached to the generation's resolved
|
|
869
880
|
`atelier.project.json` under one symbol key. When a project carries it, the
|
|
@@ -984,6 +995,133 @@ keeps one projection configuration per person.
|
|
|
984
995
|
in memory. A project configuration that declares it itself is refused at
|
|
985
996
|
every build (`personal-binding-lost`, naming the key).
|
|
986
997
|
|
|
998
|
+
### A confirmed selection
|
|
999
|
+
|
|
1000
|
+
`--personal-home DIR`, given instead of `--project` to `atelier obsidian`, to
|
|
1001
|
+
the service entry and in a login item's unit, binds the personal home DIR
|
|
1002
|
+
(`src/runtime/obsidian/personal-selection.mjs`). Without it, every argument,
|
|
1003
|
+
unit and loader is what it was.
|
|
1004
|
+
|
|
1005
|
+
- **What is loaded.** The generation the person last confirmed with
|
|
1006
|
+
`selectPersonalGeneration` (see [personal-workspace.md](personal-workspace.md)),
|
|
1007
|
+
and nothing else. The loader reads the head of the selection history from
|
|
1008
|
+
the history alone (`readPersonalSelectionHead`), loads the generation it
|
|
1009
|
+
names through the route above (in a worker for the service, in the
|
|
1010
|
+
command's thread for a command), then reads the head again. A head that
|
|
1011
|
+
moved meanwhile refuses `personal-selection-changed`. Nothing here confirms
|
|
1012
|
+
a selection or chooses one for the person.
|
|
1013
|
+
- **Nothing confirmed publishes nothing.** No record refuses
|
|
1014
|
+
`nothing-selected`; a confirmed generation whose inputs were edited and not
|
|
1015
|
+
confirmed again refuses `stale-generation`; a history whose hash chain does
|
|
1016
|
+
not hold refuses `selection-history-corrupt`; a history that ends at an
|
|
1017
|
+
earlier record than the running service has read refuses
|
|
1018
|
+
`selection-history-shortened`; a home whose history cannot be read refuses
|
|
1019
|
+
`personal-home-unavailable`, and a home that is gone or not
|
|
1020
|
+
private refuses with the module's own code (`root-missing`,
|
|
1021
|
+
`not-private-location`, `root-symlinked`). Every other refusal of the
|
|
1022
|
+
composition keeps its code. Nothing falls back to another generation or to
|
|
1023
|
+
the project without a binding. The vault keeps what it last published from
|
|
1024
|
+
a confirmed generation. `status`, `open` and `service start` load the
|
|
1025
|
+
selection and refuse with its code; the service's freshness shows every view
|
|
1026
|
+
`stale` with it, and its status document has it as the last tick's reason.
|
|
1027
|
+
`selection-history-shortened` comes only from a process that read the
|
|
1028
|
+
history longer, which is the running service: a command of the command line
|
|
1029
|
+
reads the history in a process of its own, as it then stands, so `status`
|
|
1030
|
+
reports the selection a cut history ends with while the service refuses it.
|
|
1031
|
+
A confirmed selection is reported by `status` (`selection`: the generation
|
|
1032
|
+
and the number of its record).
|
|
1033
|
+
- **The service starts whatever the selection.** A bound project's folder is
|
|
1034
|
+
its private home, so the home alone locates the workspace
|
|
1035
|
+
(`locatePersonalHome`): the service resolves its workspace from that at once
|
|
1036
|
+
and starts, and its engine awaits the loader at each tick. Started by a login
|
|
1037
|
+
item, the service asks the home, not the loader, for its workspace and log.
|
|
1038
|
+
`service status` and `stop`, the ticks `open` asks for, `service unit
|
|
1039
|
+
--remove` and `uninstall` locate the service the same way. When no project
|
|
1040
|
+
loaded (a restart while nothing is confirmed), the engine locates the
|
|
1041
|
+
workspace too, so each view published before says why nothing is published
|
|
1042
|
+
now instead of keeping the previous run's `current`. A service given a loader
|
|
1043
|
+
that answers a promise and no locator is still refused at start with
|
|
1044
|
+
`service-loader-not-synchronous`, and so is one whose locator answers a
|
|
1045
|
+
promise; a located value that names no folder refuses
|
|
1046
|
+
`service-locator-invalid`. The workspace pointer is under the home, so a home
|
|
1047
|
+
that is gone when the service starts leaves it no workspace to find
|
|
1048
|
+
(`service-workspace-not-prepared`).
|
|
1049
|
+
- **Following the history.** A loaded project is pinned to the record it was
|
|
1050
|
+
loaded under, and the engine observes that record and the place of the next
|
|
1051
|
+
one as configuration. A confirmation is therefore loaded at the next tick. A
|
|
1052
|
+
history cut short is seen at the next tick too, and a running service does
|
|
1053
|
+
not follow it back. Selections are only ever appended, so a history that
|
|
1054
|
+
ends at an earlier record than the service has read it ending at was cut by
|
|
1055
|
+
something other than a confirmation, and following it back could publish
|
|
1056
|
+
what nobody confirmed after the records removed: cut after a restore nobody
|
|
1057
|
+
confirmed, it would publish that restore. The service notes the highest
|
|
1058
|
+
record number at every read of the history's end (a load's first read, its
|
|
1059
|
+
read once the generation is loaded, the recheck at a full reconciliation,
|
|
1060
|
+
and a read made when a tick stops a view as `mixed-read` because the tick's
|
|
1061
|
+
settings changed or could not be read, once for views stopped one after
|
|
1062
|
+
another), whether or not the
|
|
1063
|
+
record's generation could be used, and whichever part of the service read
|
|
1064
|
+
it. A history that ends earlier refuses `selection-history-shortened` at a
|
|
1065
|
+
load and at a recheck, and nothing is loaded from it while it is shorter; at
|
|
1066
|
+
the read made when a tick stops a view, the view says `mixed-read` at that
|
|
1067
|
+
tick and the code at the next. A project already loaded is loaded again at
|
|
1068
|
+
the next tick once any part of the service has read the history ending at a
|
|
1069
|
+
later record than the one the project was loaded under, so a confirmation
|
|
1070
|
+
read elsewhere in the service and removed before the engine looked refuses at
|
|
1071
|
+
that tick; otherwise a project already loaded is held to this at its next
|
|
1072
|
+
recheck, at the latest the next full reconciliation, and until then it
|
|
1073
|
+
publishes nothing new under another record, and a change at its sources
|
|
1074
|
+
brings the recheck forward. A record added and removed between two of those
|
|
1075
|
+
reads was never read, and does not count: while a load of its own is under
|
|
1076
|
+
way, the plugin's status read compares the files that decide its views and
|
|
1077
|
+
reads no history, so a record it sees then only as a changed file is not
|
|
1078
|
+
noted. The views say why (`open` and
|
|
1079
|
+
`status --json` name the ways out), and the vault keeps what it last
|
|
1080
|
+
published.
|
|
1081
|
+
- **What ends that refusal.** The removed records put back; a history as long
|
|
1082
|
+
again, since a record in a place already read, with other content, is read
|
|
1083
|
+
as a confirmation (after a cut of one record the person's next confirmation
|
|
1084
|
+
is followed; after a cut of several, one confirmation is not enough); or a
|
|
1085
|
+
start of the service. The number is kept in the process only, so a service
|
|
1086
|
+
started after the cut cannot tell a cut history from one never longer: it
|
|
1087
|
+
reads the history as it then stands and follows what it ends with,
|
|
1088
|
+
publishing it if its generation still holds. Every start counts, the
|
|
1089
|
+
person's own and one at login, after an upgrade or after a failure. Stopping
|
|
1090
|
+
and starting the service is therefore how a person accepts a shorter
|
|
1091
|
+
history, and a start nobody chose accepts it too. Nothing here keeps the
|
|
1092
|
+
number across a start.
|
|
1093
|
+
- **What the history is checked for besides.** At every full reconciliation the
|
|
1094
|
+
history is read again, so a record before the last that no longer links, or
|
|
1095
|
+
a confirmation that landed before the engine first looked at the record,
|
|
1096
|
+
refuses with its code within the reconciliation interval. A confirmation
|
|
1097
|
+
during a tick changes an observed file: the view being prepared or published
|
|
1098
|
+
refuses `mixed-read` at its next wait, before its generation commits (one
|
|
1099
|
+
already committed stays), so do the views after it, and the next tick
|
|
1100
|
+
publishes under the new record. While the last load was refused, every tick
|
|
1101
|
+
loads again: with nothing confirmed only the history is read, and with a
|
|
1102
|
+
confirmed generation that no longer holds, one composition runs in a worker
|
|
1103
|
+
per tick.
|
|
1104
|
+
- **A narrowing.** When the newly confirmed generation drops a source (a
|
|
1105
|
+
repository withdrawn, a view narrowed), each view follows the publisher's
|
|
1106
|
+
rule for notes that leave a view; this binding adds no rule. At the view's
|
|
1107
|
+
next publication a generated note of the dropped source leaves the vault and
|
|
1108
|
+
its bytes are moved to private recovery. A note the person edited, once the
|
|
1109
|
+
engine has queued the edit, holds the whole view at the generation it last
|
|
1110
|
+
published (`held-for-your-edit`, `publication-withheld-for-your-edit`): the
|
|
1111
|
+
note is neither removed nor overwritten. A note edited during the
|
|
1112
|
+
publication itself stays in the vault (`retained-edit`).
|
|
1113
|
+
- **Who may see.** The binding never decides it. It stays this machine's
|
|
1114
|
+
audience setting, none by default, and `audience set` still refuses for a
|
|
1115
|
+
bound project: until an audience is allowed some other way, a bound vault
|
|
1116
|
+
shows no note. Deciding who may see a bound vault is not built.
|
|
1117
|
+
- **Reading the history on the service's event loop.** The head is one
|
|
1118
|
+
directory listing and one bounded read per record. A record that is not a
|
|
1119
|
+
regular file (a FIFO, a device) refuses `selection-history-corrupt` without
|
|
1120
|
+
being waited on. A home that becomes unreadable while the engine holds a
|
|
1121
|
+
project (its permissions changed, not removed) fails the tick when the
|
|
1122
|
+
engine observes its files, as an unreadable configuration file does: nothing
|
|
1123
|
+
is published, and the service reports the failed tick.
|
|
1124
|
+
|
|
987
1125
|
Measured once on 2,156 notes in three repositories, with 50 annotations and five
|
|
988
1126
|
saved views (six views in all). The production contributions were loaded, as
|
|
989
1127
|
the service loads them, so the proposal adapter was registered. The host's load
|
|
@@ -1011,16 +1149,133 @@ again. While an edit stays open and shared sources are quiet, the adapter
|
|
|
1011
1149
|
builds nothing more; under continuous change it builds once per tick until a
|
|
1012
1150
|
quiet tick.
|
|
1013
1151
|
|
|
1014
|
-
**What
|
|
1015
|
-
|
|
1016
|
-
|
|
1152
|
+
**What a service binding needed.** The first publication blocked the event
|
|
1153
|
+
loop far longer than the service's five-second health probe: about ten minutes
|
|
1154
|
+
here. That is the
|
|
1017
1155
|
publication path every project takes, not this route: an ordinary project of
|
|
1018
1156
|
450 notes, measured the same way, blocked as long as a bound one. Preparing six
|
|
1019
1157
|
views again blocked 4.5 s, under the probe. The proposal adapter's build of a
|
|
1020
1158
|
newly observed edit, without the engine's file cache, blocked 2.0 s, at a
|
|
1021
|
-
two-second target, as for an ordinary project. A service binding
|
|
1022
|
-
first publication to yield to the event loop
|
|
1023
|
-
|
|
1159
|
+
two-second target, as for an ordinary project. A service binding needed the
|
|
1160
|
+
first publication to yield to the event loop, which it now does (see "Since
|
|
1161
|
+
that measurement"), and a start that does not wait for its loader. The service
|
|
1162
|
+
resolves its workspace from the project its loader answers when it starts, and refuses a loader that answers a promise or any
|
|
1163
|
+
other thenable, such as `loadBoundProjectOffThread`, with
|
|
1164
|
+
`service-loader-not-synchronous`, before it reads anything from it, listens or
|
|
1165
|
+
records anything; a promise it was handed is let go, and its refusal, if it
|
|
1166
|
+
is refused, does not go unhandled. Started by a login item (`--startup`), the
|
|
1167
|
+
service asks its loader (or, under `--personal-home`, its locator) once for both
|
|
1168
|
+
the lookup of its workspace and its own start. One that answers a promise there
|
|
1169
|
+
is never handed to the workspace resolution: a promise whose `constructor`
|
|
1170
|
+
reads as this realm's `Promise` is let go without calling a `then` of its
|
|
1171
|
+
own, any other thenable (as a rule another realm's promise or a subclass's)
|
|
1172
|
+
is adopted once (its `then` is called once in all), the refusal is recorded
|
|
1173
|
+
in the workspace the unit names, when that exists, and the process ends with
|
|
1174
|
+
0, so the service manager does not start it again at once. One that throws
|
|
1175
|
+
there, whatever it throws (`undefined` too, a value with no prototype, or one
|
|
1176
|
+
whose `code` cannot be read), or answers a project whose `then` cannot be
|
|
1177
|
+
read, ends the start as a failure of the loader does anywhere else: it is
|
|
1178
|
+
logged, and what carries no code is a crash.
|
|
1179
|
+
A service bound through `--personal-home` is given a locator as well, and starts
|
|
1180
|
+
from it (see "A confirmed selection"). The plugin's status read allows for such
|
|
1181
|
+
a loader without waiting for it (see "The channel" in
|
|
1182
|
+
[obsidian-plugin.md](obsidian-plugin.md)).
|
|
1183
|
+
|
|
1184
|
+
**Since that measurement.** The engine prepares each view cooperatively and
|
|
1185
|
+
the publisher yields between complete units, so the service answers health and
|
|
1186
|
+
status while notes are emitted, staged and published. The first-tick figures
|
|
1187
|
+
in the table are from before that change and have not been taken again on that
|
|
1188
|
+
corpus. Building the canonical graph gives the event loop a turn between the
|
|
1189
|
+
Markdown sources it reads, hashes and parses (the census): a first tick reads
|
|
1190
|
+
every source, and on a heavily loaded host the build of one view of 10,000
|
|
1191
|
+
invented notes held the listener for 8 s in one stretch. The other sources
|
|
1192
|
+
(HTML files and the sidecars of PDF and Word documents) are read without a turn
|
|
1193
|
+
between them, and a project bound to a personal workspace is built by its own
|
|
1194
|
+
route in one stretch. Walking the directories, resolving the links and
|
|
1195
|
+
assembling the graph still run through.
|
|
1196
|
+
So do capturing the source snapshot, the publisher's restart recovery,
|
|
1197
|
+
validating a prepared view and planning its units, the exchange probe, writing
|
|
1198
|
+
and syncing the journal before the candidates move, and the look again at
|
|
1199
|
+
displaced files after the units (`recheckDisplacedFiles`).
|
|
1200
|
+
The read-back after a commit waits between
|
|
1201
|
+
notes: on a loaded host it took 16.8 s for one view of 10,000 invented notes
|
|
1202
|
+
and held the listener for all of it, and now a request is answered between two
|
|
1203
|
+
notes. Nothing is checked at those waits, since the generation is committed. A
|
|
1204
|
+
note edited in the vault while the read-back is under way is read as edited
|
|
1205
|
+
when the walk has not reached it, and is seen by the next tick when it has, as
|
|
1206
|
+
an edit made just after a read-back is. A stop whose grace runs out while a
|
|
1207
|
+
read-back waits ends the process after the commit and before the view is
|
|
1208
|
+
recorded as current, as a crash there does: the next start's first tick reads
|
|
1209
|
+
the committed generation back. Before a view is prepared, a tick looks at
|
|
1210
|
+
every view's notes and then at the sources, and both walks wait between files.
|
|
1211
|
+
They matter after a publication, when the watcher has named the notes it
|
|
1212
|
+
wrote, and at every full reconciliation, when every note and every source is
|
|
1213
|
+
hashed: on one view of 10,000 invented notes on a loaded host such a tick held
|
|
1214
|
+
the listener for 5.5 s when the walks ran through. Nothing is checked at their
|
|
1215
|
+
waits and nothing is written. Within Atelier only the engine commits a
|
|
1216
|
+
manifest or writes the pending edits, under the lock the tick holds, so the
|
|
1217
|
+
pending edits are read, compared and written in one stretch after the walk
|
|
1218
|
+
over the vault, and the bytes an edit keeps are read there. A file changed
|
|
1219
|
+
after its turn in a walk is seen by the next tick, by its stat hint or the
|
|
1220
|
+
watcher (one that keeps its size and times and draws no event, at the latest
|
|
1221
|
+
by the next full reconciliation), as one changed just after a walk that ran
|
|
1222
|
+
through is; when the tick builds the graph, the look again at the sources
|
|
1223
|
+
after the build refuses such a change as `mixed-read`. A source this tick
|
|
1224
|
+
observed, removed before the build read it (or its folder), is refused the
|
|
1225
|
+
same way. So is a selected source removed before a view's preparation, or the
|
|
1226
|
+
look again before its commit, read it: that view is refused, and the tick goes
|
|
1227
|
+
on to its other views. Either way the next tick reads everything again; a path
|
|
1228
|
+
that can never be read still fails the tick. A note gone from a
|
|
1229
|
+
vault, a source that changed, or a configuration file that changed is a change
|
|
1230
|
+
once: a tick that sees it and then fails without a typed reason (a file it
|
|
1231
|
+
cannot read, a disk that is full, a project loader that fails) has recorded it
|
|
1232
|
+
without acting on it. The next tick therefore starts as the first tick of a
|
|
1233
|
+
process does: it loads the project again and looks at every view, so a view
|
|
1234
|
+
whose note is missing reads `vault-note-missing` instead of staying `current`,
|
|
1235
|
+
and a changed source or view is published.
|
|
1236
|
+
The comparison of kept notes with their candidates reads asynchronously,
|
|
1237
|
+
sixteen at a time, and does not hold the listener. Observing pending edits
|
|
1238
|
+
and routing proposals wait between two edits and between two operations, so
|
|
1239
|
+
a burst of edits does not hold the listener for its whole length. One edit,
|
|
1240
|
+
or one operation, is still one stretch. The first edit an observation looks
|
|
1241
|
+
at that the object store does not hold yet builds the canonical graph in its
|
|
1242
|
+
stretch, and the first operation routed builds it again while the lock of
|
|
1243
|
+
its repository is held. An edit whose published note
|
|
1244
|
+
is not retained has its whole view prepared again in its stretch: once for a
|
|
1245
|
+
view and generation in an observation when the preparation succeeds, and for
|
|
1246
|
+
every such edit when it fails. A stop whose grace runs out while proposals
|
|
1247
|
+
are routed can end the process between two operations of a repository with
|
|
1248
|
+
that repository's proposal lock still held, as a crash can. A service in one
|
|
1249
|
+
of those stretches does not answer until it ends, and is `busy` if that
|
|
1250
|
+
outlasts the probe. A request can
|
|
1251
|
+
hold the listener too: the plugin's status read loads the project on the event
|
|
1252
|
+
loop at its first requests after the service starts (until two loads agree),
|
|
1253
|
+
when a configuration file changed, every two and a half minutes, again two to
|
|
1254
|
+
thirty seconds after a load that failed, and at every request while one of
|
|
1255
|
+
those files vouches for nothing or while its loads never agree. For a project
|
|
1256
|
+
named by `--project` none of those loads is taken off the loop. For a bound
|
|
1257
|
+
personal home they compose in a worker, as the engine's loads do, and the read
|
|
1258
|
+
starts each one on the loop by reading the selection history. A load that holds the loop
|
|
1259
|
+
for a second or more is logged (`status-project-loaded`), and so is a load at
|
|
1260
|
+
every request that names the files it started over (see "What the service
|
|
1261
|
+
shows of these loads" in [obsidian-plugin.md](obsidian-plugin.md)).
|
|
1262
|
+
|
|
1263
|
+
What the waits cost. The waits of observation and routing are taken at
|
|
1264
|
+
every boundary between two edits or two operations and before each
|
|
1265
|
+
repository, whatever time was spent; while routing describes its operations
|
|
1266
|
+
a wait comes after about 8 ms of work or 32 units. Nothing is checked around
|
|
1267
|
+
any of them. In preparation and publication a wait
|
|
1268
|
+
comes after about 8 ms of work or 32 units,
|
|
1269
|
+
whichever is first, and around each one the engine checks that the tick's
|
|
1270
|
+
settings still hold: it reads and hashes every configuration file it observes
|
|
1271
|
+
(the project configuration, the repository access file, local overlays and,
|
|
1272
|
+
for a bound personal workspace, its inputs), reads and validates the machine
|
|
1273
|
+
settings, and asks the eligibility revision. Measured on six configuration
|
|
1274
|
+
files, that check took about 0.2 ms, twice per wait; a first publication of
|
|
1275
|
+
200 invented notes waited 463 times, so roughly 0.2 s of a 64 s tick, on a
|
|
1276
|
+
host with a load average above 200 (indicative only). The selected sources
|
|
1277
|
+
cost one stat call each after the preparation and one before the commit; a
|
|
1278
|
+
note reused from the preparation cache is still not read.
|
|
1024
1279
|
|
|
1025
1280
|
## Known limits
|
|
1026
1281
|
|
|
@@ -1227,3 +1482,8 @@ The test suite proves each oracle can fail:
|
|
|
1227
1482
|
- `createMaintenanceEngineForOracleTests` with an
|
|
1228
1483
|
`ENGINE_PRIMITIVES.isRetryDue` that never holds: a refused view waits for
|
|
1229
1484
|
the full reconciliation, and the retry oracle fails.
|
|
1485
|
+
- `createMaintenanceEngineForOracleTests` with
|
|
1486
|
+
`ENGINE_PRIMITIVES.rechecksEveryViewAfterFailure` false: a note removed, a
|
|
1487
|
+
source changed or a view added in a tick that then fails without a typed
|
|
1488
|
+
reason is lost, the view stays `current` with the note gone or the old
|
|
1489
|
+
content, the new view is never published, and the oracle fails.
|
|
@@ -29,7 +29,7 @@ stable local enrollment keys; matching a recorded remote is an offline change
|
|
|
29
29
|
check, not proof of upstream membership or provider identity.
|
|
30
30
|
|
|
31
31
|
The module entrypoint is `src/personal-workspace/index.mjs`; its public package
|
|
32
|
-
subpath is `@mnstry/atelier/personal-workspace`. The API consists of
|
|
32
|
+
subpath is `@mnstry/atelier/personal-workspace`. The API consists of sixteen exports:
|
|
33
33
|
|
|
34
34
|
- `MANIFEST_SCHEMA` and `OVERLAY_SCHEMA`.
|
|
35
35
|
- `PersonalWorkspaceRefusal`.
|
|
@@ -37,8 +37,8 @@ subpath is `@mnstry/atelier/personal-workspace`. The API consists of fifteen exp
|
|
|
37
37
|
- `resolvePersonalWorkspace`.
|
|
38
38
|
- `planPersonalGeneration`, `materializePersonalGeneration`, and
|
|
39
39
|
`composePersonalWorkspace`.
|
|
40
|
-
- `selectPersonalGeneration`, `selectionConfirmDigest`,
|
|
41
|
-
`readPersonalSelection`.
|
|
40
|
+
- `selectPersonalGeneration`, `selectionConfirmDigest`,
|
|
41
|
+
`readPersonalSelection`, and `readPersonalSelectionHead`.
|
|
42
42
|
- `inventoryPersonalHome`.
|
|
43
43
|
- `planPersonalRestore` and `restorePersonalInputs`.
|
|
44
44
|
|
|
@@ -127,7 +127,16 @@ remove only their own temporary files. Every failure is a
|
|
|
127
127
|
reordering refuses `selection-history-corrupt`. Truncating, or consistently
|
|
128
128
|
rewriting, any trailing run of records (up to the whole history) yields another
|
|
129
129
|
valid chain; it changes the returned `head`, so a host that needs to detect it
|
|
130
|
-
keeps the head it last observed and compares it.
|
|
130
|
+
keeps the head it last observed and compares it. The Obsidian maintenance
|
|
131
|
+
service keeps the highest record number it has read a history ending at, for
|
|
132
|
+
as long as it runs and no longer, and refuses a history that ends earlier
|
|
133
|
+
(see [obsidian.md](obsidian.md)).
|
|
134
|
+
- `readPersonalSelectionHead({ personalHome })` returns `{ selected, sequence,
|
|
135
|
+
head }` from the history alone, with `sequence` 0 while nothing is selected.
|
|
136
|
+
It composes nothing, so it does not say whether the selection is still
|
|
137
|
+
eligible; the chain is checked as above. It is for a host that composes
|
|
138
|
+
elsewhere, and that reads the head again to compare it with the one it acted
|
|
139
|
+
on.
|
|
131
140
|
- `inventoryPersonalHome({ personalHome })` lists what these operations write, for
|
|
132
141
|
review before any deletion the person chooses: the authored files, each
|
|
133
142
|
generation with its eligibility, interrupted staging directories, every
|
|
@@ -257,7 +257,8 @@ offline-only hosts keep the behaviour above:
|
|
|
257
257
|
online install.
|
|
258
258
|
|
|
259
259
|
Failures print as `[code] message` with a `Next:` step. The pure classifiers
|
|
260
|
-
are in `
|
|
260
|
+
are in `src/upgrade/closure-diagnostics.mjs`, and
|
|
261
|
+
`scripts/consumer-closure-diagnostics.mjs` re-exports them for this script:
|
|
261
262
|
|
|
262
263
|
- `consumer-closure-incomplete`: an offline install or reinstall hit
|
|
263
264
|
`ENOTCACHED`, or the captured lockfile has a registry entry missing
|