@mnstry/atelier 0.2.0-alpha.10 → 0.2.0-alpha.12
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 +385 -0
- package/README.md +10 -6
- package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
- package/contracts/compat-baseline.json +1 -1
- package/docs/assurance-controls.md +3 -2
- package/docs/install.md +3 -3
- package/docs/local-services.md +62 -11
- package/docs/obsidian-contract.md +681 -39
- package/docs/obsidian-plugin.md +585 -0
- package/docs/obsidian.md +292 -48
- package/docs/release-engineering.md +7 -1
- package/docs/upgrade.md +32 -3
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
- package/fixtures/obsidian/edits/workspace.json +1 -1
- package/fixtures/obsidian/materialization/expected-full.json +21 -21
- package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
- package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
- package/package.json +7 -3
- package/plugins/obsidian/main.js +444 -0
- package/plugins/obsidian/manifest.json +9 -0
- package/plugins/obsidian/styles.css +22 -0
- package/src/commands/obsidian.mjs +89 -10
- package/src/contracts/corpus.mjs +6 -0
- package/src/egress/forbidden-egress.mjs +2 -0
- package/src/project/private-state.mjs +10 -1
- package/src/projection/obsidian/contracts.mjs +224 -18
- package/src/projection/obsidian/edits/apply.mjs +12 -7
- package/src/projection/obsidian/edits/regions.mjs +137 -42
- package/src/projection/obsidian/materialize/index.mjs +6 -3
- package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
- package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
- package/src/projection/obsidian/materialize/redaction.mjs +327 -0
- package/src/projection/obsidian/materialize/settings.mjs +113 -34
- package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
- package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
- package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
- package/src/projection/obsidian/proposals/observation.mjs +10 -5
- package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
- package/src/projection/obsidian/publication/index.mjs +2 -1
- package/src/projection/obsidian/publication/publisher.mjs +173 -26
- package/src/projection/obsidian/publication/transport.mjs +124 -17
- package/src/projection/obsidian/publication/vault-list.mjs +199 -0
- package/src/projection/obsidian/recovery/restart.mjs +3 -1
- package/src/projection/obsidian/recovery/store.mjs +11 -4
- package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
- package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
- package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
- package/src/runtime/obsidian/app-capability.mjs +115 -14
- package/src/runtime/obsidian/app-production-seams.mjs +126 -37
- package/src/runtime/obsidian/app-registration.mjs +166 -0
- package/src/runtime/obsidian/engine.mjs +127 -11
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +5 -1
- package/src/runtime/obsidian/launch-plan.mjs +88 -0
- package/src/runtime/obsidian/lifecycle.mjs +105 -14
- package/src/runtime/obsidian/opening.mjs +240 -51
- package/src/runtime/obsidian/pending-edits.mjs +10 -0
- package/src/runtime/obsidian/plugin-channel.mjs +281 -0
- package/src/runtime/obsidian/plugin-choice.mjs +170 -0
- package/src/runtime/obsidian/plugin-drift.mjs +76 -0
- package/src/runtime/obsidian/plugin-presence.mjs +48 -0
- package/src/runtime/obsidian/service-main.mjs +19 -4
- package/src/runtime/obsidian/service-record.mjs +36 -0
- package/src/runtime/obsidian/service-server.mjs +50 -12
- package/src/runtime/obsidian/service.mjs +100 -6
- package/src/runtime/obsidian/state-store.mjs +13 -1
- package/src/server/local-sidecar.mjs +24 -5
- package/src/server/server.mjs +18 -5
- package/src/upgrade/upgrade.mjs +1 -1
- package/templates/external-project-workspace/pack-lifecycle.json +1 -1
package/docs/obsidian.md
CHANGED
|
@@ -72,13 +72,18 @@ A focus keeps the full vault on disk and filters the native Graph view. The
|
|
|
72
72
|
query is
|
|
73
73
|
|
|
74
74
|
```
|
|
75
|
-
path
|
|
75
|
+
path:/^<note path>$/ OR path:/^<note path>$/ ...
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
in the order of the selected set (canonical-id order).
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
in the order of the selected set (canonical-id order). Each term is a regular
|
|
79
|
+
expression anchored at both ends, so it matches exactly one note: a plain
|
|
80
|
+
`path:` term matches any path that contains it, and with titles as names one
|
|
81
|
+
note's path can lie inside another's. In the term every regular-expression
|
|
82
|
+
character and the `/` delimiter is escaped with a backslash, and a space and
|
|
83
|
+
a double quote are written as `\x20` and `\x22`, so a path that itself
|
|
84
|
+
contains search operators (`tag:`, `-`, `OR`, parentheses) stays text. Paths
|
|
85
|
+
are NFC-normalized first. A persisted focus of the earlier query version
|
|
86
|
+
(`obsidian-graph-search-paths/v1`, quoted substring terms) is still read. A
|
|
82
87
|
path that carries a control character, a C1 control, or the U+2028 or U+2029
|
|
83
88
|
line separator cannot be a search term and refuses with
|
|
84
89
|
`focus-path-unrepresentable`; so does an absolute path, a backslash path or a
|
|
@@ -256,7 +261,7 @@ nothing under `scripts/obsidian/` ships.
|
|
|
256
261
|
| --- | --- |
|
|
257
262
|
| `@mnstry/atelier/obsidian` | `src/runtime/obsidian/index.mjs`: enablement, machine settings, engine, service lifecycle, production seams, app qualification |
|
|
258
263
|
| `@mnstry/atelier/obsidian/contracts` | `src/projection/obsidian/contracts.mjs`: contract validation, `selectScope`, note paths |
|
|
259
|
-
| `@mnstry/atelier/obsidian/materialize` | `prepareView`, settings, path registry |
|
|
264
|
+
| `@mnstry/atelier/obsidian/materialize` | `prepareView`, settings (including the plugin's files and entry), path registry |
|
|
260
265
|
| `@mnstry/atelier/obsidian/publication` | `publishView`, editor adapters, the exchange |
|
|
261
266
|
| `@mnstry/atelier/obsidian/recovery` | recovery store, journals, late-writer recheck |
|
|
262
267
|
| `@mnstry/atelier/obsidian/edits` | edit observation, arbitration, apply policy, source apply |
|
|
@@ -272,9 +277,10 @@ What the tarball must and must not carry, and the package proof, are in
|
|
|
272
277
|
|
|
273
278
|
## First vault: what to expect
|
|
274
279
|
|
|
275
|
-
This is the path an adopter walks on one machine
|
|
276
|
-
|
|
277
|
-
|
|
280
|
+
This is the path an adopter walks on one machine. Every operation and flag
|
|
281
|
+
below is the shipped `atelier obsidian` usage text; `atelier obsidian --help`
|
|
282
|
+
prints it. There are three steps, and none of them is done by hand in
|
|
283
|
+
Obsidian.
|
|
278
284
|
|
|
279
285
|
1. Enable the projection in the project configuration. The member
|
|
280
286
|
`ext["mnstry.atelier.obsidian"]` is an `atelier-obsidian-ext-settings/v1`
|
|
@@ -295,15 +301,54 @@ usage text; `atelier obsidian --help` prints it.
|
|
|
295
301
|
has to carry; `policy install FILE` installs it and leaves the mode
|
|
296
302
|
unchanged; `policy revoke` returns the mode to `manual` and stops every
|
|
297
303
|
later apply.
|
|
298
|
-
3.
|
|
299
|
-
`atelier obsidian
|
|
300
|
-
Reaching the installed app is never a default, so
|
|
301
|
-
is required, and the
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
304
|
+
3. Open a view:
|
|
305
|
+
`atelier obsidian open --scope ID --consent-actor ID --adapter=obsidian-cli`.
|
|
306
|
+
That's it. Reaching the installed app is never a default, so
|
|
307
|
+
`--adapter=obsidian-cli` is required, and the first open of a workspace
|
|
308
|
+
records who allowed the maintenance service to run (`--consent-actor`;
|
|
309
|
+
later opens reconnect to it and need no consent). `open` starts or
|
|
310
|
+
reconnects maintenance and asks it for a tick, makes Obsidian (version
|
|
311
|
+
1.13.7 or later) know the view's vault as one of its vaults, opens it,
|
|
312
|
+
publishes the view, verifies the vault by reading it back, and waits until
|
|
313
|
+
the app answers for exactly that vault. It does this in whatever state
|
|
314
|
+
Obsidian is in; see
|
|
315
|
+
[What `open` does in each state of Obsidian](#what-open-does-in-each-state-of-obsidian).
|
|
316
|
+
|
|
317
|
+
The maintenance service can also be managed on its own:
|
|
318
|
+
`atelier obsidian service start --consent-actor ID --adapter=obsidian-cli`,
|
|
319
|
+
`service status` and `service stop`; `service unit --print
|
|
320
|
+
--adapter=obsidian-cli` prints a startup unit and installs nothing.
|
|
321
|
+
|
|
322
|
+
After an upgrade of Atelier, a maintenance service started earlier still runs
|
|
323
|
+
the earlier release. `open` replaces it: when the service of this workspace
|
|
324
|
+
proves itself Atelier's own but runs an earlier release than the installed one
|
|
325
|
+
(its record names the release it runs: the package version and a digest of
|
|
326
|
+
its modules; an earlier version, the same version with other modules, or no
|
|
327
|
+
release named), or refuses a tick that names a view as releases up to
|
|
328
|
+
0.2.0-alpha.11 do,
|
|
329
|
+
`open` stops it as `service stop` would and starts the installed release under
|
|
330
|
+
the consent already recorded, and says `service: restarted (outdated)`. A
|
|
331
|
+
service of a later release is never replaced by an earlier one, so two
|
|
332
|
+
installations used on one workspace (a global and a project-local one, say)
|
|
333
|
+
do not replace each other's service on every open: `open` answers
|
|
334
|
+
`service-unavailable` / `service-other-release`; run `atelier obsidian service
|
|
335
|
+
stop`, then open again, or open with the later release. A service in a long
|
|
336
|
+
tick is not stopped (`open` answers `busy`), and a listener that does not
|
|
337
|
+
prove itself this workspace's service is never touched.
|
|
338
|
+
|
|
339
|
+
The first time Obsidian opens a view's vault it asks "Do you trust the author
|
|
340
|
+
of this vault?", because every vault Atelier publishes carries Atelier's own
|
|
341
|
+
plugin. "Trust author and enable plugins" turns it on: a status bar item then
|
|
342
|
+
says whether the view is current, updating, held for an edit you made, stale,
|
|
343
|
+
or whether the maintenance service does not answer, and "Atelier: show status"
|
|
344
|
+
says why. "Browse vault in Restricted Mode" keeps every community plugin off
|
|
345
|
+
in that vault, and nothing else changes: maintenance, `open` and `status` work
|
|
346
|
+
without the plugin, as they always did. Obsidian keeps the answer per vault in
|
|
347
|
+
its own storage and asks again the next time until the vault is trusted or
|
|
348
|
+
restricted mode is switched on for good in its settings. Turning the plugin
|
|
349
|
+
off in a vault, or uninstalling it there, is respected from then on;
|
|
350
|
+
`atelier obsidian plugin on --scope ID` brings it back. What the plugin does
|
|
351
|
+
and never does is in [obsidian-plugin.md](obsidian-plugin.md).
|
|
307
352
|
|
|
308
353
|
`open` and `status` answer with a freshness state, not a promise. `current`
|
|
309
354
|
means the vault is the present generation, verified by read-back, and the app
|
|
@@ -315,7 +360,9 @@ and the view is not republished over it. The remaining outcomes (`indexing`,
|
|
|
315
360
|
`publisher-conflict`, `app-missing`, `app-version-unsupported`,
|
|
316
361
|
`app-cli-unavailable`, `launch-failed`, `service-unavailable`, `busy`,
|
|
317
362
|
`disabled`) each name what to do next, and exit code 3 says the answer is not
|
|
318
|
-
success.
|
|
363
|
+
success. Both also report Atelier's plugin for each view, under `plugin`:
|
|
364
|
+
present while it holds the vault open in the app, with the app and plugin
|
|
365
|
+
versions, or not present with the reason.
|
|
319
366
|
|
|
320
367
|
Edits made in the vault are preserved before anything is republished. A body
|
|
321
368
|
replacement is held as a pending edit; under manual mode it reaches its source
|
|
@@ -338,17 +385,120 @@ absolute directory. Private state inside an enrolled repository is refused.
|
|
|
338
385
|
Publication is proven on macOS arm64 only and is refused on Windows; see
|
|
339
386
|
[Known limits](#known-limits).
|
|
340
387
|
|
|
341
|
-
###
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
vault
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
388
|
+
### What `open` does in each state of Obsidian
|
|
389
|
+
|
|
390
|
+
Obsidian opens a vault from a link only when the folder is in its own vault
|
|
391
|
+
list, the `vaults` of `obsidian.json` in its user-data directory. `open` puts
|
|
392
|
+
the view's vault there first, and never asks a person to open a folder by
|
|
393
|
+
hand. It then names the vault by its id (`obsidian://open?vault=<id>`), never
|
|
394
|
+
by path where the id reaches it first: a path is matched against the list by
|
|
395
|
+
string prefix, so a link for a note under `/A/strategy-lab` can open the
|
|
396
|
+
listed vault `/A/strategy`. When another vault's folder is named like the id,
|
|
397
|
+
or the id is unusual, the vault's own path is used, which the app matches
|
|
398
|
+
exactly because the folder is listed.
|
|
399
|
+
|
|
400
|
+
- **Obsidian runs and answers its command line (any vault is open).** `open`
|
|
401
|
+
reads the app's vault list through its command line. A vault the app does
|
|
402
|
+
not list is added and opened in a new window through the app itself (it
|
|
403
|
+
writes its own settings), and read back from its list. `open`
|
|
404
|
+
then waits, bounded, until the app answers for exactly this vault, and asks
|
|
405
|
+
maintenance for the view again: the publication runs through the app, which
|
|
406
|
+
now holds the vault, with every editor check of the publication protocol.
|
|
407
|
+
Obsidian does not have to be quit.
|
|
408
|
+
- **Obsidian is not running.** The view is published on the path with no app,
|
|
409
|
+
as before. `open` then adds the vault to the app's settings file (see
|
|
410
|
+
[Obsidian's vault list](obsidian-contract.md#obsidians-vault-list) for
|
|
411
|
+
exactly when and how that one file is written, marked to reopen) and starts
|
|
412
|
+
Obsidian plainly, with no link: an Obsidian started with a link opens only
|
|
413
|
+
that vault and drops the reopen flag of every other one, so your other
|
|
414
|
+
vaults would not come back the next time you start it. Started plainly, it
|
|
415
|
+
reopens every vault it had open, this one included, and once its command
|
|
416
|
+
line answers (the app itself, not the tool's "unable to find Obsidian")
|
|
417
|
+
`open` hands it the vault's link through that tool, never through the
|
|
418
|
+
operating system. On Linux a plain start is not
|
|
419
|
+
qualified yet, and Obsidian is started with the link (the other vaults'
|
|
420
|
+
reopen marks are lost there; a known limit).
|
|
421
|
+
An Obsidian that never ran on this account has no settings file yet, and
|
|
422
|
+
`open` does not create one: start Obsidian once, then open again.
|
|
423
|
+
- **Obsidian runs with no vault open.** Its command line then answers every
|
|
424
|
+
command with "Vault not found.", so nothing can be asked of it, and its
|
|
425
|
+
settings file belongs to the running app, so it is only read. A vault the
|
|
426
|
+
app already lists is opened by path and published through the app as
|
|
427
|
+
above. A vault it does not list yet is answered as outcome
|
|
428
|
+
`app-version-unsupported` with reason `no-vault-open`: open any vault in
|
|
429
|
+
Obsidian, or quit it, and open again. This is the one state of a running
|
|
430
|
+
Obsidian that `open` cannot get through alone.
|
|
431
|
+
- **Obsidian's command line is turned off** (the default of a new
|
|
432
|
+
installation). It then answers every command with "Command line interface
|
|
433
|
+
is not enabled", and only a link reaches it. With Obsidian running, `open`
|
|
434
|
+
adds and launches nothing. With Obsidian quit, `open` adds the vault to its
|
|
435
|
+
settings and starts it, and Obsidian opens the vault. Either way `open` then
|
|
436
|
+
answers `app-cli-unavailable` with reason `cli-turned-off` and says where to
|
|
437
|
+
turn it on: Settings > General > Advanced > Command line interface.
|
|
438
|
+
Publishing into a vault Obsidian holds needs it until Atelier's plugin takes
|
|
439
|
+
over that part.
|
|
440
|
+
|
|
441
|
+
Only `open` adds a vault to Obsidian: the maintenance service never does, so
|
|
442
|
+
it never opens a window nobody asked for. A declared view that was never
|
|
443
|
+
opened is published while Obsidian is quit, and `open --scope ID` adds it.
|
|
444
|
+
|
|
445
|
+
In no state does `open` add a view's vault inside a folder Obsidian already
|
|
446
|
+
lists as a vault (your home folder, say): that vault would show the view's
|
|
447
|
+
notes too, and a command-line call run in the view's folder would reach it.
|
|
448
|
+
`open` answers `launch-failed` with reason `vault-inside-another-vault`:
|
|
449
|
+
remove that vault from Obsidian's vault list, or keep Atelier's data root
|
|
450
|
+
outside that folder. A view's vault that Obsidian lists already, below such a
|
|
451
|
+
vault, is reached by its id instead (see "Which window answers" in
|
|
452
|
+
[Known limits](#known-limits)).
|
|
453
|
+
|
|
454
|
+
Obsidian can list one folder more than once, under another letter case or
|
|
455
|
+
through a link, and then open it in one window per entry. Each of those
|
|
456
|
+
windows holds the view's vault, and a publication coordinates with one window
|
|
457
|
+
only, so none is made: the view reports `publisher-conflict` with reason
|
|
458
|
+
`vault-open-in-several-windows`, and `open` answers the same, names the
|
|
459
|
+
entries (`open in Obsidian as: …`; `duplicates` in JSON) and launches nothing.
|
|
460
|
+
Remove the extra entries from Obsidian's vault list and open again. Closing
|
|
461
|
+
the extra windows is not enough: Obsidian keeps the last window it closed
|
|
462
|
+
marked open, so two entries can stay marked open with one window showing, and
|
|
463
|
+
the view stays refused until the list names the folder once.
|
|
464
|
+
While one entry of the folder has a window, `open` reaches only that one and
|
|
465
|
+
never opens another entry of the same folder beside it.
|
|
466
|
+
|
|
467
|
+
The publisher still writes into a vault only when it can coordinate with every
|
|
468
|
+
Obsidian that may hold it, or when the process table shows, positively, that
|
|
469
|
+
none runs. Otherwise it stops, and the view reports `publisher-conflict` with
|
|
470
|
+
reason `editor-uncoordinated`. That reason has several causes, and the report
|
|
471
|
+
does not say which: Obsidian runs without this vault open, or with it open but
|
|
472
|
+
its command line did not answer; the process table could not be read, or on
|
|
473
|
+
Linux shows an app that runs on a system Electron, which may be Obsidian; the
|
|
474
|
+
app's version was not checked when the publication began; or an app started
|
|
475
|
+
while the view was being published with the app closed. `status` gives
|
|
476
|
+
`atelier obsidian open` as the next step (it adds the vault to Obsidian and
|
|
477
|
+
publishes through it), or quitting Obsidian (on Linux, also any app that runs
|
|
478
|
+
on a system Electron). When `open` itself already did that and the
|
|
479
|
+
publication still stopped, it says to quit Obsidian and open again.
|
|
480
|
+
|
|
481
|
+
The tick `open` asks for prepares and publishes its view once more, whatever
|
|
482
|
+
state the view is in. A view whose last publication did not settle (refused,
|
|
483
|
+
stale or still updating) is also tried again without waiting for a change: as
|
|
484
|
+
soon as the app looks different (it quit or started, or opened or closed a
|
|
485
|
+
vault in its list), and otherwise after a delay that starts at 30 seconds and
|
|
486
|
+
doubles per attempt, up to the full reconciliation every five minutes. What
|
|
487
|
+
the app looks like is read from the process table and the app's vault list
|
|
488
|
+
alone: maintenance runs nothing in Obsidian to find out, and asks it for its
|
|
489
|
+
version only when a view is about to be published, without blocking.
|
|
490
|
+
|
|
491
|
+
While Atelier's plugin holds the view's vault open in the app and the command
|
|
492
|
+
line gives no version (as it answers while a vault is still loading), the
|
|
493
|
+
version the plugin reports stands in (reason `plugin-reported`); a version the
|
|
494
|
+
command line gives always decides, and while the process table shows no app
|
|
495
|
+
running, a plugin's report counts for nothing. `open` then asks the command
|
|
496
|
+
line nothing: a vault the app's settings file lists is opened by path, and one
|
|
497
|
+
it does not show is answered as `app-cli-unavailable` with reason
|
|
498
|
+
`vault-open-cli-silent` (the app has the vault open; make sure its
|
|
499
|
+
command-line interface is turned on). So is a listed vault the command line
|
|
500
|
+
does not answer for once it is opened, while only the plugin reports the
|
|
501
|
+
version: `open` says so at once instead of waiting for the app.
|
|
352
502
|
|
|
353
503
|
## Known limits
|
|
354
504
|
|
|
@@ -375,7 +525,22 @@ test reported as a pass.
|
|
|
375
525
|
is admitted although the protocol depends on the view's undocumented
|
|
376
526
|
`lastSavedData` field, and the protocol cases have not been re-run on any
|
|
377
527
|
later release. See the "App capability floor" row of
|
|
378
|
-
[obsidian-contract.md](obsidian-contract.md).
|
|
528
|
+
[obsidian-contract.md](obsidian-contract.md). An app that was not running
|
|
529
|
+
when an adapter was qualified had no version to check, and neither had an
|
|
530
|
+
app that was not found. Such an answer is never reused, and the adapter
|
|
531
|
+
built on it does not coordinate with any app it then finds running, and
|
|
532
|
+
asks it nothing. That publication stops as `publisher-conflict` with reason
|
|
533
|
+
`editor-uncoordinated`, and the next publication asks again and reads the
|
|
534
|
+
version. A view that stopped is tried again on the next tick `open` asks
|
|
535
|
+
for, as soon as the app looks different, and otherwise after a delay that
|
|
536
|
+
starts at 30 seconds and doubles, up to the full reconciliation every five
|
|
537
|
+
minutes. A tick `open` asks for never reuses a remembered app answer.
|
|
538
|
+
- Install location (macOS): the app is found only at
|
|
539
|
+
`/Applications/Obsidian.app`, and its command-line tool only inside it. An
|
|
540
|
+
Obsidian installed elsewhere, or a renamed bundle, is `app-missing`.
|
|
541
|
+
Nothing is published through it, publication waits while it runs (the
|
|
542
|
+
process probe still sees it), and `open` answers `app-missing` and cannot
|
|
543
|
+
start it. With it quit, the view is published on the path with no app.
|
|
379
544
|
- No vault open: while Obsidian runs with no vault open, its command-line tool
|
|
380
545
|
answers every command, `version` included, with `Vault not found.`. The
|
|
381
546
|
version floor cannot be checked then, so the app does not qualify:
|
|
@@ -383,23 +548,87 @@ test reported as a pass.
|
|
|
383
548
|
published through the app. `status` (under `service.app`) and `open` say what
|
|
384
549
|
to do: open any vault in Obsidian, or quit Obsidian. With the app quit,
|
|
385
550
|
maintenance publishes on its own path again; with a vault open, it reads
|
|
386
|
-
the version again on a later tick.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
app
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
551
|
+
the version again on a later tick. While the app runs with no vault open,
|
|
552
|
+
`open` has it open a vault only when its list already knows that vault,
|
|
553
|
+
by path; one it does not know cannot be added then (see below). After its
|
|
554
|
+
own launch `open` waits, bounded, while the app is still opening the vault;
|
|
555
|
+
an app whose vault window is still loading answers a command with `Error:
|
|
556
|
+
Command "…" not found`, which is read as not up yet, never as a version.
|
|
557
|
+
- Obsidian's settings file: `open` adds a view's vault to the app's own list,
|
|
558
|
+
`obsidian.json` in its user-data directory: `~/Library/Application
|
|
559
|
+
Support/obsidian` on macOS and `$XDG_CONFIG_HOME/obsidian` (else
|
|
560
|
+
`~/.config/obsidian`) on Linux. It is written only while the process table
|
|
561
|
+
shows, positively, that no Obsidian runs, only when Obsidian created it
|
|
562
|
+
before, and with a backup beside it; see
|
|
563
|
+
[Obsidian's vault list](obsidian-contract.md#obsidians-vault-list). A
|
|
564
|
+
Flatpak or snap build keeps its list inside its sandbox and never reads that
|
|
565
|
+
file. Which build is in use is read from which vault list was written last
|
|
566
|
+
(every build rewrites its own when a vault window opens or closes), and only
|
|
567
|
+
when no build wrote one from its installation; for a Flatpak or snap build
|
|
568
|
+
the file is then neither read nor written: `open` answers
|
|
569
|
+
`obsidian-sandboxed` and says to start Obsidian with any vault open, then
|
|
570
|
+
open again, and adds the vault through the app. On Windows no location is
|
|
571
|
+
known, and `open` adds the vault through the app in the same way.
|
|
572
|
+
- Recent items: adding a vault through the running app is Obsidian's own
|
|
573
|
+
"open folder as vault", which also puts the vault's folder in the operating
|
|
574
|
+
system's recently used documents (Recent Items on macOS).
|
|
575
|
+
- Which window answers: Obsidian answers a command-line call that names a
|
|
576
|
+
vault (`vault=<id>` first) in that vault's window; any other call in the
|
|
577
|
+
window of the first vault in its list whose folder is the tool's working
|
|
578
|
+
directory or contains it, and otherwise in the vault window that had focus
|
|
579
|
+
last. A vault that takes a call is opened when it is closed. Calls about a
|
|
580
|
+
vault therefore name its id; only when that id would name another vault
|
|
581
|
+
first (one whose folder has the id as its name) do they run inside its
|
|
582
|
+
folder instead, and not at all when a vault listed before it at a folder
|
|
583
|
+
above it (your home folder, say) would take them there. The vault root is
|
|
584
|
+
taken in the spelling the file system stores, so a data root given in
|
|
585
|
+
another letter case on macOS changes nothing. Publication calls do this
|
|
586
|
+
only while the app's list shows the view's vault open, and run in a
|
|
587
|
+
directory that is no vault otherwise. So maintenance never reopens a vault
|
|
588
|
+
window you closed while Obsidian keeps running, and never reaches another
|
|
589
|
+
vault: publication then stops as `publisher-conflict` until `atelier
|
|
590
|
+
obsidian open` opens the vault again or Obsidian quits.
|
|
591
|
+
- Detecting the app: the process table is read with `ps -A -o comm=`
|
|
592
|
+
(`pid=,comm=` on Linux), and the app is recognised by the executable a
|
|
593
|
+
process runs, never by its arguments, so a path argument that contains an
|
|
594
|
+
`obsidian` directory (the maintenance service itself, anything under the
|
|
595
|
+
private data root) is not the app. A `ps` that has not answered within five
|
|
596
|
+
seconds is killed and the reading is `unknown`, which is treated as a
|
|
597
|
+
running app.
|
|
598
|
+
- macOS: the executable `Obsidian` and the bundle's `Obsidian Helper`
|
|
599
|
+
processes count, in any directory. An app started through a differently
|
|
600
|
+
named link shows that name for its main process; its `Obsidian Helper`
|
|
601
|
+
processes still count.
|
|
602
|
+
- Linux: a process named `obsidian` counts, which covers the .deb, snap,
|
|
603
|
+
AppImage and Flatpak builds. Linux names a process after the path it was
|
|
604
|
+
started through, so for a name that is not recognised the probe reads
|
|
605
|
+
the executable `/proc/<pid>/exe` resolves to: an app started through a
|
|
606
|
+
link or launcher named `obs`, or a child started through
|
|
607
|
+
`/proc/self/exe` whose executable is readable, still counts. A process
|
|
608
|
+
whose executable this user may not read (another user's, or one of this
|
|
609
|
+
user's that hides it from tracing, such as ssh-agent) or that exited
|
|
610
|
+
meanwhile is judged by its name alone. Any other read failure, or a
|
|
611
|
+
table in which no executable resolves at all, is `unknown`. A
|
|
612
|
+
distribution that runs the app under a system Electron shows only
|
|
613
|
+
`electron`: the table is then `unknown`, so publication through the
|
|
614
|
+
app-free path waits until no such process runs.
|
|
615
|
+
- The command-line tool (`obsidian-cli`) is a client of the app and is not
|
|
616
|
+
counted. An app packaged under another executable name, an app on
|
|
617
|
+
another machine and an app that starts after a reading are not seen; see
|
|
618
|
+
"The path with no app" in [obsidian-contract.md](obsidian-contract.md).
|
|
619
|
+
- The app-free path is live: before 0.2.0-alpha.10 the probe always found
|
|
620
|
+
Atelier's own service, so publication never took the path with no app.
|
|
621
|
+
From 0.2.0-alpha.10 it does, whenever the process table shows, positively,
|
|
622
|
+
that no Obsidian runs. It applies only then. The table is read at path
|
|
623
|
+
selection, again immediately before the first note, and again whenever two
|
|
624
|
+
seconds have passed. An app that starts between two readings is not seen
|
|
625
|
+
for at most two seconds plus one note's publication, and in that window a
|
|
626
|
+
note is exchanged with no editor check. Bytes on disk stay protected by the
|
|
627
|
+
on-disk comparison and the exchange. An unsaved buffer in the newly started
|
|
628
|
+
app does not: the app applies its own external-modification merge, which
|
|
629
|
+
can drop overlapping edits. Once a reading shows an app, every remaining
|
|
630
|
+
note refuses. To avoid the window, do not start Obsidian while a view is
|
|
631
|
+
being published with the app closed.
|
|
403
632
|
- CRLF sources: the editor normalizes line endings when it saves, so a body
|
|
404
633
|
edit made in the vault to a note whose source uses CRLF is more than a body
|
|
405
634
|
replacement to the edit lens. It is preserved and becomes a proposal; it is
|
|
@@ -408,6 +637,15 @@ test reported as a pass.
|
|
|
408
637
|
- An edit the byte lens cannot turn into source bytes (a new or changed link
|
|
409
638
|
to another note of the vault, an edited front matter) becomes a copy-only
|
|
410
639
|
proposal. No operation applies one.
|
|
640
|
+
- Atelier's plugin (phase 1) reports presence and status and decides
|
|
641
|
+
nothing: publication still coordinates with the app through its
|
|
642
|
+
command-line tool, which must be enabled, and the plugin only supplies the
|
|
643
|
+
app version where that tool gives none. A person who turns the plugin off
|
|
644
|
+
in a vault, or uninstalls it there, is followed: its entry is not added
|
|
645
|
+
back and a deleted folder is not made again until the person turns it on
|
|
646
|
+
in Obsidian's settings or runs
|
|
647
|
+
`atelier obsidian plugin on --scope ID`. Plugin files are never removed from
|
|
648
|
+
a vault. See [obsidian-plugin.md](obsidian-plugin.md).
|
|
411
649
|
- Acceptance: a schema-valid receipt closes no gate, the package proof closes
|
|
412
650
|
no gate, and no adopter acceptance is recorded in this repository.
|
|
413
651
|
|
|
@@ -421,3 +659,9 @@ The test suite proves each oracle can fail:
|
|
|
421
659
|
- `createReceiptValidatorForOracleTests` accepts a rule table with one rule
|
|
422
660
|
switched off; every negative case that depends on that rule then passes,
|
|
423
661
|
and even that closes nothing.
|
|
662
|
+
- `openScopeForOracleTests` with an `OPENING_PRIMITIVES.keptByApp` that
|
|
663
|
+
never holds: an open with Obsidian running without the vault reports the
|
|
664
|
+
publisher conflict it could have cleared, and the first-open oracle fails.
|
|
665
|
+
- `createMaintenanceEngineForOracleTests` with an
|
|
666
|
+
`ENGINE_PRIMITIVES.isRetryDue` that never holds: a refused view waits for
|
|
667
|
+
the full reconciliation, and the retry oracle fails.
|
|
@@ -63,7 +63,13 @@ Shipped, and required by the audit:
|
|
|
63
63
|
- Every `contracts/atelier-obsidian-<name>.v<n>.schema.json` present in the
|
|
64
64
|
source tree (lowercase name, versioned). Each must be packed and exported
|
|
65
65
|
under its own path, and the audit fails if it finds none.
|
|
66
|
-
- `docs/obsidian.md` and `docs/obsidian-
|
|
66
|
+
- `docs/obsidian.md`, `docs/obsidian-contract.md` and `docs/obsidian-plugin.md`.
|
|
67
|
+
- Atelier's own Obsidian plugin, which every managed vault receives:
|
|
68
|
+
`plugins/obsidian/manifest.json`, `main.js` and `styles.css`, the only files
|
|
69
|
+
admitted under `plugins/`, with its channel
|
|
70
|
+
(`src/projection/obsidian/plugin-bridge/index.mjs`) and the service's half
|
|
71
|
+
(`src/runtime/obsidian/plugin-channel.mjs`). `main.js` is scanned for egress
|
|
72
|
+
like every packed script.
|
|
67
73
|
- `fixtures/obsidian/**`: small synthetic text only. Every packed fixture, in
|
|
68
74
|
any subtree and spelling under `fixtures/`, is at most 262,144 bytes. The
|
|
69
75
|
acceptance receipts under `fixtures/obsidian/acceptance/receipts/` are
|
package/docs/upgrade.md
CHANGED
|
@@ -9,6 +9,35 @@ workspace between Atelier package releases. The flow is local-only: it does not
|
|
|
9
9
|
provision repositories, contact a Git host, mutate the MNSTRY runtime, or write
|
|
10
10
|
through a browser view.
|
|
11
11
|
|
|
12
|
+
## Upgrading to 0.2.0-alpha.12
|
|
13
|
+
|
|
14
|
+
This release makes the first open of an Obsidian view automatic and ships
|
|
15
|
+
Atelier's own plugin. `open` adds the view's vault to Obsidian itself, whether
|
|
16
|
+
Obsidian is running or not, and publishes without quitting it; a closed
|
|
17
|
+
Obsidian is started so your other vaults reopen. Vaults are laid out
|
|
18
|
+
readably: folders mirror your repository and file names are note titles,
|
|
19
|
+
with identity kept in a properties block; a vault published by an earlier
|
|
20
|
+
release is laid out again once, keeping held edits. Each managed vault gets
|
|
21
|
+
the Atelier plugin (one trust prompt per vault) with a status bar. A
|
|
22
|
+
maintenance service of an earlier release is replaced by `open` under the
|
|
23
|
+
consent already recorded. No project or machine setting needs to change.
|
|
24
|
+
Machine settings stay v1; generation manifests move to v2 (below).
|
|
25
|
+
|
|
26
|
+
Node.js 24 (24.13.1 or later) is now supported alongside Node.js 22, and
|
|
27
|
+
both write byte-identical graph files, so people on either can share a
|
|
28
|
+
repository. `atelier dev` on a port that is already taken now says
|
|
29
|
+
`port-in-use` and names `--port=<free port>`; before, it printed an internal
|
|
30
|
+
error. A script that read that failure's exit code sees 2 instead of 1.
|
|
31
|
+
|
|
32
|
+
## Upgrading to 0.2.0-alpha.11
|
|
33
|
+
|
|
34
|
+
This release tightens the Obsidian projection's app checks after independent
|
|
35
|
+
review of 0.2.0-alpha.10: Atelier never coordinates with an Obsidian whose version it
|
|
36
|
+
has not checked, the advice for a refused publication is true for every cause
|
|
37
|
+
and appears in `status` as well as `open`, Linux resolves an app started
|
|
38
|
+
through a differently named launcher, and the process check has a timeout.
|
|
39
|
+
No project or machine setting needs to change.
|
|
40
|
+
|
|
12
41
|
## Upgrading to 0.2.0-alpha.10
|
|
13
42
|
|
|
14
43
|
This release fixes the first activation of the Obsidian projection. In
|
|
@@ -154,7 +183,7 @@ See `docs/contract-stability.md`.
|
|
|
154
183
|
## Before You Upgrade
|
|
155
184
|
|
|
156
185
|
- Start from a clean Git status in the copied workspace.
|
|
157
|
-
- Confirm Node.js matches the package range: `>=22.18.0 <23`.
|
|
186
|
+
- Confirm Node.js matches the package range: `>=22.18.0 <23 || >=24.13.1 <25`.
|
|
158
187
|
- Keep this package root and copied starter workspaces separate.
|
|
159
188
|
- Keep upgrade diffs generic: placeholders only, no project-specific private
|
|
160
189
|
material, transcripts, support bundles, local absolute paths, local agent
|
|
@@ -176,7 +205,7 @@ For registry installs, pin the exact version and record the resolved version
|
|
|
176
205
|
in the lockfile:
|
|
177
206
|
|
|
178
207
|
```bash
|
|
179
|
-
npm install --save-dev @mnstry/atelier@0.2.0-alpha.
|
|
208
|
+
npm install --save-dev @mnstry/atelier@0.2.0-alpha.12
|
|
180
209
|
npx mnstry-atelier lock write --project ./atelier.project.json
|
|
181
210
|
```
|
|
182
211
|
|
|
@@ -184,7 +213,7 @@ For Git installs, pin the release tag rather than a branch, so the lock file
|
|
|
184
213
|
records exactly what was reviewed:
|
|
185
214
|
|
|
186
215
|
```bash
|
|
187
|
-
npm install --save-dev "git+https://github.com/MNSTRY/atelier.git#v0.2.0-alpha.
|
|
216
|
+
npm install --save-dev "git+https://github.com/MNSTRY/atelier.git#v0.2.0-alpha.12"
|
|
188
217
|
npx mnstry-atelier lock write --project ./atelier.project.json
|
|
189
218
|
```
|
|
190
219
|
|
package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "atelier-obsidian-generation-manifest/v2",
|
|
3
|
+
"layoutVersion": 2,
|
|
4
|
+
"generationId": "gen-0001",
|
|
5
|
+
"scopeId": "scope-topic",
|
|
6
|
+
"snapshotId": "snap-0001",
|
|
7
|
+
"notes": [
|
|
8
|
+
{
|
|
9
|
+
"repoId": "north",
|
|
10
|
+
"nodeId": "a",
|
|
11
|
+
"title": "Shared concept",
|
|
12
|
+
"path": "north/topics/Shared concept.md",
|
|
13
|
+
"noteDigest": "sha256:5555555555555555555555555555555555555555555555555555555555555555",
|
|
14
|
+
"regions": {
|
|
15
|
+
"frontmatter": {
|
|
16
|
+
"start": 0,
|
|
17
|
+
"end": 120
|
|
18
|
+
},
|
|
19
|
+
"identity": {
|
|
20
|
+
"start": 40,
|
|
21
|
+
"end": 116
|
|
22
|
+
},
|
|
23
|
+
"body": {
|
|
24
|
+
"start": 120,
|
|
25
|
+
"end": 280
|
|
26
|
+
},
|
|
27
|
+
"generated": [
|
|
28
|
+
{
|
|
29
|
+
"kind": "relations",
|
|
30
|
+
"range": {
|
|
31
|
+
"start": 280,
|
|
32
|
+
"end": 340
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"repoId": "north",
|
|
40
|
+
"nodeId": "d",
|
|
41
|
+
"title": "Shared concept",
|
|
42
|
+
"path": "north/topics/shared CONCEPT.md",
|
|
43
|
+
"noteDigest": "sha256:6666666666666666666666666666666666666666666666666666666666666666",
|
|
44
|
+
"regions": {
|
|
45
|
+
"frontmatter": {
|
|
46
|
+
"start": 0,
|
|
47
|
+
"end": 40
|
|
48
|
+
},
|
|
49
|
+
"body": {
|
|
50
|
+
"start": 40,
|
|
51
|
+
"end": 200
|
|
52
|
+
},
|
|
53
|
+
"generated": [
|
|
54
|
+
{
|
|
55
|
+
"kind": "relations",
|
|
56
|
+
"range": {
|
|
57
|
+
"start": 200,
|
|
58
|
+
"end": 260
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"kind": "identity",
|
|
63
|
+
"range": {
|
|
64
|
+
"start": 260,
|
|
65
|
+
"end": 340
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"identity": {
|
|
70
|
+
"start": 260,
|
|
71
|
+
"end": 340
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"repoId": "north",
|
|
77
|
+
"nodeId": "e",
|
|
78
|
+
"title": "Diagram",
|
|
79
|
+
"path": "north/figures/diagram.svg.md",
|
|
80
|
+
"noteDigest": "sha256:8888888888888888888888888888888888888888888888888888888888888888",
|
|
81
|
+
"regions": {
|
|
82
|
+
"frontmatter": {
|
|
83
|
+
"start": 0,
|
|
84
|
+
"end": 80
|
|
85
|
+
},
|
|
86
|
+
"identity": {
|
|
87
|
+
"start": 0,
|
|
88
|
+
"end": 80
|
|
89
|
+
},
|
|
90
|
+
"body": {
|
|
91
|
+
"start": 240,
|
|
92
|
+
"end": 240
|
|
93
|
+
},
|
|
94
|
+
"generated": [
|
|
95
|
+
{
|
|
96
|
+
"kind": "representation",
|
|
97
|
+
"range": {
|
|
98
|
+
"start": 80,
|
|
99
|
+
"end": 240
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"links": [
|
|
107
|
+
{
|
|
108
|
+
"edgeId": "e4",
|
|
109
|
+
"sourceNodeId": "d",
|
|
110
|
+
"targetNodeId": "a",
|
|
111
|
+
"type": "links_to",
|
|
112
|
+
"origin": "markdown",
|
|
113
|
+
"targetState": "in-scope",
|
|
114
|
+
"inversions": [
|
|
115
|
+
{
|
|
116
|
+
"source": {
|
|
117
|
+
"start": 12,
|
|
118
|
+
"end": 30
|
|
119
|
+
},
|
|
120
|
+
"note": {
|
|
121
|
+
"start": 52,
|
|
122
|
+
"end": 96
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"edgeId": "e5",
|
|
129
|
+
"sourceNodeId": "a",
|
|
130
|
+
"targetNodeId": "d",
|
|
131
|
+
"type": "supersedes",
|
|
132
|
+
"origin": "declared",
|
|
133
|
+
"targetState": "in-scope"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"edgeId": "e1",
|
|
137
|
+
"sourceNodeId": "a",
|
|
138
|
+
"targetNodeId": "b",
|
|
139
|
+
"type": "related",
|
|
140
|
+
"origin": "declared",
|
|
141
|
+
"targetState": "outside-selection"
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
"attachments": [
|
|
145
|
+
{
|
|
146
|
+
"path": "north/figures/diagram.svg",
|
|
147
|
+
"digest": "sha256:7777777777777777777777777777777777777777777777777777777777777777",
|
|
148
|
+
"byteLength": 900
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"completeness": {
|
|
152
|
+
"status": "complete",
|
|
153
|
+
"expectedNotes": 3,
|
|
154
|
+
"writtenNotes": 3
|
|
155
|
+
},
|
|
156
|
+
"freshness": {
|
|
157
|
+
"status": "current",
|
|
158
|
+
"checkedAt": "2026-01-05T10:00:05Z"
|
|
159
|
+
}
|
|
160
|
+
}
|