@ai-ecoverse/slicc-kernel 1.177.0 → 1.178.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 CHANGED
@@ -1149,10 +1149,28 @@ The kernel runs scheduled jobs itself, with no daemon process: a cron service st
1149
1149
 
1150
1150
  A character device (`0666`) through which a program talks to the embedder's `sealed` hook (`createKernel({ sealed })`), the secrets vault on the page. Emscripten programs, WASI programs (`std::fs`) and JS programs (`ctx.open`, `ctx.sync.open`, listed by `ctx.fs.readdir('/dev')`) all open it, through the same kernel device, with the same framing, errnos and stamp: a JS program's stamp is built in the kernel from its own process, as for any other. The kernel holds the mode a descriptor was opened with, in every runtime, for its `dup`s and for the copies children inherit through fork, spawn and exec: one opened read-only refuses writes with `EBADF`, and one opened write-only refuses reads. Emscripten programs see it in `/dev` and open it by any path that leads to it (`./sealed` from `/dev`, `..`, a symlink to it or to `/dev`), and `fstat` and `/proc/self/fd` show it as `/dev/sealed`, a character device, after fork and exec too; WASI and JS programs open it by a path with no symlink on the way, and WASI programs by one without `..`.
1151
1151
  - **State per open:** each `open()` is independent; `dup` and fork share one, as on Linux.
1152
- - **One write is one request:** a single UTF-8 JSON object of at most 64 KiB with `v` 1 and a known `op` (`ask`, `seal`, `placeholder`, `list`, `test`, `request`, `status`, `cancel`, `materialize`). The write takes all the bytes or fails: `EMSGSIZE` over 64 KiB, `EINVAL` for anything else that isn't such an object. These are the only errnos; every other outcome is a JSON reply. The kernel zeroes the bytes it was given once it has parsed them.
1152
+ - **One write is one request:** a single UTF-8 JSON object of at most 64 KiB with `v` 1 and a known `op` (`ask`, `seal`, `placeholder`, `list`, `test`, `request`, `status`, `cancel`, `materialize`, `capabilities`, `oauthGrant`, `oauthStatus`, `oauthRefresh`, `oauthRemove`). The write takes all the bytes or fails: `EMSGSIZE` over 64 KiB, `EINVAL` for anything else that isn't such an object, including an invalid managed OAuth request shape. Managed OAuth also returns `ENOTSUP` at the write for reserved unsupported authentication or field-capture inputs, below. Every other outcome is a JSON reply. The kernel zeroes the bytes it was given once it has parsed them.
1153
1153
  - **The stamp:** the hook gets the request with `caller: { pid, ppid, uid, gid, user?, groups, users, tag? }`, from the process that writes (at the write; not the one that opened it): `uid` and `gid` the effective ids, `user` the `/etc/passwd` name of `uid` read at that moment (left out when there is none), `groups` the process's supplementary groups (only root can change them), `users` the kernel's `users` mode, since a uid means more under `'enforce'`, and `tag` the process's tag (see `kernel.run`) when it has one. A `caller` in the request is replaced.
1154
1154
  - **Reading:** the reply comes from the same descriptor, as JSON (`{ v: 1, … }`). A read waits for it, drains it, then returns end-of-file; with no request outstanding it returns end-of-file at once. A signal ends the wait with `EINTR` and the request stays outstanding, `O_NONBLOCK` gives `EAGAIN`, and `poll`/`select` report the descriptor readable once the reply is there. A new write drops an unread reply. The kernel zeroes its copy of a reply once it has been read to the end, dropped or closed.
1155
- - **The hook:** `signal` aborts with `'timeout'` after 10 s (the reply is then `{ v: 1, error: { code: 'ETIMEDOUT' } }`; an `ask` has 305 s, below), `'replaced'` when a new write on the descriptor comes first, and `'withdrawn'` when the writer exits or the descriptor is closed. A throw or a reply that isn't an object becomes an `EIO` error reply, with the first line of the message; no hook is `ENOTCONN`. On the page, `createKernel` relays the request from the kernel worker and back, and the abort too, with no time limit of its own, so an `ask` held open for minutes comes back as it was answered.
1155
+ - **The hook:** `signal` aborts with `'timeout'` after 10 s (the reply is then `{ v: 1, error: { code: 'ETIMEDOUT' } }`; an `ask` has 305 s and the four managed OAuth operations have 45 s, below), `'replaced'` when a new write on the descriptor comes first, and `'withdrawn'` when the writer exits or the descriptor is closed. A throw or a reply that isn't an object becomes an `EIO` error reply, with the first line of the message for legacy operations; managed OAuth and `capabilities` use fixed safe errors instead. No hook is `ENOTCONN`. On the page, `createKernel` relays the request from the kernel worker and back, and the abort too, with no time limit of its own, so an `ask` held open for minutes comes back as it was answered.
1156
+
1157
+ **Vault-managed OAuth (v1).** `capabilities`, `oauthGrant`, `oauthStatus`, `oauthRefresh` and `oauthRemove` carry public-client Bearer credentials to the vault; the kernel neither stores a refresh grant nor performs refresh network requests. `SealedOAuthRequest`, `SealedOAuthConfig`, `SealedOAuthIdentity`, `SealedOAuthStatus`, `SealedOAuthState`, `SealedOAuthError` and `SealedOAuthReply` are exported types from both the browser and Node entries. They describe device payloads; the existing hook adds its authoritative `caller`. No raw-grant or direct sealed request API is added to an attached client.
1158
+
1159
+ Probe before beginning a managed sign-in with `{ v: 1, op: 'capabilities' }`. The only successful shapes are `{ v: 1, capabilities: { kernelOAuth: 1 } }` and `{ v: 1, capabilities: {} }`. This is an end-to-end capability, not a kernel promise: compatible bios relays these five operations as the page envelope `{ op: 'sealed', req, stamp: caller, kernelOAuth: 1 }`, and the vault advertises or accepts managed OAuth only with that **outer** compatibility assertion. The assertion is not authorization. A process cannot supply it: nested `kernelOAuth`, `stamp`, `source`, `kind`, grant references and other unexpected request fields are `EINVAL`. A submitted `caller` is ignored and overwritten, including on inherited descriptors. An old kernel or vault can return `EINVAL` for the probe; offline, malformed and version-skewed replies never mean support. Deploy the supporting vault and compatible bios with a published supporting kernel before enabling the managed path; do not silently fall back to access-only sealing after a failed probe.
1160
+
1161
+ A registration is one device write, for example, with fake credentials:
1162
+
1163
+ ```json
1164
+ {"v":1,"op":"oauthGrant","name":"EXAMPLE_TOKEN","provider":"example","account":"default","accessToken":"fake-access-token","refreshToken":"fake-refresh-token","tokenType":"Bearer","expiresAt":1800003600000,"oauth":{"tokenUrl":"https://auth.example.invalid/token","clientId":"example-public-client","authMethod":"none","scope":"read","resource":"https://api.example.invalid/"}}
1165
+ ```
1166
+
1167
+ - Required fields are `name`, `provider`, `account`, `accessToken`, and `oauth` with `tokenUrl`, `clientId`, `authMethod`. `name` is an environment name, at most 128 characters; `provider` matches `^[a-z0-9][a-z0-9-]{0,127}$`; `account` is a nonempty selector of at most 256 characters with no C0/C1 controls, not an attested human identity. Tokens are nonempty strings. Optional `tokenType` defaults to Bearer and is compared case-insensitively without trimming; another type is `ENOTSUP`. Optional `expiresAt` is a nonnegative safe-integer epoch millisecond number, with the vault enforcing its lifetime limits. Optional `oauth.scope` is a string and `oauth.resource` a nonempty string. The vault validates canonical HTTPS token endpoints, client IDs, resource URLs, metadata secrecy and immutable binding; the kernel validates structure and does not choose endpoints.
1168
+ - Only `authMethod: 'none'` is supported. A different string auth method, or any `auth`, `clientSecret` or `fields` field at the request or OAuth-config level, is `ENOTSUP`, never processed or echoed. Other unexpected fields and wrong types are `EINVAL`. Confidential-client refresh, arbitrary auth headers/templates and captured fields are not supported. Existing confidential/access-only/field-capture paths remain legacy operations; this API does not widen their authority.
1169
+ - Without `refreshToken`, registration makes a managed non-refreshable access record and the vault atomically removes its previous owned grant. Without `expiresAt`, expiry is unknown, not guaranteed infinite validity. The vault owns atomic storage, protected-record replacement, enforced-user management checks, immutable provider/account/endpoint binding, linked-grant ownership and refresh rotation; caller claims never substitute for those checks.
1170
+ - Status, refresh and removal take only `{ v: 1, op: 'oauthStatus' | 'oauthRefresh' | 'oauthRemove', name, provider, account }`. Status does no network I/O; refresh forces at most one vault attempt or shares a concurrent successful refresh; removal soft-deletes the access record and its owned grant, not the upstream provider grant. Neither lookup nor refresh accepts new OAuth metadata. Legacy `placeholder`/`list` replies can carry `oauthManaged: true` for managed access records; the kernel preserves that marker unchanged, so clients must not treat such a record as legacy when the managed API is unavailable.
1171
+ - Grant/status/refresh replies have exactly `{ v: 1, placeholder, state, expiresAt, refreshable }`: a `fnox_proxy_` placeholder followed by 24 lowercase hex digits, state `ready`, `refreshing`, `expired`, `reauth-required`, `removed` or `locked`, `expiresAt` always a nonnegative safe integer or `null`, and boolean `refreshable`. `refreshing` means expired with a refresh source, not proof of current I/O; `locked` promises no usability, and `refreshable` describes structural availability, not network reachability. Removal returns exactly `{ v: 1, removed: true }`. No refresh placeholder, credential, OAuth configuration or provider response body can be returned.
1172
+ - An error is `{ v: 1, error: { code, cause?, message } }`, with code `EINVAL`, `ENOENT`, `EEXIST`, `EACCES`, `EAGAIN`, `ENOTSUP`, `ENOTCONN`, `ETIMEDOUT` or `EIO`, and optional cause `vault-locked`, `oauth-reauth-required` or `oauth-refresh-failed`. The kernel requires own `code` and string `message` fields, ignores supplied message text and generates fixed safe text. A missing, inherited or non-string message is malformed. Unknown codes/causes, unexpected fields, malformed types and a reply whose actual `v` is not 1 become fixed `EIO`; unlike legacy operations, a wrong reply version is never normalized into success. `ENOENT` alone means missing/non-managed; identity conflicts, lock, unavailable transport or other errors cannot justify legacy fallback.
1173
+ - These four operations have a 45-second kernel deadline; `capabilities` retains 10 seconds. Vault has a 40-second receipt-based budget and a bounded refresh lock. Aborts and late-reply discarding do not roll back a committed mutation: the current bios may only withdraw its local pending reply, without cancelling vault work. Never replay initial credentials after lost completion; reconcile later with exact-identity status. A blocked raw read can return `EINTR` without cancelling the request (JS reads restart after handled signals). The vault must reject stale refresh/drop/re-auth commits and publish scrub state before replying; bios must propagate that state before releasing successful mutation completion. These are embedder/vault obligations, not guarantees the kernel can infer from a reply. Old sign-ins whose refresh token was discarded need re-authentication.
1156
1174
 
1157
1175
  **Asking the user (`ask`).** A program asks the user for a secret with `{ v: 1, op: 'ask', name, prompt?, reveal?, replace? }`, and the vault answers it from a dialog. `name` matches `^[A-Za-z_][A-Za-z0-9_]*$` and is at most 128 characters, `prompt` is a string of at most 200 characters (code points), shown to the user as the program's text, and `reveal` and `replace` are booleans; any other field, or a field of another shape, is `EINVAL` at the write (a `caller` or `requester` is replaced, as below).
1158
1176
  - **What the hook gets:** `{ v: 1, op: 'ask', name, prompt?, reveal, replace, caller, requester }`, `reveal` and `replace` always there (`false` unless the program set them), `caller` the stamp above, and `requester: { exe, argv, cwd, tty, stdin }`, the kernel's facts about the writing process when the hook is asked: `exe` is `/usr/bin/<name>` for a command of the catalog, a kernel command such as `sealed` included, and otherwise the file `/proc/<pid>/exe` names, with its links resolved; `argv` the process's arguments, as `/proc/<pid>/cmdline` shows them; `cwd` its working directory, with its links resolved; `tty` the terminal it started on (`/dev/ttyN` or `/dev/pts/N`), or `null`; and `stdin` what its standard input is, as for [`sudo`](#sudo): `tty`, `none` (closed or `/dev/null`), `pipe` or `file`. A program can't supply or change any of it. A process the kernel has no record of (none of its own, so only under a custom embedding) gets an `EIO` reply and the hook isn't asked.