Compare commits

..
Author SHA1 Message Date
Quixos Subtree Publisher eb38d91c1b Publish quixos-nix-helpers from 358e649a7d70828db2ef96d5fed18931e48bc498 2026-10-10 15:37:44 +00:00
Timothy J. Aveni 6e591d5588 Make Node dependency outputs reproducible without build caches 2026-10-10 08:37:44 -07:00
Quixos Subtree Publisher 4a0eec33d8 Publish quixos-nix-helpers from 286ada5419c4fc2184b387cea4715fae1a74bde5 2026-10-10 00:10:14 +00:00
Timothy J. Aveni d6434efc28 Reuse checked bootstrap catalogs and coordinate execution deadlines 2026-10-09 17:10:14 -07:00
Timothy J. Aveni ccb48afc04 Document package architecture and organize current guidance and project backlog 2026-10-09 10:33:39 -07:00
Quixos Subtree Publisher e4e5c1f175 Publish quixos-nix-helpers from 98cd7bc237027368fd2c77f294f59281ef3df160 2026-10-08 12:33:28 +00:00
Timothy J. Aveni 1b91e21d49 Keep restricted engines reserved through asynchronous retirement 2026-10-08 05:21:10 -07:00
Quixos Subtree Publisher fcfce6259e Publish quixos-nix-helpers from 806ea0e9f41340acf119b7374f5150ef5b378074 2026-10-08 11:31:16 +00:00
Timothy J. Aveni dec3aa475a Expose workspace-scoped restricted artifact placement and live executor activity
Show per-export placement, static eligibility blockers, bounded metadata and live pool/idle/timing diagnostics. Attach inspector updates through one private shard stream and the existing authenticated SSE hub without waking graph views or VMs. Fix packed agent preparation to use OpenSSH and the worker’s own private systemd credential.
2026-10-08 04:31:16 -07:00
5 changed files with 110 additions and 8 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
"sourceCommit": "57684c7548883dc4fc25dc8f943c7a43b3489fcf",
"sourceCommit": "358e649a7d70828db2ef96d5fed18931e48bc498",
"sourcePath": "quixos-instance/quixos-nix-helpers",
"exportName": "quixos-nix-helpers",
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
+34 -1
View File
@@ -1,5 +1,11 @@
# Checked capability and component artifacts
This is the **current helper/output format**, not the core package abstraction.
See [the architecture](../../docs/ARCHITECTURE.md) and
[current gaps](../../docs/ARCHITECTURE_STATUS.md). In particular, the universal
artifact inventory and component-only omission of a server are implementation
gaps to resolve, not requirements for arbitrary package servers.
`mkCaminoArtifacts` is the fresh package builder. It regenerates candidate
bindings, typechecks the entry's dependency closure, bundles declared targets,
generates a process service when requested, and emits content manifests and
@@ -55,6 +61,25 @@ with their own versions. Authored source symlinks and non-bundled portable impor
other than the SDK and server Node builtins are rejected. This is build closure
checking, not a purity proof or security sandbox.
`mkCaminoYarnDependencies` installs the locked dependency recipe and exports its
installed Node dependency tree, including native package results and executable
links. Generated Yarn install/linker state stays outside runtime outputs. Yarn
describes its install-state file as an optimization cache in its
[configuration reference](https://yarnpkg.com/configuration/yarnrc#installStatePath).
This helper output is a dependency directory for a build, not a standalone
Package or a runnable Yarn project. Its `devShell` supplies freestanding Yarn for
editable projects; do not run Yarn against the readonly dependency output.
Use the shared `installDependencies` assembly for consumers: authored libraries
are copied into the consumer's `node_modules`, where Node can find their
transitive dependencies, while selected SDK links retain their canonical type
identity. Reserved SDK substitutions fail checks. Web Studio uses this same
assembly rather than retaining a full Yarn project just to resolve UI libraries.
Its dependency inputs contain manifests, lock and Yarn configuration/plugin
recipe; ordinary Studio source edits do not enter that dependency derivation.
Recipes with local/workspace dependencies must still include their needed source.
This is language-specific build machinery; it adds no core Package file contract.
The core/Automerge bundle and Replicache bundle are built once. Browser modules
import the core through a content-addressed gateway URL, independent of package
identity. Each manifest covers the shared bytes it uses. The gateway retains its
@@ -105,7 +130,7 @@ and supplies an explicit SDK for value codecs, identities and declared graph por
service = {
restricted = true;
limits = {
deadlineMs = 4000;
deadlineMs = 10000;
maxBytes = 1048576;
maxPortCalls = 1000;
maxQueued = 8;
@@ -122,6 +147,14 @@ invocation identity; authored code cannot choose another frame or workspace.
Native graph grants and transaction commit remain enforced by Camino outside
the guest. Initialization and synchronous loops are subject to the same deadline.
The default root/Package Invocation wall deadline is 10 seconds. The separate
root CPU allowance remains at most 4 CPU seconds; extra wall time accommodates
queueing and graph-port waits. Ordinary graph database transactions and idle
transactions allow 30 seconds, and ordinary client HTTP requests allow 45 seconds
so server failures can be returned. Nested calls inherit the remaining root
budget; explicit smaller limits still apply. Restore/activation jobs retain their
separate budgets. These defaults are starting limits, not latency targets.
Direct network access, native filesystem imports, ambient clock and entropy,
parent environment credentials, and unlisted modules are denied. Explicit dates,
`Intl.Segmenter`, deterministic hashes and normal JavaScript computation remain
+22 -5
View File
@@ -108,7 +108,7 @@ export async function serveRestrictedRegistry(config, io = process) {
const limits = {
maxBytes: config.maxBytes ?? 1024 * 1024,
maxPortCalls: config.maxPortCalls ?? 1000,
deadlineMs: config.deadlineMs ?? 4000,
deadlineMs: config.deadlineMs ?? 10000,
maxQueued: config.maxQueued ?? 8,
maxActive: config.maxActive ?? 2,
maxNested: config.maxNested ?? 4,
@@ -148,6 +148,11 @@ export async function serveRestrictedRegistry(config, io = process) {
engines = new Set(),
idleEngines = [];
const metrics = { starts: 0, reused: 0, completed: 0, peakRSSBytes: 0 };
const changed = () => {
try {
config.onChange?.();
} catch {}
};
let buffered = Buffer.alloc(0),
stopped = false,
failure;
@@ -234,16 +239,19 @@ export async function serveRestrictedRegistry(config, io = process) {
}
const engine = call.engine;
if (engine) {
// Revoke the frame token before returning an engine to the idle pool.
// A delayed child request can never acquire the next call's authority.
if (engine.current === call) engine.current = null;
if (!stopped && reason.code === "INVOCATION_ENDED" && !usageFailure && !engine.destroying) {
const rss = (await engineUsage(engine)).memoryBytes;
metrics.peakRSSBytes = Math.max(metrics.peakRSSBytes, rss);
// Bound retained engine heaps as well as lifetime. Hard aggregate
// physical memory remains the supervising process/cgroup's contract.
if (rss <= 128 * 1024 * 1024) {
// Keep the lease busy through every await. Another artifact can evict
// an idle lease immediately; never publish an engine still retiring
// or one whose process died while its final usage read was pending.
if (rss <= 128 * 1024 * 1024 && !stopped && !engine.destroying && engine.current === call) {
// Revoke the frame token atomically with publishing idle capacity.
engine.current = null;
idleEngines.push(engine);
engine.idleUntil = Date.now() + limits.engineIdleMs;
engine.idleTimer = setTimeout(() => void destroyEngine(engine).catch(fail), limits.engineIdleMs);
} else await destroyEngine(engine);
} else await destroyEngine(engine);
@@ -300,6 +308,7 @@ export async function serveRestrictedRegistry(config, io = process) {
} finally {
engines.delete(engine);
await engine.release?.();
changed();
}
})();
return engine.destroying;
@@ -418,6 +427,7 @@ export async function serveRestrictedRegistry(config, io = process) {
},
);
metrics.starts++;
changed();
engine.exited = new Promise((resolve) => {
engine.child.once("exit", resolve);
engine.child.once("error", resolve);
@@ -462,6 +472,9 @@ export async function serveRestrictedRegistry(config, io = process) {
executionMs: call.executionMs ?? null,
totalMs: performance.now() - call.receivedAt,
};
metrics.lastFailure =
reply.status === "failed" && /^[A-Z0-9_]{1,128}$/.test(reply.error?.code) ? reply.error.code : null;
changed();
send(call.cancellationReply ?? reply);
pump();
})());
@@ -470,6 +483,7 @@ export async function serveRestrictedRegistry(config, io = process) {
const { message } = call;
call.startedAt = performance.now();
active.set(message.invocation, call);
changed();
const terminate = (code) => {
if (!call.open) return;
void finish(call, fixedError(code), {
@@ -687,6 +701,9 @@ export async function serveRestrictedRegistry(config, io = process) {
queued: queued.length,
engines: engines.size,
idle: idleEngines.length,
idleDeadline: idleEngines.length
? new Date(Math.min(...idleEngines.map((engine) => engine.idleUntil))).toISOString()
: null,
}),
};
}
+50 -1
View File
@@ -570,12 +570,25 @@ test("an expired frame token cannot borrow a later invocation's broker", async (
});
test("idle engines evict without a graph poll and restart with fresh state", async () => {
const f = await fixture(registry("return request.input;"), { engineIdleMs: 30 });
const updates = [];
const f = await fixture(registry("return request.input;"), {
engineIdleMs: 30,
onChange: () => updates.push(f.stats()),
});
try {
f.start("first");
assert.equal((await f.receive()).output, "first");
await new Promise((resolve) => setTimeout(resolve, 100));
assert.equal(f.stats().engines, 0);
assert.ok(
updates.some((s) => s.active > 0),
"Inspectors observe active invocation admission",
);
assert.ok(
updates.some((s) => s.idle > 0 && s.idleDeadline),
"Idle eviction deadline is visible",
);
assert.equal(updates.at(-1).engines, 0, "Eviction pushes an update without a graph poll");
f.start("second");
assert.equal((await f.receive()).output, "second");
assert.equal(f.stats().starts, 2);
@@ -673,3 +686,39 @@ test("the shared engine budget evicts idle code families and rejects busy oversu
}
assert.equal(engineBudget.stats().engines, 0);
});
test("engine retirement accounting keeps its admission lease busy", async () => {
const observations = [];
let idle;
const engineBudget = new RestrictedEngineBudget(1, async () => ({
usage: async () => {
observations.push(idle());
await new Promise((done) => setImmediate(done));
return { cpuMicroseconds: 0, memoryBytes: 1024 };
},
release: async () => {},
}));
const acquire = engineBudget.acquire.bind(engineBudget);
engineBudget.acquire = (isIdle, evict) => {
idle = isIdle;
return acquire(isIdle, evict);
};
const f = await fixture(registry("return request.input;"), { engineBudget });
try {
f.start("first");
assert.equal((await f.receive()).output, "first");
assert.ok(observations.length > 0);
assert.ok(
observations.every((value) => !value),
"An engine must not become evictable during asynchronous retirement accounting",
);
assert.equal(f.stats().idle, 1);
assert.equal(idle(), true);
f.start("second");
assert.equal((await f.receive()).output, "second");
assert.equal(f.stats().starts, 1);
} finally {
await f.close();
}
assert.equal(engineBudget.stats().engines, 0);
});
+3
View File
@@ -13,6 +13,9 @@ let
test -d node_modules
mkdir -p "$out"
cp -a node_modules/. "$out/"
# Node resolves installed packages directly. This generated index is for
# Yarn's linker, not a runtime input; do not export build-session state.
rm -f "$out/.yarn-state.yml"
runHook postInstall
'';
};