valise-sh 0.1.0 → 0.3.0
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 +320 -9
- package/README.md +104 -127
- package/bin/valise +67 -3266
- package/docs/architecture.md +76 -51
- package/docs/contrib-modules.md +180 -173
- package/docs/installation.md +210 -69
- package/docs/module-abi.md +211 -148
- package/docs/projects.md +249 -0
- package/docs/release-policy.md +90 -11
- package/docs/shared-modules.md +149 -26
- package/examples/README.md +20 -8
- package/examples/e2e/README.md +61 -68
- package/examples/e2e/bundles.tsv +6 -0
- package/examples/e2e/module.d/e2e_aliases.meta +1 -2
- package/examples/e2e/module.d/e2e_completions_bash.meta +0 -1
- package/examples/e2e/module.d/e2e_completions_zsh.meta +0 -1
- package/examples/e2e/module.d/e2e_env_setup.meta +2 -3
- package/examples/e2e/module.d/e2e_fish_features.meta +0 -1
- package/examples/e2e/module.d/e2e_generated_config.meta +1 -2
- package/examples/e2e/module.d/e2e_ksh_features.meta +0 -1
- package/examples/e2e/module.d/e2e_layer_probe.meta +1 -2
- package/examples/e2e/module.d/e2e_optional_network.meta +1 -2
- package/examples/e2e/module.d/e2e_platform_tools.meta +8 -0
- package/examples/e2e/module.d/e2e_platform_tools_bsd.meta +2 -3
- package/examples/e2e/module.d/e2e_platform_tools_darwin.meta +2 -3
- package/examples/e2e/module.d/e2e_platform_tools_linux.meta +2 -3
- package/examples/e2e/valise.conf +4 -0
- package/examples/e2e/variants.tsv +3 -0
- package/examples/local/module.d/hello_valise.meta +1 -2
- package/examples/profile-project/README.md +151 -0
- package/examples/profile-project/bundles.tsv +8 -0
- package/examples/profile-project/layers/demo/module.d/contrib_demo_greeting.meta +9 -0
- package/examples/profile-project/layers/demo/modules/shared/contrib_demo_greeting.sh +4 -0
- package/examples/profile-project/module.d/greeting.meta +9 -0
- package/examples/profile-project/module.d/profile_main.meta +8 -0
- package/examples/profile-project/modules/greeting.sh +5 -0
- package/examples/profile-project/valise.conf +4 -0
- package/examples/profile-project/variants.tsv +3 -0
- package/examples/shared/README.md +13 -34
- package/examples/shared/module.d/contrib_shared_paths.meta +2 -3
- package/examples/shared/module.d/contrib_shared_umask.meta +1 -2
- package/examples/shared/modules/shared/contrib_shared_paths.sh +2 -2
- package/lib/capabilities.sh +40 -13
- package/lib/cli/commands.sh +278 -0
- package/lib/cli/common.sh +326 -0
- package/lib/cli/devtools.sh +539 -0
- package/lib/cli/fish-hook.sh +986 -0
- package/lib/cli/integration.sh +337 -0
- package/lib/cli/lifecycle.sh +578 -0
- package/lib/cli/project.sh +1807 -0
- package/lib/cli/store.sh +1023 -0
- package/lib/cli/usage.sh +308 -0
- package/lib/compiler.awk +352 -24
- package/lib/fish/loader.fish +163 -28
- package/lib/fish/metadata.fish +62 -19
- package/lib/fish/profile.fish +170 -0
- package/lib/loader.sh +736 -158
- package/lib/metadata.sh +137 -55
- package/manifests/parser-support.tsv +2 -2
- package/module.d/valise_editor.meta +9 -0
- package/module.d/valise_history.meta +9 -0
- package/module.d/valise_pager.meta +9 -0
- package/module.d/valise_path.meta +9 -0
- package/module.d/{platform_bsd.meta → valise_platform_bsd.meta} +3 -4
- package/module.d/{platform_linux.meta → valise_platform_linux.meta} +3 -4
- package/module.d/{platform_macos.meta → valise_platform_macos.meta} +3 -4
- package/module.d/{platform_posix.meta → valise_platform_posix.meta} +2 -3
- package/module.d/valise_xdg.meta +9 -0
- package/modules/system/{contrib_editor.sh → valise_editor.sh} +2 -2
- package/modules/system/{contrib_history.sh → valise_history.sh} +2 -2
- package/modules/system/{contrib_pager.sh → valise_pager.sh} +2 -2
- package/modules/system/{contrib_path.sh → valise_path.sh} +3 -3
- package/modules/system/{contrib_xdg.sh → valise_xdg.sh} +2 -2
- package/package.json +91 -9
- package/docs/variants.md +0 -63
- package/entrypoints/.gitkeep +0 -0
- package/entrypoints/fish/profile.fish +0 -99
- package/examples/e2e/build-manifest.sh +0 -250
- package/examples/local/.gitkeep +0 -0
- package/lib/.gitkeep +0 -0
- package/manifests/.gitkeep +0 -0
- package/module.d/.gitkeep +0 -0
- package/module.d/contrib_editor.meta +0 -10
- package/module.d/contrib_history.meta +0 -10
- package/module.d/contrib_pager.meta +0 -10
- package/module.d/contrib_path.meta +0 -10
- package/module.d/contrib_xdg.meta +0 -10
- package/modules/.gitkeep +0 -0
package/docs/module-abi.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Module ABI
|
|
2
2
|
|
|
3
|
-
This document defines
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
This document defines module metadata and the rules the compiled plans and
|
|
4
|
+
the loaders apply to it. Metadata is data: valise never executes a module to
|
|
5
|
+
learn its name, dependencies, or supported shells.
|
|
6
6
|
|
|
7
7
|
## Metadata file format
|
|
8
8
|
|
|
9
9
|
Module metadata lives in `module.d/*.meta` as UTF-8 text with one `key=value`
|
|
10
10
|
field per line. Blank lines and `#` comments are ignored. Values are plain text;
|
|
11
11
|
there is no shell quoting, interpolation, command substitution, glob expansion,
|
|
12
|
-
or `eval` phase.
|
|
12
|
+
or `eval` phase. Each field may appear once: a repeated key is rejected rather
|
|
13
|
+
than letting the last value win. An unknown field is rejected, and so is
|
|
14
|
+
`updates`, which was removed in 0.3.0.
|
|
13
15
|
|
|
14
16
|
Required fields:
|
|
15
17
|
|
|
@@ -19,7 +21,8 @@ Required fields:
|
|
|
19
21
|
| `shells` | Space-separated supported shell identifiers. |
|
|
20
22
|
| `platforms` | Space-separated supported platform identifiers. |
|
|
21
23
|
| `side_effect` | Startup/install/update side-effect class. |
|
|
22
|
-
| `load`
|
|
24
|
+
| `load` | Path to the module body, relative to the module's source directory. Absent for a **meta module** (see below). |
|
|
25
|
+
| `requires` | Required for a meta module. |
|
|
23
26
|
|
|
24
27
|
Optional fields:
|
|
25
28
|
|
|
@@ -28,9 +31,41 @@ Optional fields:
|
|
|
28
31
|
| `requires` | Space-separated required dependency module identifiers. |
|
|
29
32
|
| `optional` | Space-separated optional dependency module identifiers. |
|
|
30
33
|
| `exports` | Space-separated exported command/function identifiers. |
|
|
31
|
-
| `
|
|
34
|
+
| `commands` | Space-separated exports a built release publishes as commands on PATH. |
|
|
35
|
+
| `executables` | Space-separated module-relative paths of scripts published as commands. |
|
|
36
|
+
| `tests` | Space-separated module-relative test scripts that `valise test` runs. |
|
|
32
37
|
| `version` | Optional module version used to reject version skew across roots. |
|
|
33
38
|
|
|
39
|
+
## Meta modules
|
|
40
|
+
|
|
41
|
+
A **meta module** declares dependencies and has no body. It is the natural
|
|
42
|
+
way to write an aggregator: instead of an empty shell file whose only job is
|
|
43
|
+
to name a dependency, the aggregator is metadata alone.
|
|
44
|
+
|
|
45
|
+
A meta module has `requires=` with at least one entry and **no** `load=`.
|
|
46
|
+
The loader and the compiler treat it as a dependency-only
|
|
47
|
+
record: the module itself has nothing to source, but its required closure
|
|
48
|
+
loads normally and in the right order. A meta module must leave `exports=`
|
|
49
|
+
empty; it has no body to define an exported function. Metadata that declares
|
|
50
|
+
an export on a meta module is rejected.
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
name=profile_main
|
|
54
|
+
version=1.0.0
|
|
55
|
+
side_effect=read-only
|
|
56
|
+
requires=valise_path valise_editor
|
|
57
|
+
optional=
|
|
58
|
+
shells=posix
|
|
59
|
+
platforms=any
|
|
60
|
+
exports=
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
At least one of `load` or `requires` is required: a module with neither a
|
|
64
|
+
body nor dependencies is malformed.
|
|
65
|
+
|
|
66
|
+
`load` and `requires` are **not** mutually exclusive. A module that declares
|
|
67
|
+
both loads its own body *and* its dependencies, in dependency-first order.
|
|
68
|
+
|
|
34
69
|
## Identifiers and allowed values
|
|
35
70
|
|
|
36
71
|
Module names, dependency names, and exported shell functions must match
|
|
@@ -41,60 +76,76 @@ non-empty list containing only whitespace is malformed. Required `shells` and
|
|
|
41
76
|
`platforms` lists must contain at least one parsed value. Empty identifiers,
|
|
42
77
|
whitespace inside one identifier, path separators, quotes, shell metacharacters,
|
|
43
78
|
glob characters, and control characters are invalid. Invalid identifiers are
|
|
44
|
-
rejected before the loader can build function names or
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
rejected before the loader can build function names or source a body.
|
|
80
|
+
|
|
81
|
+
Accepted `shells` values are `posix`, `sh`, `bash`, `zsh`, `ksh`, and
|
|
82
|
+
`fish`. Each running POSIX shell belongs to one class: `bash`, `zsh`, or
|
|
83
|
+
`ksh` when the shell can be proven to be that shell, and `sh` for every
|
|
84
|
+
other POSIX shell (dash, BusyBox ash, and so on). The values match classes
|
|
85
|
+
as follows:
|
|
86
|
+
|
|
87
|
+
- `posix` matches every POSIX class: `sh`, `bash`, `zsh`, and `ksh`. Use it
|
|
88
|
+
for a body written in portable shell.
|
|
89
|
+
- `sh` matches only the `sh` class. Combine it with the shells the body also
|
|
90
|
+
supports: `shells=sh bash ksh` loads in dash, bash, and ksh, and is skipped
|
|
91
|
+
in zsh, for a body that needs word splitting zsh does not do.
|
|
92
|
+
- `bash`, `zsh`, and `ksh` match only that class.
|
|
93
|
+
- `fish` selects the native Fish runtime. Fish never sources a POSIX body,
|
|
94
|
+
and no POSIX value matches Fish.
|
|
95
|
+
|
|
96
|
+
A module whose shells do not include the running class is skipped; a bundle
|
|
97
|
+
root, or a module a root requires, that does not support its shell is an
|
|
98
|
+
error.
|
|
99
|
+
|
|
100
|
+
In a built release, the native Fish loader resolves the release's Fish roots
|
|
101
|
+
(`roots_fish=` in `valise-release.conf`) against the release's `module.d/`,
|
|
102
|
+
each `layers/<provider>/module.d/`, and `valise/module.d/`, in that order.
|
|
103
|
+
`runtime/fish/profile.fish` is the adapter the Fish hook sources: it sets
|
|
104
|
+
`_sp_fish_loader_bootstrap_mode` and `_sp_fish_loader_bootstrap_seeds`,
|
|
105
|
+
sources `runtime/fish/loader.fish`, and requires the
|
|
106
|
+
`_sp_fish_loader_bootstrap_status` marker the loader publishes, failing
|
|
107
|
+
closed when it is absent. Duplicate providers of one name are valid only when
|
|
108
|
+
their metadata and Fish bodies are byte-identical.
|
|
61
109
|
|
|
62
110
|
Supported `platforms` values are `any`, `darwin`, `linux`, `bsd`, `freebsd`,
|
|
63
111
|
`openbsd`, and `netbsd`. `platforms=any` matches every supported platform.
|
|
64
112
|
Specific platform values are ORed: `platforms=darwin linux` matches macOS or
|
|
65
113
|
Linux, not BSD.
|
|
66
114
|
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
## Layers
|
|
116
|
+
|
|
117
|
+
A project composes provider modules by vendoring the provider's tree as
|
|
118
|
+
`layers/<provider>/` (its `module.d/` and bodies). Every module in a layer
|
|
119
|
+
uses the `contrib_` prefix, and the `valise_` prefix belongs to the engine.
|
|
120
|
+
Dependencies resolve by name: the same source first, then the only module
|
|
121
|
+
with that name; a name defined in several sources must be written as
|
|
122
|
+
`name@source` where it is used as a root or an exclusion. A built release
|
|
123
|
+
copies the layer modules it loads to `layers/<provider>/`, and one manifest
|
|
124
|
+
covers the project, layer, and engine files together.
|
|
125
|
+
|
|
126
|
+
To update a provider, copy a reviewed revision into `layers/<provider>/`,
|
|
127
|
+
review the diff, and rebuild; record the provider revision beside it if you
|
|
128
|
+
need to know later which one you have. The manifest proves that the
|
|
129
|
+
installed files match the release, not who wrote them.
|
|
130
|
+
|
|
131
|
+
### The development loader's search path
|
|
132
|
+
|
|
133
|
+
The POSIX loader in `lib/loader.sh`, used by `entrypoints/profile.sh` from an
|
|
134
|
+
engine checkout, resolves modules across a priority-ordered root list
|
|
135
|
+
instead: the primary root first, then the colon-separated
|
|
136
|
+
`VALISE_MODULE_PATH`. Its rules, enforced by
|
|
137
|
+
`tests/loader/multi-root.test.sh`:
|
|
138
|
+
|
|
139
|
+
- Every element must be a non-empty, readable, searchable directory. An
|
|
140
|
+
empty element, a missing directory, a newline, or any of the glob
|
|
141
|
+
characters `[`, `?`, and `*` is rejected. Duplicate elements are
|
|
142
|
+
deduplicated; the first occurrence keeps its priority.
|
|
143
|
+
- A module present in several roots must have identical metadata and body
|
|
144
|
+
bytes. Identical copies resolve to the highest-priority root; any
|
|
145
|
+
difference is an error naming both paths.
|
|
146
|
+
- A module provided only by a layer root must carry the `contrib_` prefix.
|
|
147
|
+
- Bodies load from the root that provided the metadata. Dependencies resolve
|
|
148
|
+
across roots in both directions, and the duplicate-export check is global.
|
|
98
149
|
|
|
99
150
|
## Versioning
|
|
100
151
|
|
|
@@ -102,10 +153,11 @@ Shared modules carry an optional `version` field so independent releases
|
|
|
102
153
|
of the same name can be told apart. The value must start with a digit
|
|
103
154
|
and contain only digits, letters, `.`, `_`, `-`; an empty value means
|
|
104
155
|
unversioned. Providers should bump the version for a breaking change.
|
|
105
|
-
Consumers vendor a specific provider snapshot;
|
|
106
|
-
|
|
156
|
+
Consumers vendor a specific provider snapshot; nothing resolves version
|
|
157
|
+
ranges. See [Layers](#layers).
|
|
107
158
|
|
|
108
|
-
Enforcement,
|
|
159
|
+
Enforcement, where one name has several providers (the development loader's
|
|
160
|
+
roots, or the Fish loader's providers):
|
|
109
161
|
|
|
110
162
|
- The same name with different valid versions across roots fails closed
|
|
111
163
|
with a version-skew diagnostic naming both versions and both paths,
|
|
@@ -119,32 +171,6 @@ A versioned record that disagrees with itself is not a version at all:
|
|
|
119
171
|
malformed versions are rejected by `sp_metadata_read` before any body
|
|
120
172
|
is sourced.
|
|
121
173
|
|
|
122
|
-
## Cross-repo composition
|
|
123
|
-
|
|
124
|
-
The supported composition is a primary consumer root plus one
|
|
125
|
-
version-pinned layer root per provider.
|
|
126
|
-
The provider tree is vendored under `layer/<name>/` of the release
|
|
127
|
-
(preserving its internal `module.d/` + `modules/` layout), and a single
|
|
128
|
-
manifest covers the whole closure, so `verify-active` checks consumer
|
|
129
|
-
and provider bytes together before anything is sourced.
|
|
130
|
-
|
|
131
|
-
- Pinning: record the provider source and revision beside the vendored
|
|
132
|
-
bytes (the e2e example writes `layer/shared/layer-pin.txt` from
|
|
133
|
-
`git rev-parse HEAD` at vendor time). Re-pinning means copying the
|
|
134
|
-
provider at a new revision, reviewing the diff, and rebuilding the
|
|
135
|
-
manifest. Pin by git SHA and review the diff; the seal proves the
|
|
136
|
-
release matches its manifest, not who authored it.
|
|
137
|
-
- Loading: the consumer sets `VALISE_MODULE_ROOT` to the release and
|
|
138
|
-
`VALISE_MODULE_PATH` to the layer directory inside it. Deployed login
|
|
139
|
-
snippets carry only the primary root; the layer path is a consumer
|
|
140
|
-
setting applied when sourcing the entrypoint.
|
|
141
|
-
- Naming: layer-only modules must carry the `contrib_` prefix, and
|
|
142
|
-
same-name modules across roots must be byte-identical or the load
|
|
143
|
-
fails closed naming both paths. Dependencies resolve across the
|
|
144
|
-
boundary in both directions: layer modules may require primary
|
|
145
|
-
modules, and primary modules may follow optional dependencies into
|
|
146
|
-
the layer.
|
|
147
|
-
|
|
148
174
|
## Runtime detection contract
|
|
149
175
|
|
|
150
176
|
The loader classifies the running shell and platform before matching module
|
|
@@ -181,6 +207,7 @@ Supported `side_effect` values are:
|
|
|
181
207
|
- `update-network` — performs explicit update/check network behavior.
|
|
182
208
|
- `install-time` — belongs only in installer flows.
|
|
183
209
|
- `local-secret` — reads local secret references at runtime only.
|
|
210
|
+
- `local-state` — creates or caches state under the XDG state directory.
|
|
184
211
|
- `work-only` — work/project-specific behavior that must be opt-in.
|
|
185
212
|
|
|
186
213
|
`requires` dependencies must exist and load before the declaring module. Missing
|
|
@@ -211,50 +238,100 @@ and `tests/fish-runtime.test.sh` respectively:
|
|
|
211
238
|
quarantines them and continues. Unlike the POSIX fallback, quarantined
|
|
212
239
|
members are not loaded.
|
|
213
240
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
the
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
241
|
+
### Writing a cross-platform profile
|
|
242
|
+
|
|
243
|
+
`requires` means the module cannot work without the dependency, so a
|
|
244
|
+
dependency that does not support the host blocks the module and fails strict
|
|
245
|
+
mode. Use `optional` for platform-specific modules, so one profile works on
|
|
246
|
+
every host:
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
name=profile_main
|
|
250
|
+
requires=common_tools
|
|
251
|
+
optional=mac_tools linux_tools
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Only the module for the running platform loads. The other is skipped without a
|
|
255
|
+
message, because its absence is expected. The POSIX loader and compiled plans
|
|
256
|
+
follow the same rules.
|
|
257
|
+
|
|
258
|
+
`exports` lists the public functions the module defines. Plans and loaders use
|
|
259
|
+
it for the pre-load conflict check, the post-load check, and failure cleanup.
|
|
260
|
+
An exported name
|
|
261
|
+
must be defined by the module body before the module is considered
|
|
262
|
+
successfully loaded. An export must name a function, so shell reserved words
|
|
263
|
+
(`case`, `do`, `done`, `elif`, `else`, `esac`, `fi`, `for`, `if`, `in`,
|
|
264
|
+
`then`, `until`, `while`, and `time`) are rejected.
|
|
265
|
+
|
|
266
|
+
### Published commands
|
|
267
|
+
|
|
268
|
+
`commands` names exports that should also run as ordinary commands, from any
|
|
269
|
+
program and not only from an interactive shell. Every entry must also appear in
|
|
270
|
+
`exports`. `executables` names standalone scripts in the module's directory;
|
|
271
|
+
each is published under its file name, which must start with a letter, digit,
|
|
272
|
+
or underscore and contain only letters, digits, `.`, `_`, and `-`. The
|
|
273
|
+
metadata field `updates` was removed in 0.3.0; a metadata file that still sets
|
|
274
|
+
it is rejected.
|
|
275
|
+
|
|
276
|
+
`valise build` turns both into entries under the release's `bin/`:
|
|
277
|
+
|
|
278
|
+
- For a module with `commands`, it compiles `plans/commands/<module>.sh`, a
|
|
279
|
+
plan whose only root is that module, for the module's command shell: `sh`
|
|
280
|
+
when the module supports `posix` or `sh`, otherwise the first of `bash`,
|
|
281
|
+
`zsh`, and `ksh` it lists. Each command gets a launcher `bin/<command>` that
|
|
282
|
+
runs under that shell, loads the plan in strict mode from the release that
|
|
283
|
+
contains it, restores the caller's `VALISE_MODE`, and calls the function
|
|
284
|
+
with the command's arguments. The function's exit status is the command's.
|
|
285
|
+
- Each `executables` path is copied to `bin/<file name>` and keeps its own
|
|
286
|
+
interpreter line.
|
|
287
|
+
|
|
288
|
+
The plan's closure ships in the release even when no shell bundle loads it.
|
|
289
|
+
A Fish-only module cannot declare `commands`; it may declare `executables`.
|
|
290
|
+
Two modules that publish the same command name, from either field, fail the
|
|
291
|
+
build. `valise-release.conf` lists the published names in `commands=`.
|
|
292
|
+
Installation puts a stub for each name on PATH; see
|
|
293
|
+
[installation.md](installation.md#published-commands).
|
|
236
294
|
|
|
237
295
|
## Side-effect phases
|
|
238
296
|
|
|
239
297
|
The ordinary source phase rejects `generated-file`, `update-network`, and
|
|
240
298
|
`install-time` modules before sourcing their bodies. `read-only`, `path-env`,
|
|
241
|
-
`local-secret`, and `work-only` are permitted by the engine;
|
|
242
|
-
to load a `work-only` module is the consuming profile's
|
|
299
|
+
`local-secret`, `local-state`, and `work-only` are permitted by the engine;
|
|
300
|
+
choosing whether to load a `work-only` module is the consuming profile's
|
|
301
|
+
responsibility.
|
|
302
|
+
|
|
303
|
+
`local-state` is the permitted class for a module that creates a cache or
|
|
304
|
+
directory under the XDG state directory at source time (`mkdir -p` under
|
|
305
|
+
`$XDG_STATE_HOME`). It is the only write class the ordinary source phase
|
|
306
|
+
allows. A module that writes anywhere else must declare `generated-file`,
|
|
307
|
+
which the source phase rejects.
|
|
243
308
|
|
|
244
309
|
The enforcement hook is
|
|
245
310
|
`sp_metadata_validate_source_side_effect <side_effect>`. The loader calls it
|
|
246
|
-
before ordinary source invokes a load path
|
|
311
|
+
before ordinary source invokes a load path.
|
|
247
312
|
|
|
248
313
|
## Capability layer available to modules
|
|
249
314
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
315
|
+
Every plan sources the capability prelude (`runtime/capabilities.sh` in a
|
|
316
|
+
release, `lib/capabilities.sh` in the engine) before any module, and the
|
|
317
|
+
development entrypoint does the same before the loader. Module bodies may
|
|
318
|
+
therefore call the `sp_cap_*` helpers without sourcing platform files
|
|
319
|
+
themselves: CPU count, file mtime, temporary directories and files, command
|
|
320
|
+
lookup, OS detection, canonical path containment, symlink-component
|
|
321
|
+
rejection, Homebrew discovery, secure local-bin checks, and background
|
|
256
322
|
process launch.
|
|
257
323
|
|
|
324
|
+
**The helpers stay available after the profile loads.** A compiled plan
|
|
325
|
+
sources the capability prelude and leaves it in place, so a function defined
|
|
326
|
+
by a module can call `sp_cap_os` or any other `sp_cap_*` helper long after
|
|
327
|
+
the plan finished loading. The compiled plan and the loader agree on this;
|
|
328
|
+
neither cleans the helpers up at the end of a load.
|
|
329
|
+
|
|
330
|
+
A module that needs a portable operation should call the `sp_cap_*` helper
|
|
331
|
+
rather than reaching for a platform-specific command directly. That keeps the
|
|
332
|
+
module working on every platform the engine supports, and keeps the
|
|
333
|
+
platform-specific code in one reviewed library.
|
|
334
|
+
|
|
258
335
|
Mutation paths must fail closed when a capability cannot prove safety. Update
|
|
259
336
|
destinations must combine canonical containment with symlink-component rejection
|
|
260
337
|
before writing, cooldown logic must use the mtime helper instead of raw
|
|
@@ -265,24 +342,22 @@ Read-only/status paths may degrade only by quietly skipping unsupported details
|
|
|
265
342
|
or by returning a clear nonzero status with a diagnostic from the caller.
|
|
266
343
|
|
|
267
344
|
Platform modules under `modules/platform/` are thin wrappers around the library.
|
|
268
|
-
They expose stable module dependencies (`
|
|
269
|
-
`
|
|
345
|
+
They expose stable module dependencies (`valise_platform_posix`, `valise_platform_macos`,
|
|
346
|
+
`valise_platform_linux`, and `valise_platform_bsd`) while keeping compatibility logic in one
|
|
270
347
|
library.
|
|
271
348
|
|
|
272
349
|
## Load target and loader mode
|
|
273
350
|
|
|
274
|
-
For file-backed modules, `load` is the
|
|
275
|
-
|
|
276
|
-
|
|
351
|
+
For file-backed modules, `load` is the shell file sourced after metadata
|
|
352
|
+
validation, dependency ordering, shell and platform matching, and the
|
|
353
|
+
side-effect checks have passed. The ABI has no unload hook: the loader
|
|
277
354
|
does not implement module rollback or unload semantics.
|
|
278
355
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
functions. The bridge hook is invoked only after the same identifier, dependency,
|
|
283
|
-
shell/platform, and phase checks that protect file-backed modules.
|
|
356
|
+
A meta module has no `load`; it has `requires` and no body. Nothing is
|
|
357
|
+
sourced for it, but its dependency closure loads and its dependencies'
|
|
358
|
+
export checks still apply.
|
|
284
359
|
|
|
285
|
-
Module bodies
|
|
360
|
+
Module bodies must treat `_spl_*`, `_sp_loader_*`, and
|
|
286
361
|
`_SP_LOADER_*` names as loader-private reserved namespaces. Native Fish
|
|
287
362
|
transactions additionally reserve `_sp_fish_loader_scope_before_*`,
|
|
288
363
|
`__sp_fish_loader_scope_before_*`, `_sp_fish_loader_scope_after_*`, and
|
|
@@ -295,8 +370,14 @@ module and must never use module-controlled private state for recursive cleanup.
|
|
|
295
370
|
Modules must also not define shell functions that shadow critical shell control
|
|
296
371
|
utilities (`command`, `unset`, `readonly`, `return`, or `eval`) or mark those
|
|
297
372
|
functions readonly. Those names are reserved because the loader must recover
|
|
298
|
-
control after sourcing a module; direct definitions in
|
|
299
|
-
|
|
373
|
+
control after sourcing a module; direct definitions in module bodies are
|
|
374
|
+
rejected before the body is invoked.
|
|
375
|
+
|
|
376
|
+
After a body runs, the loader restores the caller's physical directory, logical
|
|
377
|
+
`PWD` and `OLDPWD`, `IFS`, and the shell options it snapshotted. A body that
|
|
378
|
+
makes any of those variables readonly is rejected as caller-state modification:
|
|
379
|
+
the loader fails the module without aborting the caller. Cleanup resolves `rm`
|
|
380
|
+
from a fixed absolute path, so a module that changes `PATH` cannot prevent it.
|
|
300
381
|
|
|
301
382
|
Failure handling is selected once by the loader caller with
|
|
302
383
|
`VALISE_MODE=strict|permissive`; it is not a per-module metadata promise.
|
|
@@ -305,25 +386,11 @@ Permissive mode reports the affected module and continues with unrelated modules
|
|
|
305
386
|
|
|
306
387
|
## Path fields
|
|
307
388
|
|
|
308
|
-
`load` paths are
|
|
389
|
+
`load`, `executables`, and `tests` paths are relative to the module's source
|
|
390
|
+
directory (the project root, `layers/<provider>/`, or the engine). Absolute
|
|
309
391
|
paths, `..` path segments, spaces, quotes, shell metacharacters, and glob
|
|
310
392
|
characters are invalid. Reading metadata must not source these paths.
|
|
311
393
|
|
|
312
|
-
## Compatibility bridge
|
|
313
|
-
|
|
314
|
-
The legacy flat-gist modules expose dependencies by executing `_shell_module_*`
|
|
315
|
-
functions with `_SHMOD_GET_DEPS=1`. That behavior is compatibility evidence, not
|
|
316
|
-
the target discovery model. The bridge serializes each legacy module into the
|
|
317
|
-
same metadata shape with:
|
|
318
|
-
|
|
319
|
-
- `name=<legacy-name>`
|
|
320
|
-
- `requires=<legacy _SHMOD_DEPS>`
|
|
321
|
-
- `load_hook=_shell_module_<legacy-name>`
|
|
322
|
-
|
|
323
|
-
The bridge exists to preserve current dependency edges and permissive warning
|
|
324
|
-
behavior while the loader migrates to metadata. It must not be used to justify
|
|
325
|
-
executing arbitrary new module bodies during discovery.
|
|
326
|
-
|
|
327
394
|
## Validation implementation
|
|
328
395
|
|
|
329
396
|
`lib/metadata.sh` provides the current metadata parser and validator:
|
|
@@ -332,9 +399,5 @@ executing arbitrary new module bodies during discovery.
|
|
|
332
399
|
- `sp_metadata_validate_identifier <identifier>` validates ABI identifiers.
|
|
333
400
|
- `sp_metadata_validate_source_side_effect <side_effect>` rejects phase-unsafe
|
|
334
401
|
classes before ordinary startup can source a module body.
|
|
335
|
-
- `sp_metadata_legacy_bridge <name> <requires> <side-effect> <exports>` emits a
|
|
336
|
-
normalized compatibility record for an existing `_shell_module_*` module. Bridge
|
|
337
|
-
records include empty `updates=` unless a later migration task explicitly maps
|
|
338
|
-
legacy update registration.
|
|
339
402
|
|
|
340
403
|
The executable contract is `tests/loader/metadata-abi.test.sh`.
|