@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.
Files changed (58) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/README.md +22 -5
  3. package/docs/blocks/capability-stewardship.md +1 -1
  4. package/docs/blocks/learning-harnesses.md +1 -1
  5. package/docs/capability-stewardship.md +2 -4
  6. package/docs/ingestion.md +3 -3
  7. package/docs/inquiry-harnesses.md +2 -3
  8. package/docs/install.md +3 -3
  9. package/docs/learning-harnesses.md +2 -3
  10. package/docs/local-services.md +42 -3
  11. package/docs/obsidian-contract.md +23 -0
  12. package/docs/obsidian-plugin.md +214 -3
  13. package/docs/obsidian.md +271 -11
  14. package/docs/personal-workspace.md +13 -4
  15. package/docs/release-engineering.md +2 -1
  16. package/docs/repo-boundary-guard.md +26 -3
  17. package/docs/semantic-operation.md +4 -3
  18. package/docs/upgrade.md +174 -2
  19. package/fixtures/templates/local-library.v1.json +2 -1
  20. package/package.json +2 -1
  21. package/plugins/obsidian/main.js +57 -8
  22. package/plugins/obsidian/manifest.json +1 -1
  23. package/src/boundary/content-rules.mjs +532 -67
  24. package/src/boundary/policy.mjs +135 -56
  25. package/src/cli/run.mjs +3 -2
  26. package/src/commands/obsidian.mjs +23 -7
  27. package/src/graph/graph.mjs +13 -5
  28. package/src/graph/knowledge-graph.mjs +19 -1
  29. package/src/ingestion/optional/graphify/graphify_supplier.py +5 -0
  30. package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
  31. package/src/ingestion/optional/graphify/native-pins.json +306 -0
  32. package/src/knowledge-health/rule-identity.mjs +14 -9
  33. package/src/personal-workspace/index.mjs +1 -1
  34. package/src/personal-workspace/selection.mjs +17 -1
  35. package/src/project/path-match.mjs +2 -1
  36. package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
  37. package/src/projection/obsidian/personal-workspace.mjs +87 -4
  38. package/src/projection/obsidian/proposals/adapter.mjs +46 -23
  39. package/src/projection/obsidian/proposals/observation.mjs +40 -2
  40. package/src/projection/obsidian/publication/publisher.mjs +72 -5
  41. package/src/projection/project.mjs +3 -2
  42. package/src/runtime/obsidian/engine.mjs +293 -55
  43. package/src/runtime/obsidian/lifecycle.mjs +17 -12
  44. package/src/runtime/obsidian/login-item.mjs +4 -1
  45. package/src/runtime/obsidian/observation.mjs +36 -4
  46. package/src/runtime/obsidian/opening.mjs +3 -2
  47. package/src/runtime/obsidian/personal-selection.mjs +125 -0
  48. package/src/runtime/obsidian/pipeline.mjs +44 -3
  49. package/src/runtime/obsidian/private-lock.mjs +39 -4
  50. package/src/runtime/obsidian/service-main.mjs +58 -12
  51. package/src/runtime/obsidian/service.mjs +66 -6
  52. package/src/runtime/obsidian/view-permission.mjs +334 -0
  53. package/src/ui/control-system.mjs +201 -0
  54. package/src/ui/html-primitives.mjs +3 -0
  55. package/src/upgrade/closure-diagnostics.mjs +135 -0
  56. package/src/upgrade/closure.mjs +649 -0
  57. package/src/upgrade/upgrade.mjs +19 -7
  58. package/templates/external-project-workspace/pack-lifecycle.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,238 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0-alpha.14
4
+
5
+ - The Obsidian maintenance service's log no longer gets a `status-project-loaded` line for every failure and recovery, or every slow load, of the plugin's status read while its loads come less than a minute apart and fail, or are slow, every other load or every third. Before, any load that answered ended a row of failures, and any load in time a row of slow loads. Now a row of failures ends only at a load that answers a minute or more after its last failure, with a line that names how many failures it held, and a row of slow loads only at a load in time a minute or more after the last slow one. Such a loader writes one row, less and less often. While a project is kept, it is loaded again every two and a half minutes, so there a row of failures ends at the next reload that answers. A loader that answers at least one reload between two failures still writes each failure and the load that ends its row, and one that fails at every reload and answers only at its retry keeps one row. Each slow load is still written there. A load at every request is now written too, when each load names the files it started over. That happens while a file that decides the views vouches for nothing, or while loads never agree, and before it showed only as a growing count. A load after which nothing is kept, though it named the files it started over, is written from the second in a row, on the same doubling row (`notKept`, with `vouched`); a load that named other files, as after a new confirmed selection, neither counts nor ends that row. `statusLoads` in the status document counts these loads as `notKept`, one at a start and one after each change on a healthy service.
6
+ - An Obsidian maintenance service started by a login item asks its project loader once (under `--personal-home`, its locator), and now hands its first answer on faithfully. A thenable that is no promise is adopted once as a native promise that settles as it does, so its `then` is called once in all. Before, it was called twice: once where the process let the answer go, and again at the service's own start. A promise whose `constructor` reads as this realm's `Promise` is let go without calling a `then` of its own, which may throw; any other, as a rule another realm's promise or a subclass's, is adopted as any other thenable is. A service started any other way that is handed a promise with a `then` of its own that throws now refuses it typed as well. Before, that start ended untyped. Anything thrown, `undefined` included, and a project whose `then` cannot be read, ends the start as a failure of the loader does. Before, `undefined` was taken as an answer of nothing: the start went on to resolve a workspace from it, and for a locator it was refused as `service-locator-invalid`. An unreadable `then` ended the process with an error the service never logged. The service also logs what was thrown when it has no prototype, or a `code` or `message` that cannot be read, where before that logging itself threw. A start ended by an error without a code is still a crash that the service manager restarts.
7
+ - A view the Obsidian maintenance service refuses for a file-system error is now named in its private log. The view's freshness names only the refusal's code, and such a tick is no failure, so nothing recorded the error before. Examples are `vault-location-unusable` for a vault folder that could not be allocated or placed, `mixed-read` for a configuration file that could not be read again during the tick or a source removed while it was read, and `staging-failed` for candidates that could not be staged. The service now writes a `view-refused` line for each such view whose refusal carries a file-system error's code, at most 64 a tick. The line names the view, the refusal's code and the error's code (`EACCES`, `EIO`, `ENOENT`, `ENOSPC`), and `cleanupCause` when a cleanup after it failed too. Only codes shaped as a file-system error's are written, never a message. A view the next tick names for the same reason is not written again, so a vault folder that stays unwritable, tried at every tick, is written once. The engine's tick report carries the same entries as `refusalCauses`, only when there are any.
8
+ - A failure while the publisher cleans up the candidates of a staging that did not complete no longer replaces what ended the staging. Before, a removal or a read that failed (`EBUSY`, `EPERM`, `EIO`, a volume gone) was thrown in place of the refusal or stop, and the tick failed without a typed reason. Now each candidate is cleaned up on its own, so one that cannot be removed leaves the others removed. What ended the staging is handed on unchanged: a caller's stop, a refusal such as `vault-root-moved`, or `staging-failed`. The code of the first such failure is added to a copy of its detail as `cleanupCause`. A candidate that could not be removed stays where it was, as before. That is in staging, or at its exchange path once the publication's journal is written, where the restart recovery of the next publication removes it.
9
+ - A selected source removed while the Obsidian maintenance service prepares or publishes a view, after the tick observed it and before the preparation, or the look again at the sources before the commit, read it, now refuses that view as `mixed-read`, as a source removed during the graph build does: no generation is committed for the view, the tick goes on to its other views, and the next tick reads everything again and publishes without the source. A generation already committed and prepared again is not reported current. Before, the tick failed without a typed reason: the view read `publisher-error`, the views after it were not prepared in that tick, and the service read degraded and recorded the error until the next tick. The rule is the build's: only a source the tick observed and that is gone when it is read (or its folder) is refused this way, and a file that cannot be read for another reason, or a path still there, still stops the tick. The engine primitive that decides it is now named `sourceRemovedSinceObserved`.
10
+ - A running Obsidian maintenance service whose loaded project is pinned to a selection record below the highest record any part of the service has read the selection history ending at loads the project again at its next tick, with no read of its own to trigger it. A confirmation read by another part of the service (the plugin's status read) and removed before the engine looked therefore refuses `selection-history-shortened` at that tick. Before, the engine went on publishing under its record until its next recheck, at the latest the next full reconciliation. Views that a change stops one after another have the history's end read once, not once a view, and it is read also when a file that decides the views could not be read.
11
+ - A source removed while the Obsidian maintenance service builds the canonical graph, after the build listed it and before it read it, now refuses the views of that tick as `mixed-read`, as a source changed during the build is: nothing is prepared from the build, and the next tick reads everything again and publishes without it. Before, the tick failed without a typed reason, and the service read degraded and recorded the error until the next tick. Only a source this tick observed, gone when the build reads it (or its folder), is refused this way. Anything else still stops the tick: a file that cannot be read for another reason, a path still there, one the walk over the sources listed and could not read, or one it never observed, so a path that can never be read fails every tick as before.
12
+ - AP-03 cleanup joins owned child handles even when the runtime status record is missing or malformed, and retains disposable roots when cleanup cannot be proven.
13
+ - The Obsidian maintenance service goes on answering health and status while a tick observes and routes pending edits. Observation gives the event loop a turn between two edits, and proposal routing gives it one before each repository and between two operations. Each edit and each operation is still handled in one stretch, and what is observed, recorded and proposed is unchanged. Before, a tick with a burst of edits held the listener for the whole observation. An observation asked for while another is under way now waits for it.
14
+ - The event-loop benchmark (`scripts/obsidian/benchmark-event-loop.mjs`) waits for the health request in flight at the end of the first tick, and of a transition, before it judges the window. A stretch at the end that holds the listener is then measured by that request's latency, under the same five-second rule as any other, instead of being read as a gap in the sampling; the 250 ms edge rule is about whether the sampling reached the edge, as it already was at the start of a tick. A request in flight at the end that is never answered now fails the run, where before it was left out. The rules a request is judged by are unchanged. The result's `health` and `transitionHealth` say whether such a request was waited for (`awaitedProbeInFlightAtEnd`; `false` also when it was answered at once). `--timeout-ms` may now be up to four hours, so a first publication of ten thousand notes in six views can be run; the window is judged without a limit on how many requests it holds.
15
+ - A running Obsidian maintenance service no longer follows a selection history back when it is cut short. It notes the highest record number at every read of the history's end, and a history that ends earlier refuses `selection-history-shortened`: nothing is loaded from it, a project already loaded is refused at its next recheck, the views say why and the vault keeps what it last published, until the removed records are back or the history is as long again. Before, the service loaded whatever record the history then ended with, so a history cut after a restore nobody confirmed published that restore. The number is kept in the process only: a service started after the cut, by the person or at login, after an upgrade or a failure, reads the history as it then stands and follows what it ends with, as each command run once does. Keeping it across a start is not part of this change.
16
+ - The Obsidian maintenance service goes on answering health and status while a published view is read back. The read-back after a commit gives the event loop a turn between notes, as preparation and publication do. It ran through before: for one view of 10,000 notes it held the listener for 1.9 s on a quiet host and 16.8 s on a loaded one, long enough for three health requests in a row to time out. Each note of the committed generation is still read once, and a view is reported current only after its read-back. `reconcileCooperatively` beside `reconcile` in `src/runtime/obsidian/observation.mjs` is the walk that waits.
17
+ - The event-loop benchmark (`scripts/obsidian/benchmark-event-loop.mjs`) has two more scenarios, which ask the service for one more tick after the first publication, with nothing changed, and judge that tick: by what it did, by how long it held the event loop, and by the health requests across it. `--scenario=next-tick` keeps the watcher a service runs with, so that tick is told of the notes the publication wrote, as the tick after a publication is; when the publication took five minutes or more it is also a full reconciliation, as it is in a service. `--scenario=full-reconciliation` makes it a full reconciliation whatever the publication took. The result records the tick, whether it was full, its longest event-loop delay, and what the watcher had reported before it. Until now no stage ran a tick that a watcher had told of anything, and the one full reconciliation a stage ran (in `observe-16`) was over the small corpus that stage is run with.
18
+ - The Obsidian maintenance service goes on answering health and status while a tick looks at the vault and at the sources. Before a view is prepared, the walk over every view's notes and the walk over the sources give the event loop a turn between files, as the read-back does. They ran through before: on one view of 10,000 notes the tick after a publication (told by the watcher of every note it wrote, and a full reconciliation as well after a long publication) held the listener for 5.5 s on a loaded host, and a health request timed out; at every full reconciliation, every five minutes, each walk reads every file. The pending edits are still read, compared and written in one stretch, after the walk over the vault. A file changed after its turn in a walk is seen by the next tick, by its stat hint or the watcher, or at the latest by the next full reconciliation. The tick reports two more phases to a caller that asks for them, `vault-observation` and `late-writer-recheck`. In the event-loop benchmark, a second-tick stage's ending is one tested function, and its delay test samples the stretch it means to leave out.
19
+ - A note removed from a vault is no longer lost when the tick that saw it fails without a typed reason (a source or a note it cannot read, a full disk while an edit is kept). The removal is a change once, and such a tick had recorded it without acting on it, so the view stayed `current` with the note gone, through every later full reconciliation. The tick after a failure without a typed reason now starts as the first tick of a process does: it loads the project again and looks at every view, and the view reads `vault-note-missing`. A source changed in such a tick, or a view added to the configuration in a tick whose project loader failed, is published by the next one for the same reason. The phase `late-writer-recheck` is reported when the look again at displaced files fails too.
20
+ - An Obsidian maintenance service whose engine lock could not be given back at the end of a tick (its released ticket could not be written: a full disk, an I/O error) goes on maintaining once the write succeeds. Before, every later tick found the service's own unreleased ticket and answered `busy`, which the service counts as a success: health read healthy, the views stayed `current`, and nothing was maintained until the service was started again. The tick whose release failed still fails; the lock stays held, and the next tick gives it back before it takes it again. A release of a private generation lock that throws now settles nothing, so calling it again tries again.
21
+ - A private generation lock whose acquisition fails after its ticket was put in place (the directory sync or the read-back of the ticket, or the listing that confirms it) removes that ticket again, while it still holds its own bytes, before the failure is reported; a removal that fails too is made at that process's next acquisition in the same directory. Before, the ticket stayed on disk unreleased and nobody held it to release it: every later acquisition by the same process found it held by that process, so an Obsidian maintenance service answered `busy` at every tick until it was started again, with health reading healthy. The engine lock, the start lock of `service start` and the proposal adapter's lock all take this path.
22
+ - The Obsidian maintenance service goes on answering health and status while a tick builds the canonical graph. The census (each Markdown source read, hashed and parsed, or reused from the graph file cache) gives the event loop a turn between sources; a first tick reads every source, and on a heavily loaded host the build for one view of 10,000 invented notes held the listener for 8 s in one stretch. The settings that select the views are checked at each wait, as during a preparation. `buildKnowledgeGraphSteps` and `buildCanonicalGraphSteps` are the builds as steps; `buildKnowledgeGraph` and `buildCanonicalGraph` run them through, unchanged for their callers. A project bound to a personal workspace is built by its own route in one stretch, as before, and the other sources (HTML files, the sidecars of PDF and Word documents) are read without a turn between them. Walking the directories, resolving links and assembling the graph still run through. `createProductionSeams()` has one more member, `buildGraphCooperatively`, which the engine uses for every project not bound to a personal workspace; a caller that replaces `buildGraph` and leaves `buildGraphCooperatively` as it ships builds every graph through its own `buildGraph`, as a replaced `prepareView` already prepares every view. Knowledge Health pins the bytes of the two graph files, so its rule identity is regenerated for them: the `check` each assessment carries names a new `version`, `sourceTree` and `graphBlob`, and the rule it applies is unchanged.
23
+ - Pages that still called shipped commands unreleased now say they shipped in 0.2.0-alpha.13: `atelier knowledge semantic`, `atelier ingest` (with the `./ingestion` export), `atelier inquiry`, `atelier harness` and `atelier capability`. Each was checked against the published 0.2.0-alpha.12 and 0.2.0-alpha.13 packages.
24
+ - A staged or pushed deletion of a tracked binary file no longer blocks the boundary check. A deleted path has nothing to read at the revision being scanned, yet the check tried to read it and reported `content-scan-incomplete` ("binary content could not be read within the 8388608-byte per-file limit"), which refused the commit or the push. A binary added or changed in the same change is still read and scanned, and only the extended header of the diff can say a path was deleted, so a content line cannot skip a file. A binary rename that git shows as a deletion and an addition is unblocked the same way.
25
+ - The optional Graphify supplier loads again. `graphify_supplier.py` reads `native-pins.json` and `native-closure-pins.json` beside it when it is imported, and neither file was in the package, so importing it (and the JSON bridge, query and composition receivers that import it) failed with a missing file in 0.2.0-alpha.13. Both pin files ship now, admitted by the release tarball audit, pinning Graphify 0.9.72 at upstream commit `1cd9a36c`: 30 operations and 67 closure modules. Of the 30, `extract-semantic` is pinned but cannot run, because its `graphify.llm` module is outside the admitted closure, so 29 are usable. `extract-structure` now refuses `parallel: true` with `NATIVE_WORKERS_UNATTESTED`: parallel extraction starts worker processes that import Graphify outside the pinned source loader, so only in-process extraction is admitted.
26
+ - `atelier upgrade closure` says what to do in two more cases and refuses one more flag. A refused linked `--npm-root` prints the directory's `realPath`, and the docs say why macOS `/tmp` and `$TMPDIR` are refused until it is given. A tarball URL with no readable host no longer suggests `--registry-host`, which cannot clear it. The `prefix-mismatch` message no longer claims npm would use another root, since the plan refuses any parent that declares workspaces. `--__proto__`, which the shared argument parser dropped, is refused as an unsupported flag. The upgrade guide now matches the code on an empty `--npm-root=` (`npm-root-required`), on `http` and `https` (counted separately in `registrySchemes`) and on when Atelier's own `package.json` is read.
27
+ - The practice-consumer reconsideration tool (`scripts/practice-consumer/reconsider.mjs measure`) can keep the full context of one measurement with `--capture NEWFILE`: the records and definition the evaluator was given, each evaluation's input and outcome as received, the citations that were not evaluated and why, and the decisions with no citation. It is written once, to a file that must not exist yet and must not be the measurements file under another name (checked by real path before measuring and by the file itself after writing), only when the measurement line can be appended, and before that line, so a refused capture appends nothing and the run removes the capture it wrote; only a run interrupted between writing the capture and appending its line can leave one standing. Without `--capture` nothing changes.
28
+ - The boundary content scan reads every changed file under its own name. Its diffs now pin their format (`--no-ext-diff --no-textconv --no-color --src-prefix=a/ --dst-prefix=b/ --no-relative --inter-hunk-context=0 --submodule=short --full-index`), so `diff.noprefix`, `diff.mnemonicPrefix`, changed prefixes, forced colour, an external diff or a textconv driver no longer leave a staged or pushed change unscanned, and fused hunks or submodule summaries do not stop clean work. Quoted paths (non-ASCII, quotes, tabs) and paths containing ` b/` are read correctly instead of being skipped or read as another file; a name holding a backslash is reported as it is, never inherits a sibling path's exception, and is in scope of every path-scoped rule. Changed binaries are read by object id, so a name shaped like an index stage (`0:name`) is read as itself. Added lines are taken from each hunk by its own line counts, so an added line that itself begins with `+`, such as a Markdown bullet, is scanned. Text that the repository's attributes declare binary (UTF-8 without a NUL byte in its first 8 KiB) was not scanned at all; it is now judged by its added lines like any other text, with renames and copies paired as in the main diff, and other content declared binary is scanned as a binary, as before. A changed file the scan cannot read from the diff is reported as `content-scan-incomplete` and blocks, where before it was skipped; a Git setting not pinned here can therefore stop a commit but cannot hide content.
29
+ - The boundary pre-commit check judges what is being committed. `git commit -a` and `git commit <paths>` hand the hook a temporary index, and a commit from a linked worktree its own index; the check dropped it with the rest of the Git environment, read the configured checkout's index and saw nothing staged. It now reads the index and work tree Git names, for the managed repository being committed, found by Git even from a hook chained into a subdirectory, and blocks when no single declared repository is being committed (`pre-commit-repo-unresolved`), as the push check does. Staged paths are listed NUL-separated, so a quoted name (non-ASCII, quotes) is matched against forbidden-path patterns, and a repository whose staged paths cannot be listed blocks. The semantic-field review reads the staged diff with the same pinned format and parser as the content scan, reads a document Git would call binary as text, blocks on a diff it cannot read, and takes only an added review marker as approval: a removed marker, or one in a file name, approves nothing; `atelier upgrade` applies the same marker rule. A forbidden-path pattern's `**` now also matches across a newline in a name.
30
+ - In the boundary content scan, a file that takes the name of a directory, or moves into a directory of its own name, in text that Git reports as binary, is judged as the main diff pairs it instead of blocking the commit. When such text is still unreadable the message says Git reports it as binary, which also covers text replacing binary content.
31
+ - The boundary push check judges the commits a push carries that the remote's tracking refs do not hold, each against its parent, binaries included, so a key added in one commit and removed in the next is no longer published unscanned; such a finding names its commit, and a key that reaches the pushed tip is reported once, listing the commits that carry it. A merge is judged only for what is new against all its parents, so a pull merge into the branch you push is not refused for what the remote already holds. The pre-push hook now passes the remote Git names (a configured remote, or a URL matched to one); reinstall hooks (`atelier boundary install-hooks`) so history held only by another remote is judged on its way to a new one. A push carrying more than 1000 such commits or more than the evidence budget is refused with `content-scan-incomplete`; push it in smaller parts. The content scan's diffs omit a deleted file's removed lines, which it never judges (the semantic-field review still reads them), so deleting a large file no longer exhausts the output limit. A new branch in a SHA-256 repository is diffed against that format's empty tree instead of failing.
32
+
33
+ - `atelier upgrade closure --npm-root ABSOLUTE_NPM_ROOT` plans a check of a staged
34
+ candidate's own dependency closure, and `planConsumerClosure` is exported from
35
+ `@mnstry/atelier/upgrade`. The plan reads the candidate's `package.json` and
36
+ `package-lock.json`, the installed manifest of every lockfile copy of the
37
+ selected package, `package.json` in each directory above the npm root (to
38
+ refuse a parent that declares workspaces) and, for the default package name,
39
+ Atelier's own `package.json`. It runs no npm command, uses no network, reads
40
+ no npm configuration and writes nothing. It reports the lockfile's sources,
41
+ registry hosts and schemes, and platform exclusions. It checks the selected
42
+ package's exact `overrides` against every installed copy, only after binding
43
+ each installed manifest to the name and version of its lockfile entry, and
44
+ lists selector, nested and non-exact overrides, and overrides for packages a
45
+ link provides, as unchecked. The npm root must be the directory's real path.
46
+ Refusals reuse `usage`, `consumer-closure-incomplete` and
47
+ `override-not-inherited`, each with one bounded reason. Every run prints one
48
+ JSON document, including a run whose arguments are refused. A completed plan
49
+ exits 3 with `"proof": "not-run"` and a refusal exits 2; the command never
50
+ exits 0 and does not prove an offline reinstall. The consumer-closure
51
+ classifiers moved to `src/upgrade/closure-diagnostics.mjs`, and
52
+ `scripts/consumer-closure-diagnostics.mjs` re-exports them. Two of them
53
+ changed: a project folder entry in a lockfile is never counted as an
54
+ installed copy, and a lockfile entry that is not an object is reported as
55
+ missing by `capturedClosure` instead of raising a `TypeError`.
56
+
57
+ - Add a tenant-neutral HTML control system at `@mnstry/atelier/ui`. The shared
58
+ geometry, variants, focus treatment, motion, action-group layouts, and safe
59
+ renderers are used by Atelier's generated project UI and are available to
60
+ downstream publication kits without importing publication semantics into
61
+ Atelier.
62
+ - The Obsidian service test suites reserve the loopback ports they choose, so
63
+ another test cannot take a port between its selection and its use.
64
+ - The Obsidian plugin (1.1.2) keeps the last authenticated status in view
65
+ through a transport interruption. After a connection refusal or timeout on
66
+ the same channel, the status window shows the last observed state, reason,
67
+ generation and check under "Last observed" labels. Those rows are historical
68
+ and never establish current freshness, service health or permission. The
69
+ history stays in memory and is cleared on a channel change, an
70
+ authentication refusal, an untrusted or malformed answer, a report for
71
+ another scope, or unload. A republish that names a different channel resets
72
+ the display to `connecting` without repeating an unchanged notice, and the
73
+ plugin opens no channel before startup admits it or after unload.
74
+ - The Obsidian maintenance service tells a plugin a view's stored freshness
75
+ and pending-edit count only while the project allows that view at the moment
76
+ of the request: its Obsidian settings are enabled and declare the view, and
77
+ its workspace pointer still names the workspace the service runs for.
78
+ Before, a view that was turned off or taken out of the settings was still
79
+ told what the service had stored, until the next tick and, for its
80
+ generations and counts, after it. The answer is now sealed as before with
81
+ `view` and `pendingEdits` null, so the plugin shows `Atelier: stale`, keeps
82
+ its session and asks again; anything that cannot be established (settings
83
+ that refuse, a project that does not load) is answered the same way. The
84
+ project is loaded again only when a file that decides it changed, compared
85
+ by content at every request. A loader that answers a promise is never
86
+ waited for: its view is told nothing until the load has answered.
87
+ - The real-app acceptance run of the small-fixture desktop procedure
88
+ (`scripts/obsidian/desktop-receipts.mjs`, maintainer tooling that is not
89
+ shipped) ends the processes it started, removes the temporary directories it
90
+ created, and bounds the receipt and console output it keeps. It signals only
91
+ a process it spawned or a recorded child of one, and removes a directory only
92
+ while the path still names the one it created; otherwise it keeps the
93
+ directories and names them.
94
+ - A project configuration file that is a link (one kept in a dotfiles
95
+ repository, say) no longer makes the Obsidian maintenance service load the
96
+ project, and run Git, at every status request a plugin makes. The service
97
+ compares what the loader reads through the link: the file it leads to and
98
+ that file's bytes, so a link pointed elsewhere is a change. A folder in a
99
+ file's place, or a link that leads nowhere or to a folder, still vouches for
100
+ nothing. The project the service keeps for those requests is taken only from
101
+ two loads in a row that agree over the same bytes, is loaded again every two
102
+ and a half minutes and never used past five. A loader that answers a promise
103
+ is given up after two and a half minutes, and a load that fails is asked for
104
+ again after two seconds, then four, up to thirty, unless a file changed.
105
+ - The Obsidian maintenance service refuses to start on a project loader that
106
+ answers a promise (or any other thenable), with
107
+ `service-loader-not-synchronous`, before it reads anything from it, listens
108
+ or records anything. Before, it handed the promise to its workspace
109
+ resolution, which then read the workspace pointer of the working directory
110
+ and protected no repository. For the status read of a plugin: a load whose
111
+ answer comes after its deadline is given up even when no request saw the
112
+ deadline pass; a link is resolved as the system resolves it, a `..` after a
113
+ link included, and a link to a folder above a configuration file pointed
114
+ elsewhere is a change; two loads agree when what the decision reads of the
115
+ project is the same, so a field that differs between loads (a time, a
116
+ binding under a symbol key) no longer stops a project from being kept; and
117
+ a load that fails, is given up or holds the event loop is logged with how
118
+ long it took.
119
+ - The Obsidian maintenance service stays responsive while it prepares and
120
+ publishes a view. The engine prepares each view cooperatively and the
121
+ publisher yields between complete units, so the loopback listener answers
122
+ health and status while notes are emitted, staged and published, instead of
123
+ being `busy` for the length of a large first publication. `open` and
124
+ `service stop` therefore reach a service in the middle of a publication:
125
+ `open` waits for a tick that starts after the one in flight, and `service
126
+ stop` ends the service after the tick in flight or its shutdown grace,
127
+ leaving a publication cut short to the publisher's restart recovery.
128
+ Walking the directories, resolving links and assembling the canonical
129
+ graph, capturing the source snapshot, the
130
+ publisher's restart recovery, validating a prepared view and planning its
131
+ units, the exchange probe, writing and syncing the journal before the
132
+ candidates move and the look again at displaced files after the units
133
+ still run without yielding. So did reading the sources into the canonical
134
+ graph, observing the source and vault files and the read-back after a
135
+ commit, until later changes made them wait between files (for the graph,
136
+ between Markdown sources, outside a personal workspace).
137
+ `prepareView` stays synchronous
138
+ and returns the same bytes. Across each wait of preparation and publication
139
+ the configuration files, the machine settings and the eligibility revision
140
+ are checked again, and after
141
+ a view is prepared and before its generation commits every selected source
142
+ is checked as observation checks it: by its stat hint, and by digest where
143
+ the hint moved. A change during the tick refuses the generation as
144
+ `mixed-read`; neither that tick nor a later restart recovery commits it,
145
+ and the next tick prepares the view again. A source that drifts under an
146
+ unchanged stat hint is still seen by the next full reconciliation, and a
147
+ note reused from the preparation cache is still not read. A cancelled
148
+ preparation leaves the preparation cache as it was, and publication keeps
149
+ its locks, journal and conditional writes.
150
+ - Started by a login item, the Obsidian maintenance service asks its project
151
+ loader once, and a loader that answers a promise there is refused and
152
+ recorded as at any other start, ending with 0. Before, the promise reached
153
+ the workspace resolution, and a refused one ended the process with an error
154
+ that the service manager answered by starting it again. The loads behind a
155
+ plugin's status requests are counted in the service's status document
156
+ (`statusLoads`), and the service log gets a `status-project-loaded` line
157
+ only for a load that failed or was given up (the first in a row, then when
158
+ the row is 2, 4, 8 and so on long), the first that answered after a failure,
159
+ and one that held the event loop for a second or more. A regular
160
+ configuration file the system resolves no path for (some Windows volumes)
161
+ is compared by its own path and bytes, instead of the project being loaded
162
+ at every request.
163
+ - A publication of the Obsidian maintenance service that is stopped or refused
164
+ on the way is recorded and cleaned up as what it was. A caller's stop after
165
+ the journal was opened (a setting that changed during the tick,
166
+ `mixed-read`, or a cancellation) is recorded in the journal's last entry
167
+ under that stop's own code instead of `publisher-error`; restart recovery
168
+ reads such a journal as before. A configuration file that cannot be read
169
+ again at a wait (no permission, an I/O error, no descriptor left) refuses
170
+ that view as `mixed-read` and has the next tick read everything again;
171
+ before, the raw error ended the tick, and the views after that one were not
172
+ prepared. A refusal raised while candidates are being staged (the vault's
173
+ folder replaced or gone) is still reported under its own code, now after
174
+ the candidates it staged are removed; before, those staged ahead of the
175
+ journal stayed in private staging, where restart recovery never looks. And
176
+ the service reads an explicit `prepareViewCooperatively: null` among its
177
+ engine seams as the engine does: there is no cooperative preparation, so a
178
+ replaced `prepareView` prepares the view.
179
+ - The desktop acceptance procedures (`scripts/obsidian/desktop-receipts.mjs`,
180
+ maintainer tooling that is not shipped) no longer signal the isolated app
181
+ by its process number outside the small-fixture run: quitting signals it
182
+ through the handle spawn returned and waits on that handle, a launch that
183
+ gives up signals the app's process group only while that handle still
184
+ holds the app, and a launch under the user's own HOME is refused before the
185
+ stale command-line socket there is removed. In the small-fixture run, a
186
+ process first recorded from a process table passes lineage on only from the
187
+ next read; a process the table shows, unrecorded, under a parent that
188
+ exited or changed during the read is remembered until it is seen gone, and
189
+ the directories are kept while it is there; and the app log copy's credit
190
+ is charged back when a directory is kept after all, with the excess over
191
+ the output bound's allowance reported.
192
+ - `atelier obsidian`, the Obsidian maintenance service and its login item take
193
+ `--personal-home DIR` instead of `--project`, and then follow the generation
194
+ last confirmed in that private home of a personal workspace. The head of the
195
+ selection history is read, that generation is loaded (composed in a worker
196
+ for the service), and the head is read again; a head that moved refuses
197
+ `personal-selection-changed`. Nothing confirmed publishes nothing, each with
198
+ its own code (`nothing-selected`, `stale-generation`,
199
+ `selection-history-corrupt`, `personal-home-unavailable`, or the module's own
200
+ code for a home that is gone), and nothing falls back to another project.
201
+ The service locates its workspace from the home alone through a new
202
+ `locateProject` option, so it starts whatever the selection; a service given
203
+ only a loader that answers a promise is still refused at start. The engine
204
+ observes the selection's record and the next one, so a confirmation or a
205
+ history cut short is loaded at the next tick, and reads the history again at
206
+ every full reconciliation. `status` reports the confirmed generation, and
207
+ nothing here confirms one. Who may see a bound vault is not decided: it stays
208
+ the machine's audience setting, none by default. `readPersonalSelectionHead`
209
+ is added to `@mnstry/atelier/personal-workspace`, and the selection history is
210
+ read without waiting on a record that is not a regular file. Without
211
+ `--personal-home` nothing changes.
212
+ - The desktop acceptance procedures (`scripts/obsidian/desktop-receipts.mjs`,
213
+ maintainer tooling that is not shipped) interrupt AP-03's service only
214
+ after a healthy status, and only through the handle of the service process
215
+ the run started; any other number is refused and the step fails. A launch
216
+ is refused unless the instance's HOME lies inside a layout root the run
217
+ made and is not the user's own home, however it is reached. In the
218
+ small-fixture run, cleanup reads the process table until a read records
219
+ nothing new before its first signal, and the run records how many
220
+ processes one command-line call becomes. The event-loop benchmark
221
+ (`scripts/obsidian/benchmark-event-loop.mjs`) signals its child's process
222
+ group only while it still holds the child.
223
+
224
+ - AP-03 reports a refused interruption without losing its failure evidence.
225
+ Separately, small-fixture cleanup remembers a child observed under a run-owned
226
+ process that exited or changed during the process-table read, then remembers
227
+ descendants of those children. It retains its temporary roots while those
228
+ remembered processes remain. It also reserves estimated time for SIGTERM
229
+ grace after reading the process tree.
230
+ - AP-03 test cleanup runs before temporary-workspace removal. It joins children
231
+ through retained handles even when the service record is missing or malformed
232
+ or graceful stop fails. Unheld launcher services use the existing forced-stop
233
+ fallback, which rechecks healthy runtime identity before signalling the PID.
234
+ A held child that does not exit within the cleanup bound fails the test.
235
+
3
236
  ## 0.2.0-alpha.13
4
237
 
5
238
  - Add the public Knowledge Health workshop on the existing local Knowledge
package/README.md CHANGED
@@ -94,7 +94,7 @@ governed projections
94
94
  You can see the complete loop in a disposable sample workspace:
95
95
 
96
96
  ```bash
97
- npm install --save-dev @mnstry/atelier@0.2.0-alpha.13
97
+ npm install --save-dev @mnstry/atelier@0.2.0-alpha.14
98
98
  npx mnstry-atelier init --fixture=sample-workspace --target ./sample
99
99
  npx mnstry-atelier graph --project ./sample/atelier.project.json
100
100
  npx mnstry-atelier project --project ./sample/atelier.project.json
@@ -276,6 +276,23 @@ if (graph.errors.length > 0) throw new Error(graph.errors.join('\n'))
276
276
  console.log(graph.nodes)
277
277
  ```
278
278
 
279
+ Interfaces that need the same control grammar as Atelier's generated tooling
280
+ can import the additive UI subpath. The stylesheet contains no brand copy or
281
+ publication semantics; map its `--system-control-*` properties to the
282
+ consumer's own theme:
283
+
284
+ ```js
285
+ import {
286
+ atelierControlStyles,
287
+ renderActionGroup,
288
+ } from '@mnstry/atelier/ui'
289
+
290
+ const navigation = renderActionGroup([
291
+ { label: 'Library', href: '/library/' },
292
+ { label: 'Concepts', href: '/concepts/' },
293
+ ], { label: 'Browse the workspace' })
294
+ ```
295
+
279
296
  The package exports change over the alpha series, so pin the exact prerelease
280
297
  version and treat the package export map and shipped source modules as the
281
298
  executable API reference. [Distribution contracts](./docs/distributions.md) explain
@@ -307,7 +324,7 @@ Installation, host discovery and successful exercise are separate evidence.
307
324
  Content-free observations can guide improvement, and draft graph records keep
308
325
  provenance connected to adoption and outcomes. Publisher authentication, live
309
326
  host evaluation and human acceptance require their own evidence. This source
310
- capability is intended for the next release; alpha.7 does not expose its command.
327
+ capability has shipped as the `capability` command since 0.2.0-alpha.13.
311
328
  <!-- atelier:block:capability-stewardship:end -->
312
329
 
313
330
  See [capability stewardship](docs/capability-stewardship.md) for the operating guide.
@@ -481,7 +498,7 @@ Node.js 22 (22.18.0 or later) or Node.js 24 (24.13.1 or later) is required.
481
498
  Pin the prerelease while the package remains in alpha:
482
499
 
483
500
  ```bash
484
- npm install --save-dev @mnstry/atelier@0.2.0-alpha.13
501
+ npm install --save-dev @mnstry/atelier@0.2.0-alpha.14
485
502
  ```
486
503
 
487
504
  Then choose the path that matches what you are building:
@@ -500,7 +517,7 @@ Then choose the path that matches what you are building:
500
517
 
501
518
  ## Status and command reference
502
519
 
503
- Current package: `@mnstry/atelier@0.2.0-alpha.13`.
520
+ Current package: `@mnstry/atelier@0.2.0-alpha.14`.
504
521
 
505
522
  The alpha package is usable and contract-tested, but its library API may still
506
523
  change before a stable release. Pin the exact version in production toolchains.
@@ -578,7 +595,7 @@ attempts and distinct verification gates. Its coordination proposal can be read
578
595
  by Fabric or another adapter. The shared harness contract connects these workflows
579
596
  to Discovery, Research and Skill Steward without replacing their domain records.
580
597
 
581
- These are local, provider-neutral source capabilities for the next release.
598
+ These are local, provider-neutral capabilities, shipped since 0.2.0-alpha.13.
582
599
  Handoffs preserve exact pins, and explicit source snapshots reveal corrections
583
600
  that require downstream reconsideration. Graph outputs remain draft proposals.
584
601
  Recorded acceptance is not authenticated authority, and local checks do not prove
@@ -14,4 +14,4 @@ Installation, host discovery and successful exercise are separate evidence.
14
14
  Content-free observations can guide improvement, and draft graph records keep
15
15
  provenance connected to adoption and outcomes. Publisher authentication, live
16
16
  host evaluation and human acceptance require their own evidence. This source
17
- capability is intended for the next release; alpha.7 does not expose its command.
17
+ capability has shipped as the `capability` command since 0.2.0-alpha.13.
@@ -10,7 +10,7 @@ attempts and distinct verification gates. Its coordination proposal can be read
10
10
  by Fabric or another adapter. The shared harness contract connects these workflows
11
11
  to Discovery, Research and Skill Steward without replacing their domain records.
12
12
 
13
- These are local, provider-neutral source capabilities for the next release.
13
+ These are local, provider-neutral capabilities, shipped since 0.2.0-alpha.13.
14
14
  Handoffs preserve exact pins, and explicit source snapshots reveal corrections
15
15
  that require downstream reconsideration. Graph outputs remain draft proposals.
16
16
  Recorded acceptance is not authenticated authority, and local checks do not prove
@@ -1,9 +1,7 @@
1
1
  # Capability stewardship
2
2
 
3
- Status: source implementation for the next release. The published alpha.7
4
- package does not include the `capability` command. From this checkout, use
5
- `node bin/atelier.mjs capability ...`; the examples below use the installed
6
- command spelling for a package built from this source.
3
+ Status: shipped since 0.2.0-alpha.13 as the `capability` command. The examples
4
+ below use the installed command spelling.
7
5
 
8
6
  Atelier can package a useful workflow, introduce it alongside a person's
9
7
  existing skills, and keep each repository's adoption explainable over time.
package/docs/ingestion.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Progressive local ingestion
2
2
 
3
- Status: experimental source implementation, not yet a released package feature.
3
+ Status: experimental. Shipped since 0.2.0-alpha.13 as the `atelier ingest`
4
+ command and the `@mnstry/atelier/ingestion` export.
4
5
  The local runner preserves explicitly selected files and makes bounded text
5
6
  search useful before semantic interpretation. It composes the existing
6
7
  [immutable intake](intake-and-guides.md) with a resumable plan. It never enrolls
@@ -16,8 +17,7 @@ traversal, symlinks and implicit external reads are refused. The local CLI is
16
17
  for a trusted workspace owner. Its workspace identifier is a binding against
17
18
  accidental cross-workspace reuse, not an authenticated identity or tenant ACL.
18
19
 
19
- The following examples assume the development checkout or a future package
20
- version containing this command. Released alpha.7 does not contain it.
20
+ The following examples need 0.2.0-alpha.13 or later.
21
21
 
22
22
  ```sh
23
23
  atelier ingest plan <<'JSON'
@@ -1,8 +1,7 @@
1
1
  # Discovery Harness and Research Harness
2
2
 
3
- Status: implemented locally for the next release. The published alpha.7 package
4
- does not expose `inquiry`. Use `node bin/atelier.mjs inquiry help` from this
5
- source checkout, or `atelier inquiry help` from a package built from it.
3
+ Status: shipped since 0.2.0-alpha.13. Run `atelier inquiry help` for the
4
+ commands.
6
5
 
7
6
  Discovery frames questions, compares explanations and prepares decisions.
8
7
  Research gathers and checks evidence and returns a portable bundle. People set
package/docs/install.md CHANGED
@@ -34,19 +34,19 @@ The registry is the distribution channel of record, as `docs/continuity.md`
34
34
  commits:
35
35
 
36
36
  ```bash
37
- npm install --save-dev @mnstry/atelier@0.2.0-alpha.13
37
+ npm install --save-dev @mnstry/atelier@0.2.0-alpha.14
38
38
  ```
39
39
 
40
40
  Installing from the matching Git tag resolves to the same reviewed commit:
41
41
 
42
42
  ```bash
43
- npm install --save-dev "git+https://github.com/MNSTRY/atelier.git#v0.2.0-alpha.13"
43
+ npm install --save-dev "git+https://github.com/MNSTRY/atelier.git#v0.2.0-alpha.14"
44
44
  ```
45
45
 
46
46
  Or over SSH:
47
47
 
48
48
  ```bash
49
- npm install --save-dev "git+ssh://git@github.com/MNSTRY/atelier.git#v0.2.0-alpha.13"
49
+ npm install --save-dev "git+ssh://git@github.com/MNSTRY/atelier.git#v0.2.0-alpha.14"
50
50
  ```
51
51
 
52
52
  Keep the `@mnstry/` scope — see the command-form note above for why the
@@ -1,8 +1,7 @@
1
1
  # Knowledge development and the learning cycle
2
2
 
3
- Status: implemented locally for the next release. Published alpha.7 does not
4
- contain these commands. Use `node bin/atelier.mjs harness help` in this source
5
- checkout, or `atelier harness help` from a package built from this candidate.
3
+ Status: shipped since 0.2.0-alpha.13. Run `atelier harness help` for the
4
+ commands.
6
5
 
7
6
  Knowledge Harness helps people establish, develop, use and revise what they know.
8
7
  Ingestion is one capability within that larger purpose. Sources can also originate
@@ -203,8 +203,19 @@ the child it created. If that proof never arrives it stops only that child, by
203
203
  its process handle, and reports the private operational log,
204
204
  `state/service/service.log`.
205
205
 
206
- A tick is largely synchronous, so a healthy service in a long tick may not
207
- answer health within the deadline. That is `busy`, not `occupied`: it is
206
+ Stretches of a tick are synchronous (walking the directories, reading the
207
+ HTML sources and the sidecars of PDF and Word documents, resolving links and
208
+ assembling the canonical graph, building the whole graph of a project bound to
209
+ a personal workspace, capturing the source snapshot, the
210
+ publisher's restart recovery, its exchange probe, writing the journal, the
211
+ look again at displaced files), so a healthy service in a long one may not
212
+ answer health within the deadline; observing the vault and source files before
213
+ a view is prepared, reading the Markdown sources into the canonical graph,
214
+ preparing
215
+ and publishing a view, and reading it back after a commit yield between files
216
+ and between publication units, and health is answered meanwhile. A service
217
+ that does not answer in time is `busy`, not
218
+ `occupied`: it is
208
219
  never adopted, never stopped and never started over, and a `start` whose own
209
220
  child went straight into a long first tick reports it as started and busy
210
221
  instead of ending it. The command line of another process is read from
@@ -268,7 +279,35 @@ A tick that fails for a reason nobody typed (a full disk, a crash inside a
268
279
  step) does not end the service. Its error code is kept in
269
280
  `state/service/last-error.json`, its message only in the private log; the
270
281
  delay before the next attempt doubles up to a ceiling, and the next tick
271
- proceeds.
282
+ proceeds. It starts as the first tick of the service does, loading the project
283
+ again and looking at every view, so nothing the failed tick saw and did not
284
+ act on is lost. A tick whose engine lock could not be given back at its end
285
+ (its released ticket could not be written) fails too: its error is recorded
286
+ and the loop backs off, and its work was already recorded before the release.
287
+ The lock stays this service's, and the next tick gives it back before it takes
288
+ it again, instead of finding its own ticket and answering `busy` until the
289
+ service is started again.
290
+
291
+ A view refused for a file-system error does not fail the tick. Examples are
292
+ a vault folder that could not be allocated or placed (`vault-location-unusable`),
293
+ a configuration file that could not be read again during the tick, a source
294
+ removed while it was read (both `mixed-read`) and candidates that could not be
295
+ staged (`staging-failed`). The view's freshness names only the refusal, so the
296
+ service writes a `view-refused` line to its private log for each such view
297
+ whose refusal carries a file-system error's code, at most 64 a tick: the view,
298
+ the refusal's code and the error's code (`EACCES`, `EIO`, `ENOENT`, `ENOSPC`).
299
+ Only codes shaped as a file-system error's are written, never a message. A view
300
+ the next tick names for the same reason is not written again, so a folder that
301
+ stays unwritable (tried at every tick) is written once. It is written again when
302
+ its reason changes, or after a tick that failed or did not name it: a tick that
303
+ did not refuse it, refused it for no file-system error, had named 64 other
304
+ views first, or was refused as a whole. When removing the staged candidates
305
+ afterwards fails too, the line also names that error's code as `cleanupCause`,
306
+ and a line is written for that alone when the refusal names no error of its
307
+ own. That failure does not replace the refusal. What could not be removed
308
+ stays where it was: in `staging/`, or at its exchange path under `recovery/`
309
+ once the publication's journal is written. The restart recovery of the next
310
+ publication removes the latter.
272
311
 
273
312
  ### Cases that need a person
274
313
 
@@ -665,6 +665,29 @@ again, so bytes that drift under an unchanged pinned digest are not seen by
665
665
  describes, and the next observation by digest sees the drift. A note whose pin
666
666
  changed is read and verified against the pin as always.
667
667
 
668
+ The maintenance engine prepares a view cooperatively (the
669
+ `prepareViewCooperatively` production seam): it waits between notes so that
670
+ the service keeps answering. The bound above holds there too. After a
671
+ cooperative preparation, and again while the publisher holds its locks,
672
+ before the journal entry that says the publication settled and before the
673
+ commit, the engine checks every selected source as observation does: a source
674
+ that still has the stat hint it was indexed with is taken as unchanged and is
675
+ not opened, and one whose hint moved is read and must still be the pinned
676
+ bytes. A source that changed during the tick, or was removed, refuses the
677
+ generation as `mixed-read`: its manifest is not committed, at that tick or by the restart
678
+ recovery of a later one, and the next tick observes in full and prepares the
679
+ view again. The same waits check that the configuration files, the machine
680
+ settings and the eligibility revision are still the ones the tick read. A
681
+ configuration file that cannot be read at such a check (no permission, an I/O
682
+ error) refuses the view as `mixed-read` too, and the tick goes on to its
683
+ other views.
684
+
685
+ Called on its own, a cooperative preparation reads every selected source
686
+ against its pin once more before it returns, a reused note's included, unless
687
+ its caller says it checks them itself (`scheduling.recheckSources: false`, as
688
+ the engine does). A preparation refused or cancelled on the way leaves the
689
+ preparation cache as it was.
690
+
668
691
  The engine holds one graph file cache and one preparation cache per scope for
669
692
  its lifetime and hands them through the `createGraphCache` and
670
693
  `createPreparationCache` production seams; a test replaces either with