@irisrun/host 0.1.0 → 0.2.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 +37 -0
- package/dist/adapter.d.ts +2 -2
- package/dist/adapter.js +7 -7
- package/dist/capabilities.d.ts +1 -1
- package/dist/capabilities.js +7 -7
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @irisrun/host
|
|
2
|
+
|
|
3
|
+
**Same image, a different host — made explicit.** A host is just
|
|
4
|
+
`{name, capabilities, store, scheduler}`. Once the engine is pure and durability
|
|
5
|
+
lives in the journal, "run anywhere" stops being a slogan: any host that supplies
|
|
6
|
+
a store + scheduler can resume the same image, and a capability diff decides — at
|
|
7
|
+
deploy time, loudly — whether it's allowed to. Host-side; the core stays pure.
|
|
8
|
+
|
|
9
|
+
## What it is
|
|
10
|
+
|
|
11
|
+
`HostAdapter` is the four-field surface (`name`, `capabilities`, `store`,
|
|
12
|
+
`scheduler`). `runTurnOn(adapter, opts)` runs ONE turn on that host's store +
|
|
13
|
+
scheduler — a thin call into the engine's `runTurn` with the adapter's ports
|
|
14
|
+
injected, defaulting the writer `holderId` to the host name.
|
|
15
|
+
|
|
16
|
+
The deploy gate is two functions over an image's `CapabilityProfile`:
|
|
17
|
+
`diffCapabilities` returns the structured `CapabilityGap[]` (booleans like
|
|
18
|
+
`filesystem`/`websockets`, plus `tool_locality` ranked `remote < local <
|
|
19
|
+
in-process` against the host's ceiling); `assertDeployable` throws — joining every
|
|
20
|
+
gap's message — if the image can't run. `checkHostCapabilities` is the narrower
|
|
21
|
+
tool-level boolean refusal. Refuse LOUDLY, never silently degrade. Zero external
|
|
22
|
+
deps; depends on `@irisrun/core` + `@irisrun/agent` only.
|
|
23
|
+
|
|
24
|
+
## Use it
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { runTurnOn, assertDeployable, type HostAdapter } from "@irisrun/host";
|
|
28
|
+
|
|
29
|
+
assertDeployable(image.requires, host); // throws naming every gap, or returns
|
|
30
|
+
const outcome = await runTurnOn(host, { sessionId, defDigest, program, performers, clock });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
See **[docs/Architecture](../../docs/architecture.md)** and
|
|
34
|
+
**[docs/Deploy](../../docs/deploy.md)**.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
Part of [Iris](../../README.md) — own, portable, verifiable state.
|
package/dist/adapter.d.ts
CHANGED
|
@@ -24,10 +24,10 @@ export interface RunTurnOnOptions<S extends Json> {
|
|
|
24
24
|
* on any host → the engine's deterministic replay does the rest. */
|
|
25
25
|
export declare function runTurnOn<S extends Json>(adapter: HostAdapter, opts: RunTurnOnOptions<S>): Promise<TurnOutcome<S>>;
|
|
26
26
|
/**
|
|
27
|
-
* Tool/host-level capability check
|
|
27
|
+
* Tool/host-level capability check: for every capability the image
|
|
28
28
|
* REQUIRES (`requires[k] === true`), the host must PROVIDE it (`capabilities[k]
|
|
29
29
|
* === true`). `undefined`/`false` on the host means NOT satisfied — never silently
|
|
30
30
|
* widened (cf. the secure-floor posture). Refuses LOUDLY, naming the gaps; the
|
|
31
|
-
* full host capability-diff gate is deferred
|
|
31
|
+
* full host capability-diff gate is deferred.
|
|
32
32
|
*/
|
|
33
33
|
export declare function checkHostCapabilities(requires: CapabilityProfile, capabilities: CapabilityProfile, hostName?: string): void;
|
package/dist/adapter.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// HostAdapter
|
|
1
|
+
// HostAdapter: a host is just {name, capabilities, store, scheduler}.
|
|
2
2
|
// `runTurnOn` runs a turn ON a host's store+scheduler — the host convenience that
|
|
3
3
|
// makes "the SAME image, a DIFFERENT host" explicit. It REPLACES the framework's
|
|
4
4
|
// enterTurn(sessionId,event) member (there is nothing to replace — no core type);
|
|
5
5
|
// it is a thin call into the engine's runTurn with the adapter's ports injected.
|
|
6
|
-
// `checkHostCapabilities` is the tool/host-level
|
|
7
|
-
// capability-diff gate stays deferred
|
|
6
|
+
// `checkHostCapabilities` is the tool/host-level capability refusal (the FULL host
|
|
7
|
+
// capability-diff gate stays deferred). Host-side; core stays pure.
|
|
8
8
|
import { runTurn } from "@irisrun/core";
|
|
9
9
|
/** Run one turn on `adapter`'s store + scheduler. Same image (defDigest) + program
|
|
10
10
|
* on any host → the engine's deterministic replay does the rest. */
|
|
@@ -26,7 +26,7 @@ export async function runTurnOn(adapter, opts) {
|
|
|
26
26
|
}, opts.sessionId);
|
|
27
27
|
}
|
|
28
28
|
// The boolean capability keys (tool_locality is a profile STRING, not a gateable
|
|
29
|
-
// boolean, so it is not part of this refusal — the full degrade/refuse matrix is
|
|
29
|
+
// boolean, so it is not part of this refusal — the full degrade/refuse matrix is deferred).
|
|
30
30
|
const BOOLEAN_CAPS = [
|
|
31
31
|
"long_running",
|
|
32
32
|
"local_subprocess",
|
|
@@ -34,11 +34,11 @@ const BOOLEAN_CAPS = [
|
|
|
34
34
|
"websockets",
|
|
35
35
|
];
|
|
36
36
|
/**
|
|
37
|
-
* Tool/host-level capability check
|
|
37
|
+
* Tool/host-level capability check: for every capability the image
|
|
38
38
|
* REQUIRES (`requires[k] === true`), the host must PROVIDE it (`capabilities[k]
|
|
39
39
|
* === true`). `undefined`/`false` on the host means NOT satisfied — never silently
|
|
40
40
|
* widened (cf. the secure-floor posture). Refuses LOUDLY, naming the gaps; the
|
|
41
|
-
* full host capability-diff gate is deferred
|
|
41
|
+
* full host capability-diff gate is deferred.
|
|
42
42
|
*/
|
|
43
43
|
export function checkHostCapabilities(requires, capabilities, hostName = "host") {
|
|
44
44
|
const unmet = [];
|
|
@@ -48,6 +48,6 @@ export function checkHostCapabilities(requires, capabilities, hostName = "host")
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
if (unmet.length > 0) {
|
|
51
|
-
throw new Error(`checkHostCapabilities: '${hostName}' cannot satisfy required capabilities
|
|
51
|
+
throw new Error(`checkHostCapabilities: '${hostName}' cannot satisfy required capabilities: ${unmet.join("; ")}`);
|
|
52
52
|
}
|
|
53
53
|
}
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface CapabilityGap {
|
|
|
12
12
|
* checkHostCapabilities rule; tool_locality compares the image's DEMAND to the
|
|
13
13
|
* host's CEILING by the fixed rank remote<local<in-process. The local-tools-on-edge
|
|
14
14
|
* gap (local_subprocess required, OR tool_locality over ceiling) carries the
|
|
15
|
-
* literal
|
|
15
|
+
* literal refusal message; every other boolean gap carries the precise per-cap
|
|
16
16
|
* message. Never silently widens: undefined/false on the host = NOT satisfied.
|
|
17
17
|
*/
|
|
18
18
|
export declare function diffCapabilities(requires: CapabilityProfile, host: HostAdapter): CapabilityGap[];
|
package/dist/capabilities.js
CHANGED
|
@@ -21,9 +21,9 @@ function localityRank(v) {
|
|
|
21
21
|
// An absent demand defaults to `remote` (the least-demanding, edge-safe floor).
|
|
22
22
|
return LOCALITY_RANK[v ?? "remote"];
|
|
23
23
|
}
|
|
24
|
-
/** The literal
|
|
24
|
+
/** The literal host-capability refusal, interpolating the host
|
|
25
25
|
* label. With `name === "Cloudflare"` the result is byte-identical to the example. */
|
|
26
|
-
function
|
|
26
|
+
function localToolsRefusal(hostName) {
|
|
27
27
|
return `this agent requires local_subprocess tools; the ${hostName} target supports remote MCP tools only. Set tool_locality: remote or choose a VPS/serverless target.`;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
@@ -32,7 +32,7 @@ function adr0008Refusal(hostName) {
|
|
|
32
32
|
* checkHostCapabilities rule; tool_locality compares the image's DEMAND to the
|
|
33
33
|
* host's CEILING by the fixed rank remote<local<in-process. The local-tools-on-edge
|
|
34
34
|
* gap (local_subprocess required, OR tool_locality over ceiling) carries the
|
|
35
|
-
* literal
|
|
35
|
+
* literal refusal message; every other boolean gap carries the precise per-cap
|
|
36
36
|
* message. Never silently widens: undefined/false on the host = NOT satisfied.
|
|
37
37
|
*/
|
|
38
38
|
export function diffCapabilities(requires, host) {
|
|
@@ -52,7 +52,7 @@ export function diffCapabilities(requires, host) {
|
|
|
52
52
|
}
|
|
53
53
|
// The local-tools-on-edge dimension: local_subprocess demanded, OR the
|
|
54
54
|
// requested tool_locality is more host-demanding than the host's ceiling. Both
|
|
55
|
-
// surface as the literal
|
|
55
|
+
// surface as the literal refusal.
|
|
56
56
|
const ceiling = caps.tool_locality;
|
|
57
57
|
const wantsLocalSubprocess = requires.local_subprocess === true && caps.local_subprocess !== true;
|
|
58
58
|
const localityOverCeiling = localityRank(requires.tool_locality) > localityRank(ceiling);
|
|
@@ -61,7 +61,7 @@ export function diffCapabilities(requires, host) {
|
|
|
61
61
|
capability: "local_subprocess",
|
|
62
62
|
required: true,
|
|
63
63
|
hostProvides: caps.local_subprocess,
|
|
64
|
-
message:
|
|
64
|
+
message: localToolsRefusal(host.name),
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
if (localityOverCeiling) {
|
|
@@ -69,7 +69,7 @@ export function diffCapabilities(requires, host) {
|
|
|
69
69
|
capability: "tool_locality",
|
|
70
70
|
required: requires.tool_locality,
|
|
71
71
|
hostProvides: ceiling,
|
|
72
|
-
message:
|
|
72
|
+
message: localToolsRefusal(host.name),
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
return gaps;
|
|
@@ -84,7 +84,7 @@ export function assertDeployable(requires, host) {
|
|
|
84
84
|
if (gaps.length > 0) {
|
|
85
85
|
// De-duplicate identical messages before joining: an image demanding BOTH
|
|
86
86
|
// local_subprocess AND an over-ceiling tool_locality is ONE root cause (local
|
|
87
|
-
// tools on a remote-only host) and both gaps carry the same literal
|
|
87
|
+
// tools on a remote-only host) and both gaps carry the same literal
|
|
88
88
|
// refusal — the user-facing message must render that sentence ONCE, not twice.
|
|
89
89
|
// (diffCapabilities still returns both structured gaps for programmatic inspection.)
|
|
90
90
|
const messages = [...new Set(gaps.map((g) => g.message))];
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irisrun/host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Iris host adapter surface — HostAdapter {name, capabilities, store, scheduler}, runTurnOn (run a turn ON a host's store+scheduler), and checkHostCapabilities (the tool/host-level
|
|
5
|
+
"description": "Iris host adapter surface — HostAdapter {name, capabilities, store, scheduler}, runTurnOn (run a turn ON a host's store+scheduler), and checkHostCapabilities (the tool/host-level refusal). Makes 'same image, different host' explicit for the portability proof. Host-side; zero external deps.",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
8
|
"iris-src": "./src/index.ts",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@irisrun/core": "^0.
|
|
15
|
-
"@irisrun/agent": "^0.
|
|
14
|
+
"@irisrun/core": "^0.2.0",
|
|
15
|
+
"@irisrun/agent": "^0.2.0"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"engines": {
|