nawabari 0.7.1 → 0.9.2
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/README.md +264 -29
- package/dist/cli-command-registry.d.ts +50 -0
- package/dist/cli-command-registry.js +509 -0
- package/dist/cli-command-registry.js.map +1 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +302 -583
- package/dist/cli.js.map +1 -1
- package/dist/contract.d.ts +6 -0
- package/dist/contract.js +296 -192
- package/dist/contract.js.map +1 -1
- package/dist/domain/cgroups-v2.d.ts +77 -0
- package/dist/domain/cgroups-v2.js +434 -0
- package/dist/domain/cgroups-v2.js.map +1 -0
- package/dist/domain/doctor.d.ts +4 -1
- package/dist/domain/doctor.js +64 -13
- package/dist/domain/doctor.js.map +1 -1
- package/dist/domain/errors.d.ts +1 -1
- package/dist/domain/errors.js.map +1 -1
- package/dist/domain/landlock.d.ts +55 -0
- package/dist/domain/landlock.js +279 -0
- package/dist/domain/landlock.js.map +1 -0
- package/dist/domain/runtime.d.ts +12 -0
- package/dist/domain/runtime.js +50 -0
- package/dist/domain/runtime.js.map +1 -0
- package/dist/domain/sandbox-launcher.d.ts +26 -0
- package/dist/domain/sandbox-launcher.js +312 -23
- package/dist/domain/sandbox-launcher.js.map +1 -1
- package/dist/domain/sandbox-seccomp.d.ts +46 -0
- package/dist/domain/sandbox-seccomp.js +302 -0
- package/dist/domain/sandbox-seccomp.js.map +1 -0
- package/dist/domain/sandbox.d.ts +45 -1
- package/dist/domain/sandbox.js +176 -25
- package/dist/domain/sandbox.js.map +1 -1
- package/dist/domain/session-backend.d.ts +4 -1
- package/dist/domain/session-backend.js +256 -3
- package/dist/domain/session-backend.js.map +1 -1
- package/dist/domain/session.d.ts +207 -3
- package/dist/domain/session.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/failure-code-vocabulary.d.ts +20 -0
- package/dist/failure-code-vocabulary.js +216 -0
- package/dist/failure-code-vocabulary.js.map +1 -0
- package/dist/git.js +92 -6
- package/dist/git.js.map +1 -1
- package/dist/operation-authorization.d.ts +1 -1
- package/dist/product-state-manifest.d.ts +152 -0
- package/dist/product-state-manifest.js +240 -0
- package/dist/product-state-manifest.js.map +1 -0
- package/dist/public-contract.d.ts +25 -0
- package/dist/public-contract.js +29 -0
- package/dist/public-contract.js.map +1 -0
- package/dist/public-state.d.ts +75 -0
- package/dist/public-state.js +60 -0
- package/dist/public-state.js.map +1 -0
- package/dist/registry/lock.d.ts +8 -0
- package/dist/registry/lock.js +38 -20
- package/dist/registry/lock.js.map +1 -1
- package/dist/repository-evidence.d.ts +16 -0
- package/dist/repository-evidence.js.map +1 -1
- package/dist/resource-claims.d.ts +2 -2
- package/dist/resource-claims.js +96 -27
- package/dist/resource-claims.js.map +1 -1
- package/dist/session-lifecycle-actions.d.ts +64 -0
- package/dist/session-lifecycle-actions.js +117 -0
- package/dist/session-lifecycle-actions.js.map +1 -0
- package/dist/session-lifecycle-classification.d.ts +120 -0
- package/dist/session-lifecycle-classification.js +88 -0
- package/dist/session-lifecycle-classification.js.map +1 -0
- package/dist/session-registry.d.ts +210 -2
- package/dist/session-registry.js +1330 -87
- package/dist/session-registry.js.map +1 -1
- package/dist/state/index.d.ts +5 -0
- package/dist/state/index.js +4 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/session/actors.d.ts +71 -0
- package/dist/state/session/actors.js +259 -0
- package/dist/state/session/actors.js.map +1 -0
- package/dist/state/session/guards.d.ts +25 -0
- package/dist/state/session/guards.js +108 -0
- package/dist/state/session/guards.js.map +1 -0
- package/dist/state/session/index.d.ts +6 -0
- package/dist/state/session/index.js +5 -0
- package/dist/state/session/index.js.map +1 -0
- package/dist/state/session/machine.d.ts +185 -0
- package/dist/state/session/machine.js +408 -0
- package/dist/state/session/machine.js.map +1 -0
- package/dist/state/session/types.d.ts +94 -0
- package/dist/state/session/types.js +14 -0
- package/dist/state/session/types.js.map +1 -0
- package/package.json +46 -26
package/README.md
CHANGED
|
@@ -17,9 +17,11 @@ authority (`src/domain/sandbox.ts`, contract
|
|
|
17
17
|
resolved through the authoritative registry/guard path to a typed sandbox
|
|
18
18
|
execution request; it does not create a second session identity. A
|
|
19
19
|
machine-readable capability/doctor report distinguishes required Linux
|
|
20
|
-
primitives (bubblewrap, user/mount/PID/IPC/UTS namespaces
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
primitives (bubblewrap, user/mount/PID/IPC/UTS namespaces, the versioned
|
|
21
|
+
seccomp baseline, and capability reduction) from optional defense-in-depth
|
|
22
|
+
primitives (cgroups v2 and Landlock). The sandbox report exposes Landlock's
|
|
23
|
+
observed ABI, support, and effective state (`available`, `enforced`,
|
|
24
|
+
`reduced-defense`, `incompatible`, or `error`). When protected execution is requested and a required capability
|
|
23
25
|
is unavailable or the platform is unsupported, resolution fails closed and
|
|
24
26
|
never returns a request that claims the legacy unsandboxed path is
|
|
25
27
|
protected. The lower-level contract remains responsible only for capability
|
|
@@ -44,15 +46,43 @@ and pnpm's user bin when present) are read-only; credentials and the rest of
|
|
|
44
46
|
the host HOME are not mounted. `/dev`, system certificates/configuration, and
|
|
45
47
|
the detected runtime are explicit read-only/runtime inputs.
|
|
46
48
|
|
|
49
|
+
When the host exposes a compatible Landlock ABI, the protected launcher applies
|
|
50
|
+
a rule set derived from this same topology inside bubblewrap. An unavailable
|
|
51
|
+
or incompatible optional ABI leaves bubblewrap active and reports
|
|
52
|
+
`reduced-defense`; an adapter setup failure fails closed, and a profile that
|
|
53
|
+
explicitly requires Landlock also fails closed when its adapter cannot be
|
|
54
|
+
established. Setup diagnostics are bounded and no ambient/unsandboxed retry is
|
|
55
|
+
attempted.
|
|
56
|
+
|
|
47
57
|
On standalone Linux the profile uses existing `/usr`, `/bin`, `/lib*` and
|
|
48
58
|
selected `/etc` paths only when present. On NixOS it additionally selects
|
|
49
59
|
`/nix/store`, `/run/current-system`, `/run/wrappers`, and the per-user profile
|
|
50
|
-
when present;
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
when present; broad FHS views (`/usr`, `/bin`, `/lib*`) are not selected when
|
|
61
|
+
the NixOS closure roots are present. Missing required paths or namespace
|
|
62
|
+
support produces a stable capability/topology error. The protected child uses
|
|
63
|
+
`nawabari.seccomp.v1`, a compatibility-first deny-list whose policy denials
|
|
64
|
+
return `EPERM` rather than hanging or terminating ordinary development
|
|
65
|
+
subprocess trees. Ambient capabilities are empty (`--cap-drop ALL`). Network
|
|
66
|
+
remains inherited by design.
|
|
67
|
+
|
|
68
|
+
The canonical Mottainai NixOS Runtime fixture can run the opt-in compatibility
|
|
69
|
+
conformance matrix as its unprivileged repository principal:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
NAWABARI_NIXOS_RUNTIME_CONFORMANCE=1 \
|
|
73
|
+
node --test --import tsx src/domain/nixos-runtime-compat.test.ts
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This invokes the normal `session run` CLI route and production launcher. It
|
|
77
|
+
checks explicit NixOS closure paths, private versus repository-shared HOME
|
|
78
|
+
state, Git/checkpoint authority, representative toolchain subprocesses, and
|
|
79
|
+
sequential/concurrent session isolation. It does not create a test-only
|
|
80
|
+
sandbox or treat Mottainai state as Nawabari authority.
|
|
53
81
|
|
|
54
82
|
## Install
|
|
55
83
|
|
|
84
|
+
Nawabari requires Node.js >=24.
|
|
85
|
+
|
|
56
86
|
```bash
|
|
57
87
|
npm install -g nawabari
|
|
58
88
|
```
|
|
@@ -82,6 +112,17 @@ result-schema versions, identity fields, and stable `failure_codes`. The
|
|
|
82
112
|
package version is release metadata; it is not a substitute for the
|
|
83
113
|
machine-contract identifier.
|
|
84
114
|
|
|
115
|
+
The `protected-execution` capability separately advertises the versioned
|
|
116
|
+
`nawabari.sandbox-execution.v1` contract, its required and optional host
|
|
117
|
+
capabilities, the canonical `session run` entry point and `session exec` alias,
|
|
118
|
+
`network_mode: "inherited"`, and fail-closed behavior. Use
|
|
119
|
+
`nawabari doctor --json` to inspect the current host without creating or
|
|
120
|
+
selecting a session. Its `sandbox` report is produced by the same
|
|
121
|
+
`sandboxDoctorReport` authority used when resolving protected execution and
|
|
122
|
+
contains `platform_supported`, per-capability status, `ready`, and
|
|
123
|
+
`missing_required`. An unavailable required capability never implies an
|
|
124
|
+
ambient protected fallback.
|
|
125
|
+
|
|
85
126
|
The `resource-claims` capability additionally exposes a machine-readable
|
|
86
127
|
`claim_set_replacement` object (`commands`, `atomic: true`,
|
|
87
128
|
`pairing: "adjacent-resource-mode"`, `idempotent_retry: true`,
|
|
@@ -126,8 +167,8 @@ The result schemas expose the following identities:
|
|
|
126
167
|
| claims | `claim_id`, `session_id`, `resource`, `mode`, `claim_set_generation`, `previous_claim_set_generation` |
|
|
127
168
|
| authorization | `operation`, `allowed`, `code`, `claim_ids` |
|
|
128
169
|
| checkpoint evidence | `head`, `changed`, `staged`, `unstaged`, `untracked`, `in_claim`, `out_of_claim` |
|
|
129
|
-
| repository evidence | `session_id`, `base_revision`, `head`, `clean`, `paths.stats`, `evidence_hash`
|
|
130
|
-
| bounded diff | `from_revision`, `to_revision`, `paths`, `stats`, `patch`, `evidence_hash`
|
|
170
|
+
| repository evidence | `session_id`, `session_updated_at`, `base_revision`, `head`, `clean`, `paths.stats`, `evidence_hash` |
|
|
171
|
+
| bounded diff | `from_revision`, `to_revision`, `paths`, `stats`, `diagnostics`, `patch`, `evidence_hash` |
|
|
131
172
|
| commit/push | `commit_sha`, `remote`, `branch`, `target`, `relation` |
|
|
132
173
|
| reconciliation/cleanup | `clean`, `issues`, `candidates`, `cleaned`, `blocked`, `recovery_hints` |
|
|
133
174
|
|
|
@@ -138,6 +179,41 @@ The local lifecycle requires Git and the repository-local registry/lock only;
|
|
|
138
179
|
it does not require Mottainai, GitHub, `gh`, network access, an LLM, or a
|
|
139
180
|
coding-agent runtime.
|
|
140
181
|
|
|
182
|
+
## Package state/contract API
|
|
183
|
+
|
|
184
|
+
The CLI/JSON surface above remains the primary integration boundary. For a
|
|
185
|
+
Node caller that wants public lifecycle observation/snapshot,
|
|
186
|
+
transition-decision data, or machine-contract discovery without spawning the
|
|
187
|
+
CLI and parsing its output, the package additionally exports two explicit,
|
|
188
|
+
stable entry points:
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
import { classifyNawabariState, getNawabariSessionStateSnapshot } from "nawabari/state";
|
|
192
|
+
import { nawabariMachineContract } from "nawabari/contract";
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`nawabari/state` exposes the public lifecycle vocabulary
|
|
196
|
+
(`NawabariLifecycleState`, `NawabariCommand`), the public observation input
|
|
197
|
+
(`NawabariObservation`), and the public projection produced from it
|
|
198
|
+
(`NawabariStateSnapshot`, `NawabariTransitionDecision`). `classifyNawabariState`
|
|
199
|
+
is a pure, transport-neutral projection function; `getNawabariSessionStateSnapshot`
|
|
200
|
+
observes one real, already-provisioned session through the same
|
|
201
|
+
`SessionRegistry` Git/filesystem/session-registry authority `session inspect`
|
|
202
|
+
uses, without mutating anything. `nawabari/contract` exposes
|
|
203
|
+
`nawabariMachineContract()`, a zero-argument wrapper over the same
|
|
204
|
+
`capabilities --json` contract that defaults to the installed package's own
|
|
205
|
+
version.
|
|
206
|
+
|
|
207
|
+
These are the only supported package entry points beyond the CLI binaries.
|
|
208
|
+
Raw XState machine/actor internals, actor refs, internal state-node ids, and
|
|
209
|
+
private machine context are not exported — `package.json#exports` declares no
|
|
210
|
+
other subpath, so a deep import such as `nawabari/dist/state/session/machine.js`
|
|
211
|
+
is rejected. `classifyNawabariState` only ever projects a caller-supplied observation into
|
|
212
|
+
a decision; it never mutates anything, and no observation or transition
|
|
213
|
+
decision it returns grants mutation authority. Mutating a session
|
|
214
|
+
(close/discard/claim/commit/push/...) still requires the existing CLI or the
|
|
215
|
+
`SessionRegistry` authority directly.
|
|
216
|
+
|
|
141
217
|
## Read-only repository evidence
|
|
142
218
|
|
|
143
219
|
The evidence family is session-addressed and has no task, Issue, semantic, or
|
|
@@ -159,13 +235,24 @@ New sessions persist the exact creation/base revision as `base_revision`;
|
|
|
159
235
|
legacy records that lack this field report `base_revision: null` and
|
|
160
236
|
`base_revision_proven: false` rather than inferring it from a mutable ref.
|
|
161
237
|
|
|
238
|
+
`session_updated_at` is the UTC timestamp of the last authoritative
|
|
239
|
+
session-state mutation, not a general Git filesystem mtime. A successful
|
|
240
|
+
Nawabari-managed `commit` is such a mutation, so the registry timestamp is
|
|
241
|
+
persisted before the command returns and a subsequent snapshot reports it with
|
|
242
|
+
the resulting `head`. Git changes made outside Nawabari do not advance this
|
|
243
|
+
field; consumers must use the Git-observed fields for those changes.
|
|
244
|
+
|
|
162
245
|
`diff` requires at least one explicit concrete path and never accepts a glob or
|
|
163
246
|
an empty repository-wide selection. Stats are returned by default. Patch text
|
|
164
247
|
requires `--patch` and is bounded to at most 64 paths, 64 KiB, and 128 hunks;
|
|
165
248
|
the caller may request smaller limits. Unrepresentable Git observations fail
|
|
166
249
|
with `GIT_STATE_AMBIGUOUS`; a requested path whose stat is not exposed by Git
|
|
167
250
|
remains in the result with `available: false` and makes snapshot evidence
|
|
168
|
-
`complete: false`, so no path silently disappears.
|
|
251
|
+
`complete: false`, so no path silently disappears. Such paths include a
|
|
252
|
+
bounded `diagnostics` entry; a target directly observed as untracked reports
|
|
253
|
+
`reason: UNTRACKED_TARGET`, while other unavailable-stat causes remain
|
|
254
|
+
`STAT_UNAVAILABLE`. These diagnostics are read-only and do not stage or add
|
|
255
|
+
files.
|
|
169
256
|
|
|
170
257
|
## Session lifecycle
|
|
171
258
|
|
|
@@ -216,8 +303,9 @@ destructive cleanup. A clean close releases only the owned worktree and
|
|
|
216
303
|
branch, and repeating close is idempotent. `gc` detects stale or interrupted
|
|
217
304
|
sessions; `--apply` uses the same close safety checks and reports blocked
|
|
218
305
|
sessions instead of guessing. `gc --dry-run` performs the same non-mutating
|
|
219
|
-
cleanup preflight
|
|
220
|
-
|
|
306
|
+
cleanup preflight, reports age/physical/lifecycle suspicion separately from
|
|
307
|
+
destructive eligibility, and includes stable blocker codes and
|
|
308
|
+
`recovery_hints` for every eligible candidate that is not safe. Cleanup revalidates the physical worktree,
|
|
221
309
|
branch, and `HEAD` observations immediately before each destructive Git
|
|
222
310
|
operation.
|
|
223
311
|
|
|
@@ -238,12 +326,15 @@ history view; closed records remain persisted and are never silently deleted
|
|
|
238
326
|
by listing or cleanup.
|
|
239
327
|
|
|
240
328
|
`gc` stale eligibility is separate from closed-history retention. Its default
|
|
241
|
-
threshold is 24 hours (`86,400,000` ms), measured from persisted `updated_at
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
329
|
+
threshold is 24 hours (`86,400,000` ms), measured from persisted `updated_at`.
|
|
330
|
+
Elapsed age is diagnostic suspicion only: it never authorizes destructive
|
|
331
|
+
cleanup for a physically healthy active session. Records already in `stale` or
|
|
332
|
+
`closing` state are eligible, as is an otherwise live record when Git reports
|
|
333
|
+
its registered worktree as safely prunable and missing. Ambiguous physical
|
|
334
|
+
state remains ineligible and fail-closed. `gc --dry-run` exposes suspicion and
|
|
335
|
+
destructive eligibility/reason separately for each candidate; `gc --apply`
|
|
336
|
+
uses only eligible candidates. Closed records are never stale cleanup
|
|
337
|
+
candidates.
|
|
247
338
|
|
|
248
339
|
`doctor` includes a non-destructive `reconciliation` check. It reports
|
|
249
340
|
registry/Git ownership drift, including missing or prunable worktrees and
|
|
@@ -258,8 +349,18 @@ code. Claim JSON exposes `schema_version`, `claim_id`, `session_id`, the
|
|
|
258
349
|
repository/worktree identities, canonical `resource`, `mode`, and timestamps.
|
|
259
350
|
The claim schema version is `2` and supports `read`, `write`, and
|
|
260
351
|
`exclusive-write`. Schema v1 records use different overlap semantics and are
|
|
261
|
-
not interpreted implicitly
|
|
262
|
-
`
|
|
352
|
+
not interpreted implicitly. If an upgraded repository reports
|
|
353
|
+
`UNSUPPORTED_CLAIM_SCHEMA_VERSION`, run the public migration command:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
git nawabari migrate --json
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Migration validates the complete legacy registry under the registry lock and
|
|
360
|
+
rewrites it with an atomic replace. It is idempotent and retry-safe; ambiguous
|
|
361
|
+
or corrupt state is rejected with bounded diagnostics. Do not hand-edit or
|
|
362
|
+
delete the registry. Embedding callers may use the same authority through
|
|
363
|
+
`SessionRegistry.migrate()`.
|
|
263
364
|
|
|
264
365
|
```bash
|
|
265
366
|
git nawabari session claim --session "$NAWABARI_SESSION_ID" \
|
|
@@ -337,6 +438,14 @@ garbage-collecting a session releases its claims; no separate claim registry
|
|
|
337
438
|
or claim lock exists. Claims describe ownership state only and do not provide
|
|
338
439
|
OS-level filesystem observation or a filesystem sandbox.
|
|
339
440
|
|
|
441
|
+
For an existing path, canonicalization follows the physical directory entry,
|
|
442
|
+
so a case-insensitive filesystem cannot give `README.md` and `readme.md`
|
|
443
|
+
independent claim identities. This is determined from the filesystem entry
|
|
444
|
+
itself; paths that do not exist yet, and wildcard portions of a claim, retain
|
|
445
|
+
their exact lexical case because their future physical identity is unknown.
|
|
446
|
+
On a case-sensitive filesystem an alternate-case spelling is therefore a
|
|
447
|
+
distinct (possibly future) path, and no global lowercasing is applied.
|
|
448
|
+
|
|
340
449
|
An ordinary source change uses `write` and can proceed while another session
|
|
341
450
|
holds a `read` declaration:
|
|
342
451
|
|
|
@@ -389,6 +498,16 @@ identifiers, in addition to the human-readable `message`. JSON and human
|
|
|
389
498
|
output always render the identical underlying result; only the formatting
|
|
390
499
|
differs.
|
|
391
500
|
|
|
501
|
+
Lifecycle diagnostics additionally expose `next_action` (and the bounded
|
|
502
|
+
`next_actions` list) as typed, non-mutating caller actions. The action schema
|
|
503
|
+
is versioned independently and currently includes `retain-session`,
|
|
504
|
+
`supply-exact-integrated-revision`,
|
|
505
|
+
`retry-close-with-bounded-integration-fetch`, `discard-session`, and
|
|
506
|
+
`reconcile-physical-state`. A discard action always carries explicit intent;
|
|
507
|
+
ambiguous or terminal states never advertise destructive actions. These
|
|
508
|
+
fields are additive to `session-diagnostic.v1`, so existing consumers may
|
|
509
|
+
continue using `safe_actions`.
|
|
510
|
+
|
|
392
511
|
- **`RESOURCE_CLAIM_CONFLICT`** (`session claim`/`session update`,
|
|
393
512
|
`authorize`, `guard --operation`) reports the blocking claim
|
|
394
513
|
(`ownerClaimId`, `ownerResource`, `ownerMode`) and the blocking session's
|
|
@@ -521,6 +640,16 @@ successful commit — it fails with `COMMIT_RESULT_DIVERGED`, which retains the
|
|
|
521
640
|
resulting `commitSha` (the Git commit already happened) alongside the
|
|
522
641
|
authorized, actual, and divergent path sets for recovery/reconciliation.
|
|
523
642
|
|
|
643
|
+
If Git reports a bounded transport failure (timeout, output limit, or spawn
|
|
644
|
+
failure) after the commit invocation, Nawabari re-reads the local `HEAD` and
|
|
645
|
+
that commit's bounded changed-path set before classifying the outcome. A
|
|
646
|
+
failure whose `HEAD` is unchanged is reported with `outcome: "proven-absent"`
|
|
647
|
+
and `retrySafe: true`; a matching new commit is returned as a successful
|
|
648
|
+
result with `reconciliation.outcome: "proven-committed"` and its resulting
|
|
649
|
+
SHA; if either observation is unavailable or does not match the authorized
|
|
650
|
+
paths, the failure carries `outcome: "unresolved"` and `retrySafe: false`.
|
|
651
|
+
Unresolved outcomes never authorize a blind retry.
|
|
652
|
+
|
|
524
653
|
```bash
|
|
525
654
|
git nawabari commit --session "$NAWABARI_SESSION_ID" \
|
|
526
655
|
--message 'record the local change' --resource src/example.ts --json
|
|
@@ -546,12 +675,19 @@ Governed push requires explicit claim-covered resources and an explicit
|
|
|
546
675
|
`--remote`/`--branch` target. Existing upstream and local/remote relation are
|
|
547
676
|
inspected before mutation. A missing upstream requires `--create-upstream`;
|
|
548
677
|
behind or diverged history requires explicit `--force`. Force pushes use an
|
|
549
|
-
exact `--force-with-lease` bound to the observed remote branch SHA
|
|
550
|
-
pushes
|
|
551
|
-
|
|
552
|
-
|
|
678
|
+
exact `--force-with-lease` bound to the observed remote branch SHA. Ordinary
|
|
679
|
+
pushes use the same exact-generation lease, while force authorization remains
|
|
680
|
+
separate. A new remote branch uses an empty lease, requiring the target ref to
|
|
681
|
+
remain absent. Nawabari fetches only the explicit remote branch into a
|
|
682
|
+
disposable ref when local ancestry is missing; it does not update tracking
|
|
683
|
+
refs or fetch unrelated branches/tags.
|
|
553
684
|
The JSON result includes the immutable `source_sha`, explicit `target_ref`,
|
|
554
|
-
observed `observed_remote_sha`, and relation.
|
|
685
|
+
observed `observed_remote_sha`, and relation. If a bounded transport failure
|
|
686
|
+
may have happened after the remote mutation, Nawabari re-observes only that
|
|
687
|
+
exact remote ref. It reports `reconciliation.outcome` as `proven-pushed`,
|
|
688
|
+
`proven-absent`, or `unresolved`, with the exact precondition and post-failure
|
|
689
|
+
generation evidence. Only a proven-absent outcome is retry-safe; unresolved
|
|
690
|
+
outcomes never authorize a blind retry.
|
|
555
691
|
|
|
556
692
|
```bash
|
|
557
693
|
git nawabari push --session "$NAWABARI_SESSION_ID" \
|
|
@@ -610,11 +746,18 @@ ID, canonical worktree and branch identities, lifecycle state, and timestamps.
|
|
|
610
746
|
|
|
611
747
|
Ownership-changing writes use an exclusive repository-local lock and a synced
|
|
612
748
|
temporary file followed by atomic replacement. Concurrent creation cannot
|
|
613
|
-
silently duplicate an active worktree or branch. Lock recovery is conservative
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
749
|
+
silently duplicate an active worktree or branch. Lock recovery is conservative
|
|
750
|
+
and platform-qualified: stale-lock reclamation is supported only on Linux,
|
|
751
|
+
where the lock records a random token, PID, host, and the exact process-start
|
|
752
|
+
token from `/proc/<pid>/stat`. An owner is reclaimed only when the same host
|
|
753
|
+
proves that exact process identity is dead; elapsed age and PID liveness alone
|
|
754
|
+
are never reclaim authority. On non-Linux platforms, Node can run ordinary
|
|
755
|
+
Nawabari operations but does not provide a safe process-generation identity,
|
|
756
|
+
so stale local locks remain `LOCK_STALE` and require deliberate operator
|
|
757
|
+
remediation. Invalid, remote, or otherwise unverifiable lock metadata is never
|
|
758
|
+
stolen and fails closed. The same limitation is machine-readable under
|
|
759
|
+
`capabilities --json` at the `session-lifecycle.registry_lock_recovery`
|
|
760
|
+
contract.
|
|
618
761
|
|
|
619
762
|
### Conformance and extraction boundary
|
|
620
763
|
|
|
@@ -639,6 +782,98 @@ The relevant Mottainai #28 execution cases are mapped as follows:
|
|
|
639
782
|
Run `pnpm run test:package` to validate the exact packed tarball and its
|
|
640
783
|
installed CLI, or `pnpm run verify` for the complete local conformance gate.
|
|
641
784
|
|
|
785
|
+
## Exact packed standalone protected-execution evidence
|
|
786
|
+
|
|
787
|
+
`pnpm run test:package:protected` is the package/evidence gate for the
|
|
788
|
+
standalone protected product. It creates one exact `pnpm pack` archive, records its
|
|
789
|
+
package/version, filename, byte size, SHA-256, source revision, and host
|
|
790
|
+
identity in `test-artifacts/packed-standalone-protected-execution.json`,
|
|
791
|
+
validates the archive contents, and installs that archive into a fresh
|
|
792
|
+
temporary consumer with `npm install --offline`. The default evidence report
|
|
793
|
+
is ignored by Git; pass `--evidence-output <path>` to retain it elsewhere and
|
|
794
|
+
`--keep-tarball` to retain the exact archive for inspection.
|
|
795
|
+
|
|
796
|
+
The smoke test invokes only the installed `nawabari` bin. Its package allowlist
|
|
797
|
+
contains `dist` runtime artifacts, `README.md`, `LICENSE`, and `package.json`;
|
|
798
|
+
source, test, and script modules are rejected, and the installed manifest must
|
|
799
|
+
not declare runtime dependencies. The lifecycle proof goes through the
|
|
800
|
+
installed public CLI for `capabilities --json`, `doctor --json`, session
|
|
801
|
+
creation/resolution, `session run`, resource claims, checkpoint, commit,
|
|
802
|
+
local-bare-remote push, and close. The gate requires the protected doctor
|
|
803
|
+
report to be ready; unavailable protected execution is a failure, not a skip
|
|
804
|
+
or an ambient fallback. The fixture uses only a temporary local repository and
|
|
805
|
+
local bare remote and has no Mottainai, GitHub, `gh`, LLM, or network
|
|
806
|
+
dependency.
|
|
807
|
+
|
|
808
|
+
The ordinary `pnpm run test:package` command runs the package/install smoke
|
|
809
|
+
without requiring a real protected host, so it retains the existing
|
|
810
|
+
compatibility CI job; it still verifies fail-closed rejection when protection
|
|
811
|
+
is unavailable. The `pnpm run test:package:protected` command is the #149
|
|
812
|
+
evidence command and must be run on a supported Linux host. It requires a
|
|
813
|
+
ready protected profile and fails when that prerequisite is unavailable.
|
|
814
|
+
|
|
815
|
+
The evidence supports these boundaries only:
|
|
816
|
+
|
|
817
|
+
- Process: on Linux with the required capabilities, the canonical launcher
|
|
818
|
+
establishes bubblewrap user, mount, PID, IPC, and UTS namespaces, applies
|
|
819
|
+
the versioned seccomp profile, and drops ambient capabilities. This is not a
|
|
820
|
+
VM or a claim about every host process.
|
|
821
|
+
- Filesystem: the protected child receives the authoritative session worktree
|
|
822
|
+
read-write, private session HOME/cache/`/tmp`/`/proc`, repository-owned
|
|
823
|
+
shared HOME state, and the fixed read-only runtime/tool inputs selected by
|
|
824
|
+
the profile. Sibling worktrees and unselected host HOME paths are not part
|
|
825
|
+
of the selected topology.
|
|
826
|
+
- HOME/cache: the child sees `/home/nawabari`; private state is per session,
|
|
827
|
+
while only the repository's selected shared-home subtree is shared. Selected
|
|
828
|
+
host tool directories are read-only inputs, not the host HOME.
|
|
829
|
+
- Network: `network_mode` is `inherited`; this evidence does not prove egress
|
|
830
|
+
isolation.
|
|
831
|
+
- Linux prerequisites: use `nawabari doctor --json` on a supported Linux host.
|
|
832
|
+
The package's supported Node.js engine, Git, and required bubblewrap,
|
|
833
|
+
namespace, seccomp, and capability support must be available and ready;
|
|
834
|
+
cgroups v2 and Landlock remain profile-reported optional defenses.
|
|
835
|
+
- Failure behavior: protected resolution uses `enforce: true`; missing
|
|
836
|
+
required capability, unsupported topology, or launch failure returns a
|
|
837
|
+
bounded failure and never retries through ambient execution.
|
|
838
|
+
|
|
839
|
+
## Packed Mottainai preselected-UID handoff evidence
|
|
840
|
+
|
|
841
|
+
`pnpm run test:package:mottainai` is the required-CI external-handoff gate for
|
|
842
|
+
Issue #150. `scripts/run-mottainai-uid-handoff-gate.mjs` runs the #149
|
|
843
|
+
protected package gate exactly once, retains that exact tarball and its
|
|
844
|
+
`nawabari.packed-standalone-protected-execution.v1` evidence, and passes only
|
|
845
|
+
those exact paths into `scripts/run-mottainai-uid-handoff.mjs`, which installs
|
|
846
|
+
that same tarball into a disposable consumer. `run-mottainai-uid-handoff.mjs`
|
|
847
|
+
itself has no self-pack path: it always requires an already-produced
|
|
848
|
+
`--tarball`/`--artifact-evidence` pair, so #150 can never hide a stale or
|
|
849
|
+
mismatched artifact behind a second pack. The fixture invokes the installed
|
|
850
|
+
`session run` contract under two distinct preselected unprivileged UIDs
|
|
851
|
+
(23001 and 23002 by default) and records bounded
|
|
852
|
+
artifact/fixture/UID/session/worktree/resource/Git evidence in
|
|
853
|
+
`test-artifacts/mottainai-packed-uid-handoff.json`. This gate runs in required
|
|
854
|
+
CI on a Linux host with bubblewrap (`mottainai-uid-handoff` job).
|
|
855
|
+
|
|
856
|
+
The checked-in `scripts/test-fixtures/mottainai-preselected-uid-runner.sh` is a
|
|
857
|
+
UID-only execution adapter. A real Mottainai Runtime may supply another
|
|
858
|
+
executable with the same `--uid`, `--root`, `--cwd`, `--` argv contract:
|
|
859
|
+
|
|
860
|
+
```bash
|
|
861
|
+
node scripts/run-mottainai-uid-handoff.mjs \
|
|
862
|
+
--tarball nawabari-0.7.1.tgz \
|
|
863
|
+
--artifact-evidence test-artifacts/packed-standalone-protected-execution.json \
|
|
864
|
+
--fixture-runner /path/to/mottainai-uid-runner \
|
|
865
|
+
--uids 23001,23002 \
|
|
866
|
+
--output test-artifacts/mottainai-packed-uid-handoff.json
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
The runner supplies only the caller's OS UID/GID view. Nawabari remains the
|
|
870
|
+
sole authority for session, worktree, resource claims, protected execution,
|
|
871
|
+
checkpoint, commit, push, local Git, and close; the fixture creates no
|
|
872
|
+
repository-principal registry and passes no Mottainai task, policy, or
|
|
873
|
+
credential semantics. Identical display names, branch names, and resource
|
|
874
|
+
paths are used under different canonical repository paths, and the protected
|
|
875
|
+
child's session/cwd marker is checked to prevent principal confusion.
|
|
876
|
+
|
|
642
877
|
## Development
|
|
643
878
|
|
|
644
879
|
```bash
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type CliHelpOptionSpec = {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly aliases?: readonly string[];
|
|
4
|
+
readonly alias_of?: string;
|
|
5
|
+
readonly value?: string;
|
|
6
|
+
readonly required?: boolean;
|
|
7
|
+
readonly default?: string;
|
|
8
|
+
readonly minimum?: number;
|
|
9
|
+
readonly maximum?: number;
|
|
10
|
+
readonly repeatable?: boolean;
|
|
11
|
+
/** Closed values projected from the authority that validates this option. */
|
|
12
|
+
readonly values?: readonly string[];
|
|
13
|
+
/** This option remains required unless one of these additive selectors is present. */
|
|
14
|
+
readonly required_unless?: readonly string[];
|
|
15
|
+
/** Selectors that cannot be combined with this option. */
|
|
16
|
+
readonly mutually_exclusive_with?: readonly string[];
|
|
17
|
+
readonly description: string;
|
|
18
|
+
};
|
|
19
|
+
export type CliCommandDefinition = {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly aliases?: readonly string[];
|
|
22
|
+
readonly summary: string;
|
|
23
|
+
readonly usage: string;
|
|
24
|
+
readonly options: readonly CliHelpOptionSpec[];
|
|
25
|
+
readonly notes?: readonly string[];
|
|
26
|
+
};
|
|
27
|
+
export declare const GLOBAL_HELP_OPTIONS: readonly CliHelpOptionSpec[];
|
|
28
|
+
/**
|
|
29
|
+
* Canonical public command/discovery registry.
|
|
30
|
+
*
|
|
31
|
+
* Dispatcher implementation deliberately remains below in this module. This
|
|
32
|
+
* registry only describes the public discovery surface; aliases point at the
|
|
33
|
+
* canonical command so option metadata cannot drift between projections.
|
|
34
|
+
*/
|
|
35
|
+
export declare const CLI_COMMAND_REGISTRY: readonly CliCommandDefinition[];
|
|
36
|
+
export declare const ROOT_HELP_SPEC: CliCommandDefinition;
|
|
37
|
+
/** Resolve a public name through the one canonical registry. */
|
|
38
|
+
export declare function canonicalCommandForName(name: string): CliCommandDefinition | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Materialize public names for projections without copying option metadata.
|
|
41
|
+
* Alias entries are generated from their canonical definition, including the
|
|
42
|
+
* usage, options, and parser notes.
|
|
43
|
+
*/
|
|
44
|
+
export declare function publicCliCommandDefinitions(): readonly CliCommandDefinition[];
|
|
45
|
+
/** Stable alias for consumers such as the future dispatcher parity layer. */
|
|
46
|
+
export declare const COMMAND_REGISTRY: readonly CliCommandDefinition[];
|
|
47
|
+
/** Resolve either a canonical command or one of its public aliases. */
|
|
48
|
+
export declare function resolveCliCommandDefinition(name: string): CliCommandDefinition | undefined;
|
|
49
|
+
/** Return the complete public discovery name list in registry order. */
|
|
50
|
+
export declare function publicCliCommandNames(): readonly string[];
|