nawabari 0.7.0 → 0.8.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/README.md +262 -32
- package/dist/cli-command-registry.d.ts +44 -0
- package/dist/cli-command-registry.js +488 -0
- package/dist/cli-command-registry.js.map +1 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +345 -584
- package/dist/cli.js.map +1 -1
- package/dist/contract.d.ts +16 -0
- package/dist/contract.js +505 -220
- 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 +20 -13
- package/dist/domain/doctor.js.map +1 -1
- package/dist/domain/errors.d.ts +7 -1
- package/dist/domain/errors.js +45 -0
- 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 +3 -1
- package/dist/domain/session-backend.js +155 -1
- package/dist/domain/session-backend.js.map +1 -1
- package/dist/domain/session.d.ts +128 -1
- package/dist/domain/session.js.map +1 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.js.map +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 +3 -3
- 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 +17 -2
- package/dist/resource-claims.js +98 -27
- package/dist/resource-claims.js.map +1 -1
- package/dist/session-lifecycle-actions.d.ts +63 -0
- package/dist/session-lifecycle-actions.js +108 -0
- package/dist/session-lifecycle-actions.js.map +1 -0
- package/dist/session-lifecycle-classification.d.ts +67 -0
- package/dist/session-lifecycle-classification.js +355 -0
- package/dist/session-lifecycle-classification.js.map +1 -0
- package/dist/session-registry.d.ts +133 -3
- package/dist/session-registry.js +984 -91
- package/dist/session-registry.js.map +1 -1
- package/package.json +25 -25
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`,
|
|
@@ -89,6 +130,18 @@ The `resource-claims` capability additionally exposes a machine-readable
|
|
|
89
130
|
surface documented above, so a caller can discover this contract instead of
|
|
90
131
|
assuming it from the CLI help text.
|
|
91
132
|
|
|
133
|
+
Resource-claim semantics are generation `nawabari.resource-claims.v2` with
|
|
134
|
+
claim-record schema `2`. The standalone envelope deliberately remains
|
|
135
|
+
`nawabari.standalone-execution.v1`: this is a meaning-compatible top-level
|
|
136
|
+
identity, while callers select the child resource-claim generation before
|
|
137
|
+
operating. A future meaning-changing claim authorization, conflict, transition,
|
|
138
|
+
release, or required-mode change must publish a new resource-claim generation
|
|
139
|
+
and identity; the package version alone is never a compatibility decision.
|
|
140
|
+
The capability binds every lifecycle command and alias to its result schema,
|
|
141
|
+
implementation-owned stable failure vocabulary, transition-matrix identity,
|
|
142
|
+
CAS/force and rejected-non-mutation guarantees, and deterministic recovery
|
|
143
|
+
action schema.
|
|
144
|
+
|
|
92
145
|
The supported standalone sequence is:
|
|
93
146
|
|
|
94
147
|
```text
|
|
@@ -111,11 +164,11 @@ The result schemas expose the following identities:
|
|
|
111
164
|
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
165
|
| session lifecycle | `session_id`, `repository`, `worktree`, `branch`, `state` |
|
|
113
166
|
| explicit discard | `previous_head`, `worktree_removed`, `branch_removed`, `released_claims`, `final_state`, final `session.state`/`terminal_operation` |
|
|
114
|
-
| claims | `claim_id`, `session_id`, `resource`, `mode`
|
|
167
|
+
| claims | `claim_id`, `session_id`, `resource`, `mode`, `claim_set_generation`, `previous_claim_set_generation` |
|
|
115
168
|
| authorization | `operation`, `allowed`, `code`, `claim_ids` |
|
|
116
169
|
| checkpoint evidence | `head`, `changed`, `staged`, `unstaged`, `untracked`, `in_claim`, `out_of_claim` |
|
|
117
|
-
| repository evidence | `session_id`, `base_revision`, `head`, `clean`, `paths.stats`, `evidence_hash`
|
|
118
|
-
| 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` |
|
|
119
172
|
| commit/push | `commit_sha`, `remote`, `branch`, `target`, `relation` |
|
|
120
173
|
| reconciliation/cleanup | `clean`, `issues`, `candidates`, `cleaned`, `blocked`, `recovery_hints` |
|
|
121
174
|
|
|
@@ -147,13 +200,24 @@ New sessions persist the exact creation/base revision as `base_revision`;
|
|
|
147
200
|
legacy records that lack this field report `base_revision: null` and
|
|
148
201
|
`base_revision_proven: false` rather than inferring it from a mutable ref.
|
|
149
202
|
|
|
203
|
+
`session_updated_at` is the UTC timestamp of the last authoritative
|
|
204
|
+
session-state mutation, not a general Git filesystem mtime. A successful
|
|
205
|
+
Nawabari-managed `commit` is such a mutation, so the registry timestamp is
|
|
206
|
+
persisted before the command returns and a subsequent snapshot reports it with
|
|
207
|
+
the resulting `head`. Git changes made outside Nawabari do not advance this
|
|
208
|
+
field; consumers must use the Git-observed fields for those changes.
|
|
209
|
+
|
|
150
210
|
`diff` requires at least one explicit concrete path and never accepts a glob or
|
|
151
211
|
an empty repository-wide selection. Stats are returned by default. Patch text
|
|
152
212
|
requires `--patch` and is bounded to at most 64 paths, 64 KiB, and 128 hunks;
|
|
153
213
|
the caller may request smaller limits. Unrepresentable Git observations fail
|
|
154
214
|
with `GIT_STATE_AMBIGUOUS`; a requested path whose stat is not exposed by Git
|
|
155
215
|
remains in the result with `available: false` and makes snapshot evidence
|
|
156
|
-
`complete: false`, so no path silently disappears.
|
|
216
|
+
`complete: false`, so no path silently disappears. Such paths include a
|
|
217
|
+
bounded `diagnostics` entry; a target directly observed as untracked reports
|
|
218
|
+
`reason: UNTRACKED_TARGET`, while other unavailable-stat causes remain
|
|
219
|
+
`STAT_UNAVAILABLE`. These diagnostics are read-only and do not stage or add
|
|
220
|
+
files.
|
|
157
221
|
|
|
158
222
|
## Session lifecycle
|
|
159
223
|
|
|
@@ -204,8 +268,9 @@ destructive cleanup. A clean close releases only the owned worktree and
|
|
|
204
268
|
branch, and repeating close is idempotent. `gc` detects stale or interrupted
|
|
205
269
|
sessions; `--apply` uses the same close safety checks and reports blocked
|
|
206
270
|
sessions instead of guessing. `gc --dry-run` performs the same non-mutating
|
|
207
|
-
cleanup preflight
|
|
208
|
-
|
|
271
|
+
cleanup preflight, reports age/physical/lifecycle suspicion separately from
|
|
272
|
+
destructive eligibility, and includes stable blocker codes and
|
|
273
|
+
`recovery_hints` for every eligible candidate that is not safe. Cleanup revalidates the physical worktree,
|
|
209
274
|
branch, and `HEAD` observations immediately before each destructive Git
|
|
210
275
|
operation.
|
|
211
276
|
|
|
@@ -226,12 +291,15 @@ history view; closed records remain persisted and are never silently deleted
|
|
|
226
291
|
by listing or cleanup.
|
|
227
292
|
|
|
228
293
|
`gc` stale eligibility is separate from closed-history retention. Its default
|
|
229
|
-
threshold is 24 hours (`86,400,000` ms), measured from persisted `updated_at
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
294
|
+
threshold is 24 hours (`86,400,000` ms), measured from persisted `updated_at`.
|
|
295
|
+
Elapsed age is diagnostic suspicion only: it never authorizes destructive
|
|
296
|
+
cleanup for a physically healthy active session. Records already in `stale` or
|
|
297
|
+
`closing` state are eligible, as is an otherwise live record when Git reports
|
|
298
|
+
its registered worktree as safely prunable and missing. Ambiguous physical
|
|
299
|
+
state remains ineligible and fail-closed. `gc --dry-run` exposes suspicion and
|
|
300
|
+
destructive eligibility/reason separately for each candidate; `gc --apply`
|
|
301
|
+
uses only eligible candidates. Closed records are never stale cleanup
|
|
302
|
+
candidates.
|
|
235
303
|
|
|
236
304
|
`doctor` includes a non-destructive `reconciliation` check. It reports
|
|
237
305
|
registry/Git ownership drift, including missing or prunable worktrees and
|
|
@@ -246,16 +314,26 @@ code. Claim JSON exposes `schema_version`, `claim_id`, `session_id`, the
|
|
|
246
314
|
repository/worktree identities, canonical `resource`, `mode`, and timestamps.
|
|
247
315
|
The claim schema version is `2` and supports `read`, `write`, and
|
|
248
316
|
`exclusive-write`. Schema v1 records use different overlap semantics and are
|
|
249
|
-
not interpreted implicitly
|
|
250
|
-
`
|
|
317
|
+
not interpreted implicitly. If an upgraded repository reports
|
|
318
|
+
`UNSUPPORTED_CLAIM_SCHEMA_VERSION`, run the public migration command:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
git nawabari migrate --json
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Migration validates the complete legacy registry under the registry lock and
|
|
325
|
+
rewrites it with an atomic replace. It is idempotent and retry-safe; ambiguous
|
|
326
|
+
or corrupt state is rejected with bounded diagnostics. Do not hand-edit or
|
|
327
|
+
delete the registry. Embedding callers may use the same authority through
|
|
328
|
+
`SessionRegistry.migrate()`.
|
|
251
329
|
|
|
252
330
|
```bash
|
|
253
331
|
git nawabari session claim --session "$NAWABARI_SESSION_ID" \
|
|
254
332
|
--resource 'src/**/*.ts' --mode read --json
|
|
255
333
|
git nawabari session claims --session "$NAWABARI_SESSION_ID" --json
|
|
256
334
|
git nawabari session update --session "$NAWABARI_SESSION_ID" \
|
|
257
|
-
--resource 'src/**/*.ts' --mode write --json
|
|
258
|
-
git nawabari session release --session "$NAWABARI_SESSION_ID" --json
|
|
335
|
+
--resource 'src/**/*.ts' --mode write --force --json
|
|
336
|
+
git nawabari session release --session "$NAWABARI_SESSION_ID" --all --force --json
|
|
259
337
|
```
|
|
260
338
|
|
|
261
339
|
`session update` (and its `resource update` alias) atomically replaces a
|
|
@@ -280,6 +358,24 @@ complete desired set again is idempotent. A successful replacement's JSON
|
|
|
280
358
|
exposes the resulting `claims` together with machine-readable `added` and
|
|
281
359
|
`released` claims.
|
|
282
360
|
|
|
361
|
+
The complete public claim lifecycle is:
|
|
362
|
+
|
|
363
|
+
```text
|
|
364
|
+
session claim/resource claim (additive acquire)
|
|
365
|
+
-> session transition/resource transition (one exact-resource mode change)
|
|
366
|
+
-> session mutate/resource mutate (atomic exact-resource deltas)
|
|
367
|
+
-> session release/resource release (--resource, --claim-id, or explicit --all)
|
|
368
|
+
-> session update/resource update (atomic complete-set replacement)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
All destructive mutations require exactly one `--if-generation` CAS or
|
|
372
|
+
explicit `--force`. A stale CAS returns `STALE_CLAIM_SET` without changing
|
|
373
|
+
claims or generation. Additive claim is not replacement; selected release
|
|
374
|
+
preserves unrelated claims; `--all` is the unambiguous all-claims selector.
|
|
375
|
+
An exact contradictory additive claim remains rejected with
|
|
376
|
+
`CONTRADICTORY_CLAIM` and may carry the typed `transition-exact-resource`
|
|
377
|
+
recovery action, whose generation is directly usable as the transition CAS.
|
|
378
|
+
|
|
283
379
|
The modes have these normative meanings:
|
|
284
380
|
|
|
285
381
|
- `read`: a non-mutating access declaration. It is not a consistency lease,
|
|
@@ -307,6 +403,14 @@ garbage-collecting a session releases its claims; no separate claim registry
|
|
|
307
403
|
or claim lock exists. Claims describe ownership state only and do not provide
|
|
308
404
|
OS-level filesystem observation or a filesystem sandbox.
|
|
309
405
|
|
|
406
|
+
For an existing path, canonicalization follows the physical directory entry,
|
|
407
|
+
so a case-insensitive filesystem cannot give `README.md` and `readme.md`
|
|
408
|
+
independent claim identities. This is determined from the filesystem entry
|
|
409
|
+
itself; paths that do not exist yet, and wildcard portions of a claim, retain
|
|
410
|
+
their exact lexical case because their future physical identity is unknown.
|
|
411
|
+
On a case-sensitive filesystem an alternate-case spelling is therefore a
|
|
412
|
+
distinct (possibly future) path, and no global lowercasing is applied.
|
|
413
|
+
|
|
310
414
|
An ordinary source change uses `write` and can proceed while another session
|
|
311
415
|
holds a `read` declaration:
|
|
312
416
|
|
|
@@ -359,6 +463,16 @@ identifiers, in addition to the human-readable `message`. JSON and human
|
|
|
359
463
|
output always render the identical underlying result; only the formatting
|
|
360
464
|
differs.
|
|
361
465
|
|
|
466
|
+
Lifecycle diagnostics additionally expose `next_action` (and the bounded
|
|
467
|
+
`next_actions` list) as typed, non-mutating caller actions. The action schema
|
|
468
|
+
is versioned independently and currently includes `retain-session`,
|
|
469
|
+
`supply-exact-integrated-revision`,
|
|
470
|
+
`retry-close-with-bounded-integration-fetch`, `discard-session`, and
|
|
471
|
+
`reconcile-physical-state`. A discard action always carries explicit intent;
|
|
472
|
+
ambiguous or terminal states never advertise destructive actions. These
|
|
473
|
+
fields are additive to `session-diagnostic.v1`, so existing consumers may
|
|
474
|
+
continue using `safe_actions`.
|
|
475
|
+
|
|
362
476
|
- **`RESOURCE_CLAIM_CONFLICT`** (`session claim`/`session update`,
|
|
363
477
|
`authorize`, `guard --operation`) reports the blocking claim
|
|
364
478
|
(`ownerClaimId`, `ownerResource`, `ownerMode`) and the blocking session's
|
|
@@ -491,6 +605,16 @@ successful commit — it fails with `COMMIT_RESULT_DIVERGED`, which retains the
|
|
|
491
605
|
resulting `commitSha` (the Git commit already happened) alongside the
|
|
492
606
|
authorized, actual, and divergent path sets for recovery/reconciliation.
|
|
493
607
|
|
|
608
|
+
If Git reports a bounded transport failure (timeout, output limit, or spawn
|
|
609
|
+
failure) after the commit invocation, Nawabari re-reads the local `HEAD` and
|
|
610
|
+
that commit's bounded changed-path set before classifying the outcome. A
|
|
611
|
+
failure whose `HEAD` is unchanged is reported with `outcome: "proven-absent"`
|
|
612
|
+
and `retrySafe: true`; a matching new commit is returned as a successful
|
|
613
|
+
result with `reconciliation.outcome: "proven-committed"` and its resulting
|
|
614
|
+
SHA; if either observation is unavailable or does not match the authorized
|
|
615
|
+
paths, the failure carries `outcome: "unresolved"` and `retrySafe: false`.
|
|
616
|
+
Unresolved outcomes never authorize a blind retry.
|
|
617
|
+
|
|
494
618
|
```bash
|
|
495
619
|
git nawabari commit --session "$NAWABARI_SESSION_ID" \
|
|
496
620
|
--message 'record the local change' --resource src/example.ts --json
|
|
@@ -516,12 +640,19 @@ Governed push requires explicit claim-covered resources and an explicit
|
|
|
516
640
|
`--remote`/`--branch` target. Existing upstream and local/remote relation are
|
|
517
641
|
inspected before mutation. A missing upstream requires `--create-upstream`;
|
|
518
642
|
behind or diverged history requires explicit `--force`. Force pushes use an
|
|
519
|
-
exact `--force-with-lease` bound to the observed remote branch SHA
|
|
520
|
-
pushes
|
|
521
|
-
|
|
522
|
-
|
|
643
|
+
exact `--force-with-lease` bound to the observed remote branch SHA. Ordinary
|
|
644
|
+
pushes use the same exact-generation lease, while force authorization remains
|
|
645
|
+
separate. A new remote branch uses an empty lease, requiring the target ref to
|
|
646
|
+
remain absent. Nawabari fetches only the explicit remote branch into a
|
|
647
|
+
disposable ref when local ancestry is missing; it does not update tracking
|
|
648
|
+
refs or fetch unrelated branches/tags.
|
|
523
649
|
The JSON result includes the immutable `source_sha`, explicit `target_ref`,
|
|
524
|
-
observed `observed_remote_sha`, and relation.
|
|
650
|
+
observed `observed_remote_sha`, and relation. If a bounded transport failure
|
|
651
|
+
may have happened after the remote mutation, Nawabari re-observes only that
|
|
652
|
+
exact remote ref. It reports `reconciliation.outcome` as `proven-pushed`,
|
|
653
|
+
`proven-absent`, or `unresolved`, with the exact precondition and post-failure
|
|
654
|
+
generation evidence. Only a proven-absent outcome is retry-safe; unresolved
|
|
655
|
+
outcomes never authorize a blind retry.
|
|
525
656
|
|
|
526
657
|
```bash
|
|
527
658
|
git nawabari push --session "$NAWABARI_SESSION_ID" \
|
|
@@ -580,11 +711,18 @@ ID, canonical worktree and branch identities, lifecycle state, and timestamps.
|
|
|
580
711
|
|
|
581
712
|
Ownership-changing writes use an exclusive repository-local lock and a synced
|
|
582
713
|
temporary file followed by atomic replacement. Concurrent creation cannot
|
|
583
|
-
silently duplicate an active worktree or branch. Lock recovery is conservative
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
714
|
+
silently duplicate an active worktree or branch. Lock recovery is conservative
|
|
715
|
+
and platform-qualified: stale-lock reclamation is supported only on Linux,
|
|
716
|
+
where the lock records a random token, PID, host, and the exact process-start
|
|
717
|
+
token from `/proc/<pid>/stat`. An owner is reclaimed only when the same host
|
|
718
|
+
proves that exact process identity is dead; elapsed age and PID liveness alone
|
|
719
|
+
are never reclaim authority. On non-Linux platforms, Node can run ordinary
|
|
720
|
+
Nawabari operations but does not provide a safe process-generation identity,
|
|
721
|
+
so stale local locks remain `LOCK_STALE` and require deliberate operator
|
|
722
|
+
remediation. Invalid, remote, or otherwise unverifiable lock metadata is never
|
|
723
|
+
stolen and fails closed. The same limitation is machine-readable under
|
|
724
|
+
`capabilities --json` at the `session-lifecycle.registry_lock_recovery`
|
|
725
|
+
contract.
|
|
588
726
|
|
|
589
727
|
### Conformance and extraction boundary
|
|
590
728
|
|
|
@@ -609,6 +747,98 @@ The relevant Mottainai #28 execution cases are mapped as follows:
|
|
|
609
747
|
Run `pnpm run test:package` to validate the exact packed tarball and its
|
|
610
748
|
installed CLI, or `pnpm run verify` for the complete local conformance gate.
|
|
611
749
|
|
|
750
|
+
## Exact packed standalone protected-execution evidence
|
|
751
|
+
|
|
752
|
+
`pnpm run test:package:protected` is the package/evidence gate for the
|
|
753
|
+
standalone protected product. It creates one exact `pnpm pack` archive, records its
|
|
754
|
+
package/version, filename, byte size, SHA-256, source revision, and host
|
|
755
|
+
identity in `test-artifacts/packed-standalone-protected-execution.json`,
|
|
756
|
+
validates the archive contents, and installs that archive into a fresh
|
|
757
|
+
temporary consumer with `npm install --offline`. The default evidence report
|
|
758
|
+
is ignored by Git; pass `--evidence-output <path>` to retain it elsewhere and
|
|
759
|
+
`--keep-tarball` to retain the exact archive for inspection.
|
|
760
|
+
|
|
761
|
+
The smoke test invokes only the installed `nawabari` bin. Its package allowlist
|
|
762
|
+
contains `dist` runtime artifacts, `README.md`, `LICENSE`, and `package.json`;
|
|
763
|
+
source, test, and script modules are rejected, and the installed manifest must
|
|
764
|
+
not declare runtime dependencies. The lifecycle proof goes through the
|
|
765
|
+
installed public CLI for `capabilities --json`, `doctor --json`, session
|
|
766
|
+
creation/resolution, `session run`, resource claims, checkpoint, commit,
|
|
767
|
+
local-bare-remote push, and close. The gate requires the protected doctor
|
|
768
|
+
report to be ready; unavailable protected execution is a failure, not a skip
|
|
769
|
+
or an ambient fallback. The fixture uses only a temporary local repository and
|
|
770
|
+
local bare remote and has no Mottainai, GitHub, `gh`, LLM, or network
|
|
771
|
+
dependency.
|
|
772
|
+
|
|
773
|
+
The ordinary `pnpm run test:package` command runs the package/install smoke
|
|
774
|
+
without requiring a real protected host, so it retains the existing
|
|
775
|
+
compatibility CI job; it still verifies fail-closed rejection when protection
|
|
776
|
+
is unavailable. The `pnpm run test:package:protected` command is the #149
|
|
777
|
+
evidence command and must be run on a supported Linux host. It requires a
|
|
778
|
+
ready protected profile and fails when that prerequisite is unavailable.
|
|
779
|
+
|
|
780
|
+
The evidence supports these boundaries only:
|
|
781
|
+
|
|
782
|
+
- Process: on Linux with the required capabilities, the canonical launcher
|
|
783
|
+
establishes bubblewrap user, mount, PID, IPC, and UTS namespaces, applies
|
|
784
|
+
the versioned seccomp profile, and drops ambient capabilities. This is not a
|
|
785
|
+
VM or a claim about every host process.
|
|
786
|
+
- Filesystem: the protected child receives the authoritative session worktree
|
|
787
|
+
read-write, private session HOME/cache/`/tmp`/`/proc`, repository-owned
|
|
788
|
+
shared HOME state, and the fixed read-only runtime/tool inputs selected by
|
|
789
|
+
the profile. Sibling worktrees and unselected host HOME paths are not part
|
|
790
|
+
of the selected topology.
|
|
791
|
+
- HOME/cache: the child sees `/home/nawabari`; private state is per session,
|
|
792
|
+
while only the repository's selected shared-home subtree is shared. Selected
|
|
793
|
+
host tool directories are read-only inputs, not the host HOME.
|
|
794
|
+
- Network: `network_mode` is `inherited`; this evidence does not prove egress
|
|
795
|
+
isolation.
|
|
796
|
+
- Linux prerequisites: use `nawabari doctor --json` on a supported Linux host.
|
|
797
|
+
The package's supported Node.js engine, Git, and required bubblewrap,
|
|
798
|
+
namespace, seccomp, and capability support must be available and ready;
|
|
799
|
+
cgroups v2 and Landlock remain profile-reported optional defenses.
|
|
800
|
+
- Failure behavior: protected resolution uses `enforce: true`; missing
|
|
801
|
+
required capability, unsupported topology, or launch failure returns a
|
|
802
|
+
bounded failure and never retries through ambient execution.
|
|
803
|
+
|
|
804
|
+
## Packed Mottainai preselected-UID handoff evidence
|
|
805
|
+
|
|
806
|
+
`pnpm run test:package:mottainai` is the required-CI external-handoff gate for
|
|
807
|
+
Issue #150. `scripts/run-mottainai-uid-handoff-gate.mjs` runs the #149
|
|
808
|
+
protected package gate exactly once, retains that exact tarball and its
|
|
809
|
+
`nawabari.packed-standalone-protected-execution.v1` evidence, and passes only
|
|
810
|
+
those exact paths into `scripts/run-mottainai-uid-handoff.mjs`, which installs
|
|
811
|
+
that same tarball into a disposable consumer. `run-mottainai-uid-handoff.mjs`
|
|
812
|
+
itself has no self-pack path: it always requires an already-produced
|
|
813
|
+
`--tarball`/`--artifact-evidence` pair, so #150 can never hide a stale or
|
|
814
|
+
mismatched artifact behind a second pack. The fixture invokes the installed
|
|
815
|
+
`session run` contract under two distinct preselected unprivileged UIDs
|
|
816
|
+
(23001 and 23002 by default) and records bounded
|
|
817
|
+
artifact/fixture/UID/session/worktree/resource/Git evidence in
|
|
818
|
+
`test-artifacts/mottainai-packed-uid-handoff.json`. This gate runs in required
|
|
819
|
+
CI on a Linux host with bubblewrap (`mottainai-uid-handoff` job).
|
|
820
|
+
|
|
821
|
+
The checked-in `scripts/test-fixtures/mottainai-preselected-uid-runner.sh` is a
|
|
822
|
+
UID-only execution adapter. A real Mottainai Runtime may supply another
|
|
823
|
+
executable with the same `--uid`, `--root`, `--cwd`, `--` argv contract:
|
|
824
|
+
|
|
825
|
+
```bash
|
|
826
|
+
node scripts/run-mottainai-uid-handoff.mjs \
|
|
827
|
+
--tarball nawabari-0.7.1.tgz \
|
|
828
|
+
--artifact-evidence test-artifacts/packed-standalone-protected-execution.json \
|
|
829
|
+
--fixture-runner /path/to/mottainai-uid-runner \
|
|
830
|
+
--uids 23001,23002 \
|
|
831
|
+
--output test-artifacts/mottainai-packed-uid-handoff.json
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
The runner supplies only the caller's OS UID/GID view. Nawabari remains the
|
|
835
|
+
sole authority for session, worktree, resource claims, protected execution,
|
|
836
|
+
checkpoint, commit, push, local Git, and close; the fixture creates no
|
|
837
|
+
repository-principal registry and passes no Mottainai task, policy, or
|
|
838
|
+
credential semantics. Identical display names, branch names, and resource
|
|
839
|
+
paths are used under different canonical repository paths, and the protected
|
|
840
|
+
child's session/cwd marker is checked to prevent principal confusion.
|
|
841
|
+
|
|
612
842
|
## Development
|
|
613
843
|
|
|
614
844
|
```bash
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
readonly description: string;
|
|
12
|
+
};
|
|
13
|
+
export type CliCommandDefinition = {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly aliases?: readonly string[];
|
|
16
|
+
readonly summary: string;
|
|
17
|
+
readonly usage: string;
|
|
18
|
+
readonly options: readonly CliHelpOptionSpec[];
|
|
19
|
+
readonly notes?: readonly string[];
|
|
20
|
+
};
|
|
21
|
+
export declare const GLOBAL_HELP_OPTIONS: readonly CliHelpOptionSpec[];
|
|
22
|
+
/**
|
|
23
|
+
* Canonical public command/discovery registry.
|
|
24
|
+
*
|
|
25
|
+
* Dispatcher implementation deliberately remains below in this module. This
|
|
26
|
+
* registry only describes the public discovery surface; aliases point at the
|
|
27
|
+
* canonical command so option metadata cannot drift between projections.
|
|
28
|
+
*/
|
|
29
|
+
export declare const CLI_COMMAND_REGISTRY: readonly CliCommandDefinition[];
|
|
30
|
+
export declare const ROOT_HELP_SPEC: CliCommandDefinition;
|
|
31
|
+
/** Resolve a public name through the one canonical registry. */
|
|
32
|
+
export declare function canonicalCommandForName(name: string): CliCommandDefinition | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Materialize public names for projections without copying option metadata.
|
|
35
|
+
* Alias entries are generated from their canonical definition, including the
|
|
36
|
+
* usage, options, and parser notes.
|
|
37
|
+
*/
|
|
38
|
+
export declare function publicCliCommandDefinitions(): readonly CliCommandDefinition[];
|
|
39
|
+
/** Stable alias for consumers such as the future dispatcher parity layer. */
|
|
40
|
+
export declare const COMMAND_REGISTRY: readonly CliCommandDefinition[];
|
|
41
|
+
/** Resolve either a canonical command or one of its public aliases. */
|
|
42
|
+
export declare function resolveCliCommandDefinition(name: string): CliCommandDefinition | undefined;
|
|
43
|
+
/** Return the complete public discovery name list in registry order. */
|
|
44
|
+
export declare function publicCliCommandNames(): readonly string[];
|