@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
|
@@ -260,10 +260,33 @@ That is how a public site's owner-authorized mock cart stranded real work on a
|
|
|
260
260
|
machine for weeks.
|
|
261
261
|
|
|
262
262
|
- `pre-commit` runs `atelier boundary check --staged` — added lines in the
|
|
263
|
-
|
|
263
|
+
index Git is committing, including the temporary index of `git commit -a`
|
|
264
|
+
or `git commit <paths>` and a linked worktree's own index.
|
|
264
265
|
- `pre-push` runs `atelier boundary push-check` — git writes the ref updates to
|
|
265
|
-
the hook's stdin, and only that range is judged
|
|
266
|
-
|
|
266
|
+
the hook's stdin, and only that range is judged: the change from the
|
|
267
|
+
remote's commit to the pushed tip, and every commit the push carries that the
|
|
268
|
+
remote's tracking refs do not hold, each against its parent, so content added
|
|
269
|
+
and removed again within the push is still caught (the finding names its
|
|
270
|
+
commit). A merge is judged only for what is new against all of its parents,
|
|
271
|
+
so a pull merge into the branch you push is not refused for what the remote
|
|
272
|
+
already holds; merging another branch into a feature branch can still be
|
|
273
|
+
refused through the change from the remote's commit, as before. The hook
|
|
274
|
+
passes the remote Git names: a configured remote, or a URL matched to the
|
|
275
|
+
remote configured with it. Only that remote's tracking refs, stale ones
|
|
276
|
+
included, exclude commits (a remote whose `pushurl` names another
|
|
277
|
+
repository is still scoped by its fetch-side tracking refs); a URL no remote
|
|
278
|
+
is configured with excludes only the remote's commit. A hook installed before this judges against every
|
|
279
|
+
remote's tracking refs, so reinstall it (`atelier boundary install-hooks`)
|
|
280
|
+
for history that only another remote holds to be judged on its way to a new
|
|
281
|
+
one. A brand-new branch is diffed against the empty tree, in SHA-1 and SHA-256
|
|
282
|
+
repositories alike, so nothing slips through unscanned. A push carrying more
|
|
283
|
+
than 1000 such commits, or more evidence than the aggregate budget, is
|
|
284
|
+
refused; push it in smaller parts, an earlier commit first. Each diff reads
|
|
285
|
+
what it needs, so a history that swaps large binaries back and forth makes
|
|
286
|
+
the hook slow, within the commit cap and per-diff limits. The remote's
|
|
287
|
+
tracking refs are found by name: a remote whose fetch refspec writes into
|
|
288
|
+
another remote's namespace, or two remote names that differ only in case on
|
|
289
|
+
a case-insensitive file system, can blur which history the remote holds.
|
|
267
290
|
- `atelier boundary audit` scans the current working tree by default and
|
|
268
291
|
**reports without blocking**, listing matches, incomplete-read diagnostics,
|
|
269
292
|
and declared exceptions with their reasons. Use `--head` for a committed
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Semantic operation: internal vanilla profile
|
|
2
2
|
|
|
3
3
|
`src/knowledge/semantic-operation.mjs` connects a host's extraction to the
|
|
4
|
-
existing intake and knowledge owners. It is an internal library
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
existing intake and knowledge owners. It is an internal library with no public
|
|
5
|
+
export or registered schema. Since 0.2.0-alpha.13 the experimental CLI reaches
|
|
6
|
+
it as `atelier knowledge semantic OPERATION`; an agent operating that command
|
|
7
|
+
is its intended consumer.
|
|
7
8
|
|
|
8
9
|
Atelier never invokes a model. The host supplies its raw output, its normalized
|
|
9
10
|
candidate envelope, configuration pins and usage report. A deterministic or
|
package/docs/upgrade.md
CHANGED
|
@@ -9,6 +9,59 @@ 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.14
|
|
13
|
+
|
|
14
|
+
`atelier upgrade closure --npm-root ABSOLUTE_NPM_ROOT` plans a check of a staged
|
|
15
|
+
candidate's own dependency closure before an upgrade. It reads the candidate's
|
|
16
|
+
lockfile and manifests only and runs no npm command. Exit code 3 is a completed
|
|
17
|
+
plan and exit code 2 a refusal; the command never exits 0, because a plan is not
|
|
18
|
+
proof of an offline reinstall. It refuses `--__proto__` as an unsupported flag.
|
|
19
|
+
See [Consumer dependency closure plan](#consumer-dependency-closure-plan).
|
|
20
|
+
|
|
21
|
+
This release also introduces `@mnstry/atelier/ui`, a tenant-neutral control
|
|
22
|
+
grammar for local Atelier tooling and compatible downstream interfaces. Existing
|
|
23
|
+
consumers do not need to change. Consumers that adopt it map the
|
|
24
|
+
`--system-control-*` custom properties to their own theme.
|
|
25
|
+
|
|
26
|
+
No schema or stored record format changes in this release. The boundary checks
|
|
27
|
+
now judge work they used to miss, so a commit or push that passed on
|
|
28
|
+
0.2.0-alpha.13 can be refused, and a few other results change:
|
|
29
|
+
|
|
30
|
+
- Reinstall the boundary hooks with `atelier boundary install-hooks`. The new
|
|
31
|
+
pre-push hook passes the remote Git names, so history held only by another
|
|
32
|
+
remote is judged on its way to a new one. The push check judges every commit
|
|
33
|
+
the remote's tracking refs do not hold, each against its parent, binaries
|
|
34
|
+
included. A push carrying more than 1000 such commits or more than the
|
|
35
|
+
evidence budget is refused with `content-scan-incomplete`; push it in
|
|
36
|
+
smaller parts.
|
|
37
|
+
- The pre-commit check reads the index and work tree Git names, so
|
|
38
|
+
`git commit -a`, `git commit <paths>` and commits from a linked worktree are
|
|
39
|
+
judged. It blocks with `pre-commit-repo-unresolved` when no single declared
|
|
40
|
+
repository is being committed, as the push check does.
|
|
41
|
+
- The content scan pins its diff format, so an external diff, a textconv
|
|
42
|
+
driver, changed prefixes or quoted paths no longer leave a change unscanned.
|
|
43
|
+
A staged or pushed deletion of a binary file no longer blocks.
|
|
44
|
+
- The semantic-field review takes only an added review marker as approval; a
|
|
45
|
+
removed marker, or one in a file name, approves nothing. `atelier upgrade`
|
|
46
|
+
applies the same rule.
|
|
47
|
+
- `extract-structure` refuses `parallel: true` with `NATIVE_WORKERS_UNATTESTED`.
|
|
48
|
+
- Knowledge Health regenerates its rule identity for the two graph files: the
|
|
49
|
+
`check` each assessment carries names a new `version`, `sourceTree` and
|
|
50
|
+
`graphBlob`. The rule it applies is unchanged. A workshop contribution
|
|
51
|
+
started on 0.2.0-alpha.13, completed or not, cannot start Review on this
|
|
52
|
+
release (`finding changed; refresh before selecting a new draft`); start
|
|
53
|
+
Review before upgrading, or record a new contribution.
|
|
54
|
+
- `runMaintenanceService` from `@mnstry/atelier/obsidian` refuses a project
|
|
55
|
+
loader that answers with a promise when no `locateProject` is given
|
|
56
|
+
(`service-loader-not-synchronous`); on 0.2.0-alpha.13 such a service
|
|
57
|
+
protected no repository. Pass a synchronous `locateProject`, which answers
|
|
58
|
+
a project naming its folder, to keep an asynchronous loader (see
|
|
59
|
+
[Obsidian](obsidian.md)).
|
|
60
|
+
|
|
61
|
+
A package update supplies no interpretation approval, source-write permission,
|
|
62
|
+
publication authority or runtime activation. Rehearse this exact installed
|
|
63
|
+
version in a disposable workspace before client use.
|
|
64
|
+
|
|
12
65
|
## Upgrading to 0.2.0-alpha.13
|
|
13
66
|
|
|
14
67
|
The public [Knowledge Health workshop](knowledge-health/devday/public-default.md)
|
|
@@ -250,7 +303,7 @@ For registry installs, pin the exact version and record the resolved version
|
|
|
250
303
|
in the lockfile:
|
|
251
304
|
|
|
252
305
|
```bash
|
|
253
|
-
npm install --save-dev @mnstry/atelier@0.2.0-alpha.
|
|
306
|
+
npm install --save-dev @mnstry/atelier@0.2.0-alpha.14
|
|
254
307
|
npx mnstry-atelier lock write --project ./atelier.project.json
|
|
255
308
|
```
|
|
256
309
|
|
|
@@ -258,7 +311,7 @@ For Git installs, pin the release tag rather than a branch, so the lock file
|
|
|
258
311
|
records exactly what was reviewed:
|
|
259
312
|
|
|
260
313
|
```bash
|
|
261
|
-
npm install --save-dev "git+https://github.com/MNSTRY/atelier.git#v0.2.0-alpha.
|
|
314
|
+
npm install --save-dev "git+https://github.com/MNSTRY/atelier.git#v0.2.0-alpha.14"
|
|
262
315
|
npx mnstry-atelier lock write --project ./atelier.project.json
|
|
263
316
|
```
|
|
264
317
|
|
|
@@ -337,3 +390,122 @@ For the bounded, manually confirmed single-repository workflow, see
|
|
|
337
390
|
commands are separate from the legacy flag-based migration workflow above.
|
|
338
391
|
They preserve history and honor existing commit hooks; they do not install a
|
|
339
392
|
new package or activate a runtime.
|
|
393
|
+
|
|
394
|
+
## Consumer dependency closure plan
|
|
395
|
+
|
|
396
|
+
`atelier upgrade closure --npm-root ABSOLUTE_NPM_ROOT` plans a check of a staged
|
|
397
|
+
candidate's own dependency closure. A consumer does not inherit a dependency
|
|
398
|
+
publisher's npm `overrides`, so the candidate's own lockfile is what counts.
|
|
399
|
+
|
|
400
|
+
The command reads these files and no others:
|
|
401
|
+
|
|
402
|
+
- the candidate's `package.json` and `package-lock.json`;
|
|
403
|
+
- the installed `package.json` of every lockfile copy of the selected package;
|
|
404
|
+
- `package.json` in each directory above the npm root, up to the filesystem
|
|
405
|
+
root, to refuse a parent that declares workspaces. When the npm root is under
|
|
406
|
+
your home directory, that includes a `package.json` in the home directory;
|
|
407
|
+
- Atelier's own `package.json`. The CLI reads it on every run for its version;
|
|
408
|
+
this plan also takes the default package name from it when `--package` is
|
|
409
|
+
not given.
|
|
410
|
+
|
|
411
|
+
It does not run npm, use the network, read npm configuration or credentials, or
|
|
412
|
+
write anything. The directory need not be an Atelier workspace, and `--npm-root`
|
|
413
|
+
has no default.
|
|
414
|
+
|
|
415
|
+
Every run prints exactly one JSON document with `"proof": "not-run"`, including
|
|
416
|
+
a run whose arguments are refused. A completed plan holds:
|
|
417
|
+
|
|
418
|
+
- the lockfile's SHA-256, version and entry count;
|
|
419
|
+
- entries by source (`npm`, `git`, `local_path`, `link`, `bundled` and
|
|
420
|
+
`unclassified`, an installed entry with no `resolved`); the counts add up to
|
|
421
|
+
the entry count;
|
|
422
|
+
- the registry hosts the lockfile names, and separately the URL schemes (`http`,
|
|
423
|
+
`https`) it uses;
|
|
424
|
+
- the number of registry tarballs an offline proof would need;
|
|
425
|
+
- lockfile entries whose `os`, `cpu` or `libc` excludes this host;
|
|
426
|
+
- the selected package's lockfile copies, and under `manifests` the path, name,
|
|
427
|
+
version and SHA-256 of each installed manifest that was read;
|
|
428
|
+
- the selected package's exact `overrides` that were checked against every
|
|
429
|
+
installed copy, the ones with no copy, and the ones left unchecked: a
|
|
430
|
+
version-selector key, a nested override, a value that is not an exact
|
|
431
|
+
version, or a package that a link provides (`link-only`, or `linked-copy`
|
|
432
|
+
when a registry copy is installed beside the link);
|
|
433
|
+
- every refusal, with its code, one `reason`, a message and a next step.
|
|
434
|
+
|
|
435
|
+
A refused plan holds its refusals and whatever else was established. A refusal
|
|
436
|
+
of the arguments, the npm root or the shape of the lockfile stops the plan
|
|
437
|
+
there, so it holds nothing about the lockfile's entries; any other refusal is
|
|
438
|
+
listed beside the rest of the classification.
|
|
439
|
+
|
|
440
|
+
Exit code 3 means a completed plan. Exit code 2 means a refusal: the JSON
|
|
441
|
+
document, then `[code] message` with a `Next:` step on standard error. The
|
|
442
|
+
command never exits 0: a plan is not a pass, and this command does not prove an
|
|
443
|
+
offline reinstall.
|
|
444
|
+
|
|
445
|
+
Overrides are taken from the installed manifest only when it is the package the
|
|
446
|
+
lockfile records: each copy's manifest must declare the selected name and the
|
|
447
|
+
version of its own lockfile entry, and all copies must be at one version with
|
|
448
|
+
the same overrides. Otherwise the plan is refused and no override is reported
|
|
449
|
+
as checked.
|
|
450
|
+
|
|
451
|
+
Refusals reuse existing codes:
|
|
452
|
+
|
|
453
|
+
- `usage`: the input is not supported. Reasons: `npm-root-required`,
|
|
454
|
+
`npm-root-not-absolute`, `npm-root-not-directory`, `npm-root-linked` (the
|
|
455
|
+
path is a link or passes through one; the refusal prints the directory's
|
|
456
|
+
`realPath` to pass instead), `argument-unsupported` (an unknown flag, an
|
|
457
|
+
extra argument, a flag with no value, an empty `--package` or
|
|
458
|
+
`--registry-host`, or a flag given more than once; an empty value after
|
|
459
|
+
`--npm-root=` is `npm-root-required`), `manifest-missing`, `lockfile-missing`,
|
|
460
|
+
`lockfile-invalid` (not a JSON object, or a `packages` entry that is not an
|
|
461
|
+
object), `lockfile-version` (version 1, or no `packages` map), `workspaces`,
|
|
462
|
+
`prefix-mismatch` (a parent directory declares workspaces) and
|
|
463
|
+
`linked-node-modules`.
|
|
464
|
+
- `consumer-closure-incomplete`: an offline reinstall could not be proven for a
|
|
465
|
+
lockfile entry. Reasons: `missing-integrity`, `git-source`, `other-host`,
|
|
466
|
+
`outside-project`, `link-target-missing` (a link that names no folder),
|
|
467
|
+
`zero-copies`, `selected-package-unreadable`, `selected-package-mismatch`
|
|
468
|
+
(an installed manifest that is not the package its lockfile entry records)
|
|
469
|
+
and `selected-copies-differ`.
|
|
470
|
+
- `override-not-inherited`: reason `copy-off-pin`. A copy is installed at a
|
|
471
|
+
version other than the selected package's exact pin. More than one copy, all
|
|
472
|
+
at the pin, is reported and not refused.
|
|
473
|
+
|
|
474
|
+
Options:
|
|
475
|
+
|
|
476
|
+
- `--package NAME` selects the package whose overrides are checked. It defaults
|
|
477
|
+
to `@mnstry/atelier`.
|
|
478
|
+
- `--registry-host HOST[,HOST]` names the approved registry hosts. A tarball on
|
|
479
|
+
any other host, or at a URL with no readable host, is then refused. Without
|
|
480
|
+
it, hosts are reported and `approvedRegistryVerified` is `false`, because the
|
|
481
|
+
plan does not read npm configuration.
|
|
482
|
+
|
|
483
|
+
Give each option once, with a value, after the word `closure`. An option with
|
|
484
|
+
an empty value is refused; leave the option out to use its default.
|
|
485
|
+
|
|
486
|
+
Limits:
|
|
487
|
+
|
|
488
|
+
- The plan cannot run `npm prefix`, so `prefixVerified` is always `false`.
|
|
489
|
+
Instead it requires `--npm-root` to be the directory's real path and refuses
|
|
490
|
+
any parent directory of it that declares workspaces, whether or not that
|
|
491
|
+
parent's workspaces include the candidate. A path that is a link, or passes
|
|
492
|
+
through one, is refused, with its `realPath` in the JSON result. On macOS,
|
|
493
|
+
`/tmp` is a link into `/private` and `$TMPDIR` passes through `/var`, another
|
|
494
|
+
such link, so a candidate staged there needs its real path (for example
|
|
495
|
+
`pwd -P` from inside it).
|
|
496
|
+
- Containment of links and local files is checked by path, without following
|
|
497
|
+
links. The folder a link points to is not opened: an override for a package
|
|
498
|
+
that a link provides is listed as unchecked, never as checked.
|
|
499
|
+
- Approving a host does not verify the transport. A host is matched by its
|
|
500
|
+
exact name and port, whatever the scheme; `registrySchemes` counts `http`
|
|
501
|
+
and `https` separately.
|
|
502
|
+
- The manifest binding compares the name and version the installed manifest
|
|
503
|
+
declares. It does not verify the installed files against the lockfile's
|
|
504
|
+
`integrity`.
|
|
505
|
+
- Platform evidence uses this process's `os` and `cpu`; `libc` is unknown
|
|
506
|
+
unless a caller of `planConsumerClosure` supplies it.
|
|
507
|
+
- A flag with no value placed before the word `closure` (for example
|
|
508
|
+
`atelier upgrade --package closure`) takes `closure` as its value, so the
|
|
509
|
+
ordinary upgrade command runs instead of this one.
|
|
510
|
+
- `lockTreeDifferences`, the comparison of two `npm ls` trees, is in the source
|
|
511
|
+
but is not exported from the package and no command uses it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mnstry/atelier",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Local toolkit that turns a Git repository into an ontology-governed knowledge graph, enforcement layer, and runtime for people, agents, and tools. No telemetry or required hosted account; optional host-bound vault adapters.",
|
|
6
6
|
"keywords": [
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"./readiness": "./src/readiness/readiness.mjs",
|
|
62
62
|
"./readiness-protocols": "./src/readiness-protocols/bundled-pack.mjs",
|
|
63
63
|
"./server": "./src/server/local-sidecar.mjs",
|
|
64
|
+
"./ui": "./src/ui/control-system.mjs",
|
|
64
65
|
"./harness": "./src/harness/context.mjs",
|
|
65
66
|
"./collaboration": "./src/collaboration/index.mjs",
|
|
66
67
|
"./support": "./src/support/support-bundle.mjs",
|
package/plugins/obsidian/main.js
CHANGED
|
@@ -196,11 +196,14 @@ class AtelierStatusModal extends obsidian.Modal {
|
|
|
196
196
|
class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
197
197
|
async onload() {
|
|
198
198
|
this.channel = null
|
|
199
|
+
this.lastObservedStatus = null
|
|
199
200
|
this.session = null
|
|
200
201
|
this.cycling = null
|
|
201
202
|
this.unloaded = false
|
|
203
|
+
this.channelAdmitted = false
|
|
202
204
|
this.view = { state: 'connecting', reason: 'not-yet-asked', report: null }
|
|
203
205
|
this.shownState = null
|
|
206
|
+
this.noticeState = null
|
|
204
207
|
// A round's failure that differs from the failure shown, not yet seen twice in a row.
|
|
205
208
|
this.pendingFailure = null
|
|
206
209
|
this.appVersion = typeof obsidian.apiVersion === 'string' ? obsidian.apiVersion : null
|
|
@@ -226,6 +229,7 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
226
229
|
this.setView({ state: 'not-set-up', reason: 'node-modules-unavailable', report: null })
|
|
227
230
|
return
|
|
228
231
|
}
|
|
232
|
+
this.channelAdmitted = true
|
|
229
233
|
// This launch of the plugin: the service tells two apps holding the same vault apart by it.
|
|
230
234
|
this.instanceId = `pi-${this.crypto.randomBytes(16).toString('hex')}`
|
|
231
235
|
await this.readChannel()
|
|
@@ -238,6 +242,8 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
238
242
|
// The lease lapses by itself; releasing it says at once that the vault closed. A round still under way stops at its
|
|
239
243
|
// next step, and a session it opens after this is released at once.
|
|
240
244
|
this.unloaded = true
|
|
245
|
+
this.channelAdmitted = false
|
|
246
|
+
this.lastObservedStatus = null
|
|
241
247
|
const session = this.session
|
|
242
248
|
this.session = null
|
|
243
249
|
if (this.channel && session) void this.release(this.channel, session)
|
|
@@ -245,6 +251,7 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
245
251
|
|
|
246
252
|
// Obsidian calls this when data.json changed on disk: Atelier republished the channel.
|
|
247
253
|
async onExternalSettingsChange() {
|
|
254
|
+
if (!this.channelAdmitted || this.unloaded) return
|
|
248
255
|
await this.readChannel()
|
|
249
256
|
// A round under way still speaks for the old channel; the next one uses the new.
|
|
250
257
|
if (this.cycling) await this.cycling
|
|
@@ -252,13 +259,19 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
252
259
|
}
|
|
253
260
|
|
|
254
261
|
async readChannel() {
|
|
262
|
+
if (!this.channelAdmitted || this.unloaded) return
|
|
255
263
|
let data = null
|
|
256
264
|
try { data = await this.loadData() } catch { data = null }
|
|
265
|
+
if (!this.channelAdmitted || this.unloaded) return
|
|
257
266
|
const next = channelOf(data)
|
|
258
267
|
const same = next && this.channel && next.host === this.channel.host && next.port === this.channel.port && next.scopeId === this.channel.scopeId && next.bearer === this.channel.bearer
|
|
259
|
-
if (!same)
|
|
268
|
+
if (!same) {
|
|
269
|
+
this.session = null
|
|
270
|
+
this.lastObservedStatus = null
|
|
271
|
+
} else if (this.lastObservedStatus?.channel === this.channel) this.lastObservedStatus.channel = next
|
|
260
272
|
this.channel = next
|
|
261
273
|
if (next === null) this.setView({ state: 'not-set-up', reason: data === null ? 'no-channel-data' : 'channel-data-unusable', report: null })
|
|
274
|
+
else if (!same) this.setView({ state: 'connecting', reason: 'not-yet-asked', report: null })
|
|
262
275
|
}
|
|
263
276
|
|
|
264
277
|
// One round: shake hands (once per session), renew the lease, read the status.
|
|
@@ -270,12 +283,12 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
270
283
|
|
|
271
284
|
// Whether a round that started for `channel` may still act.
|
|
272
285
|
current(channel) {
|
|
273
|
-
return !this.unloaded && channel === this.channel
|
|
286
|
+
return this.channelAdmitted && !this.unloaded && channel === this.channel
|
|
274
287
|
}
|
|
275
288
|
|
|
276
289
|
async runCycle() {
|
|
277
290
|
const channel = this.channel
|
|
278
|
-
if (channel === null || this.unloaded) return
|
|
291
|
+
if (!this.channelAdmitted || channel === null || this.unloaded) return
|
|
279
292
|
if (this.session === null) {
|
|
280
293
|
if (!(await this.handshake(channel))) return
|
|
281
294
|
} else {
|
|
@@ -290,6 +303,7 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
290
303
|
return
|
|
291
304
|
}
|
|
292
305
|
// The service started again since, the vault's key changed, or the session is not accepted: shake hands at once.
|
|
306
|
+
this.lastObservedStatus = null
|
|
293
307
|
if (!(await this.handshake(channel))) return
|
|
294
308
|
}
|
|
295
309
|
}
|
|
@@ -386,13 +400,18 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
386
400
|
|
|
387
401
|
unreachable(answer) {
|
|
388
402
|
const reason = answer.kind === 'unreachable' ? answer.code : typeof answer.error === 'string' ? answer.error : answer.body && typeof answer.body.error === 'string' ? answer.body.error : `answered-${answer.statusCode}`
|
|
389
|
-
|
|
403
|
+
// Retain history only for a transport interruption, never an unsealed or
|
|
404
|
+
// refused answer, failed listener proof or unknown authentication state.
|
|
405
|
+
const transportFailure = answer.kind === 'unreachable' && ['refused', 'timeout'].includes(answer.code)
|
|
406
|
+
return { state: 'unreachable', reason, report: null, transportFailure }
|
|
390
407
|
}
|
|
391
408
|
|
|
392
409
|
// What a round found. A failure other than the one shown ('not set up' where 'service unreachable' is shown, or the other
|
|
393
410
|
// way round) is shown only when the next round finds it too, so answers that alternate between them do not make the
|
|
394
411
|
// status bar flip. Anything else is shown at once.
|
|
395
412
|
showRound(view) {
|
|
413
|
+
// A setup refusal clears remembered evidence even while its label is debounced.
|
|
414
|
+
if (view.state === 'not-set-up' || (view.state === 'unreachable' && view.transportFailure !== true)) this.lastObservedStatus = null
|
|
396
415
|
if (ROUND_FAILURES.has(view.state) && ROUND_FAILURES.has(this.shownState) && view.state !== this.shownState && this.pendingFailure !== view.state) {
|
|
397
416
|
this.pendingFailure = view.state
|
|
398
417
|
return
|
|
@@ -404,11 +423,32 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
404
423
|
// Nothing is shown once the plugin unloaded.
|
|
405
424
|
if (this.unloaded) return
|
|
406
425
|
this.pendingFailure = null
|
|
407
|
-
const previous = this.
|
|
426
|
+
const previous = this.noticeState
|
|
427
|
+
// A transient display projection, never a freshness or permission record.
|
|
428
|
+
// Only a report of this exact channel may be remembered. `channel` is an
|
|
429
|
+
// in-memory identity binding, never shown or stored; the other fields hold
|
|
430
|
+
// no service health, edit counts, source content or credentials.
|
|
431
|
+
if (!this.channel || view.state === 'not-set-up' || view.state === 'unsupported' || (view.state === 'unreachable' && view.transportFailure !== true)) this.lastObservedStatus = null
|
|
432
|
+
else if (view.report !== null) {
|
|
433
|
+
const report = view.report
|
|
434
|
+
const observed = isPlainObject(report) && report.schema === CHANNEL.statusSchema && report.scopeId === this.channel.scopeId && isPlainObject(report.view) && report.view.state !== 'disabled' ? report.view : null
|
|
435
|
+
this.lastObservedStatus = observed === null ? null : {
|
|
436
|
+
channel: this.channel, state: view.state, reason: view.reason,
|
|
437
|
+
generationId: typeof observed.generationId === 'string' ? observed.generationId : null,
|
|
438
|
+
checkedAt: typeof observed.checkedAt === 'string' ? observed.checkedAt : null,
|
|
439
|
+
}
|
|
440
|
+
}
|
|
408
441
|
this.view = view
|
|
409
442
|
this.statusBarEl.setText(labelOf(view))
|
|
410
443
|
this.statusBarEl.setAttr('aria-label', `${labelOf(view)} (${readable(view.reason)})`)
|
|
411
444
|
this.shownState = view.state
|
|
445
|
+
// A republished channel resets the display before its first answer, but
|
|
446
|
+
// does not repeat attention notices or erase the recovery transition.
|
|
447
|
+
if (view.state === 'connecting') {
|
|
448
|
+
if (previous === null) this.noticeState = view.state
|
|
449
|
+
return
|
|
450
|
+
}
|
|
451
|
+
this.noticeState = view.state
|
|
412
452
|
if (previous === view.state || previous === null) return
|
|
413
453
|
if (Object.prototype.hasOwnProperty.call(ATTENTION, view.state)) new obsidian.Notice(ATTENTION[view.state](view))
|
|
414
454
|
else if (view.state === 'current' && Object.prototype.hasOwnProperty.call(ATTENTION, previous)) new obsidian.Notice('Atelier: this view is current again.')
|
|
@@ -419,13 +459,22 @@ class AtelierProjectionPlugin extends obsidian.Plugin {
|
|
|
419
459
|
const view = isPlainObject(report.view) ? report.view : {}
|
|
420
460
|
const count = (value) => (Number.isInteger(value) ? String(value) : 'unknown')
|
|
421
461
|
const channel = this.channel
|
|
462
|
+
const last = this.lastObservedStatus?.channel === channel ? this.lastObservedStatus : null
|
|
463
|
+
const missingGeneration = this.view.state === 'unreachable' ? 'unknown' : 'none yet'
|
|
464
|
+
const missingCheck = this.view.state === 'unreachable' ? 'unknown' : 'not yet'
|
|
422
465
|
return [
|
|
423
466
|
['View', channel ? channel.scopeId : 'not set up'],
|
|
424
467
|
['State', labelOf(this.view).replace(/^Atelier: /, '')],
|
|
425
468
|
['Reason', readable(this.view.reason)],
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
469
|
+
...(this.view.state !== 'unreachable' ? [] : last ? [
|
|
470
|
+
['Last observed state', readable(last.state)],
|
|
471
|
+
['Last observed reason', readable(last.reason)],
|
|
472
|
+
['Last observed generation', last.generationId || 'none yet'],
|
|
473
|
+
['Last observed check', last.checkedAt || 'not yet'],
|
|
474
|
+
] : [['Last observed status', 'not available']]),
|
|
475
|
+
['Generation', typeof view.generationId === 'string' ? view.generationId : missingGeneration],
|
|
476
|
+
['Prepared generation', typeof view.preparedGenerationId === 'string' ? view.preparedGenerationId : missingGeneration],
|
|
477
|
+
['Checked at', typeof view.checkedAt === 'string' ? view.checkedAt : missingCheck],
|
|
429
478
|
['Held edits', count(view.heldNoteCount)],
|
|
430
479
|
['Retained edits', count(view.retainedEdits)],
|
|
431
480
|
['Pending edits', count(isPlainObject(report.pendingEdits) ? report.pendingEdits.open : null)],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "atelier-projection",
|
|
3
3
|
"name": "Atelier",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.2",
|
|
5
5
|
"minAppVersion": "1.13.7",
|
|
6
6
|
"description": "Shows whether this Atelier view is current and tells Atelier's maintenance service that the vault is open. Shipped by Atelier into the vaults it manages.",
|
|
7
7
|
"author": "MNSTRY",
|