mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 17:02:28 +00:00
Merge branch 'unstable' into area/client-reliability
This commit is contained in:
commit
280f043a66
8 files changed
+164
-1
No files matched your search
@@ -2,6 +2,8 @@
|
||||
|
||||
## dispatch
|
||||
|
||||
- 2026-10-23 resume: actual QA4 bootEPERM/release consumed; browser/runtimechecks0, prior mobile strict failures unchanged. HLS sole full/normal grant12:32:43Z at clean79a9a130 after safe merge latest672ca953; START/results pending. Health cooling77→71C/RAM68.3%/swap90% advisory; one heavy max. QA host escalation disposition/browser grant later; no app-fix/cron/autoapproval-effect claim. Overnight client wake left composer yet unread55642s, recovery now consumed.
|
||||
|
||||
- 2026-10-22 storage3+4DONE: storage4 product7edc7ea8/closure8317da4d independently durable status/log/ancestry; worker normalgateexit0/discovery436/build29cbe6f0 artifactPASS, no independent436start audit. QA4 now sole bounded mobile browser grant21:04:02; HLS candidatecdc398b0/merged79a9a130 next full after QA release. Dispatch exact candidate35/35 natural321ms; worker129focused/19mutants/lint11. HLS gates/preview/campaign remain unfinished.
|
||||
|
||||
- 2026-10-22 HLS follow-on: dispatch independent actual observer/controller/helper scratch atd04d501e12=7pass/5fail/cancelled0/naturalexit1/165.17ms. Final-owner GET/timer/promise retained, rejoin stale, late healthy cache, body-read disposal fail; no media-slot leak. Controller HLS handoff + optional manifest-helper cancellation only routed with strict spec/integrity/regressions. Focused/mutants/lint only while storage4 holds heavy slot; torrent/RangeGET remain separate, no HLS fix landed.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Bitvid scheduled-job policy
|
||||
|
||||
Owner direction, 2026-10-23: "I would like to not have extra processes like this running when the fleet is down or paused".
|
||||
|
||||
All seven installed Bitvid cron jobs enter through `tools/fleet/bin/bitvid-schedule-guard.mjs`, not directly through `sched.sh`.
|
||||
|
||||
- A missing/dead/stopped interactive fleet means no scheduled headless launch. At least one configured interactive area must have a running record and a live PID.
|
||||
- `fleet/PAUSED` is represented by the existing state-directory `PAUSED` marker. It unconditionally blocks every job, including the weekly restart job; `ignorePause` does not bypass this outer guard.
|
||||
- A running scheduled job is checked every second. On pause/down, the guard sends TERM only to the process group it created. Fleet's nested process runners forward termination to their own agent group. A bounded KILL fallback handles a group that does not exit.
|
||||
- A cron tick while down or paused starts only the brief guard check, which exits without an AI session or persistent watcher. There is no always-on supervisor added.
|
||||
- Unpausing and bringing the fleet up lets the next normal cron tick run; this policy does not automatically resume a paused fleet.
|
||||
- Existing quiet hours, per-job non-overlap locks and time limits still apply inside `sched.sh`. The one-heavy-job convention still applies.
|
||||
- This is a Bitvid-specific file, separate from the kit-managed scheduler. Kit upgrades should retain it. `fleet crontab` prints generic, unguarded commands: do not overwrite the installed guarded entries without preserving this policy.
|
||||
|
||||
Validation: `env -u NODE_TEST_CONTEXT node --test tools/fleet/test/bitvid-schedule-guard.test.mjs` passed 8 tests with natural exit; fixtures only, no real fleet shutdown/pause or headless model run. Test integrity: new behavioral coverage, no existing assertions/gates changed. Process stop tests use a disposable sleep child and verify the unrelated test parent remains alive. Nested production runner cleanup relies on the kit's existing TERM forwarding; this is not a real-model stop qualification.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Program — the owner's priorities and rulings
|
||||
|
||||
Last updated: 2026-10-22
|
||||
Last updated: 2026-10-23
|
||||
|
||||
## Current milestone
|
||||
|
||||
@@ -17,6 +17,12 @@ Local-review handoff: storage2/history6 and demonstrated cold-feed blockerclient
|
||||
|
||||
## Rulings log (newest first)
|
||||
|
||||
## 2026-10-23 — Existing seven schedules authorized in interpreter session
|
||||
|
||||
> "All seven jobs shown above" — owner, relayed by interpreter at2026-10-23T12:32:43Z; not typed in dispatch.
|
||||
|
||||
Interpreter reports installing the existing checkin, hygiene, triage, summary, winddown, restart and resources jobs in user crontab, preserving prior entries and verifying readback. Scoped PATH/FLEET_CONFIG; resources every15minutes, only while laptop awake. Installation/readback are interpreter evidence, not independently verified here. This records scope and provenance; dispatch did not install or alter cron. One-heavy-job/resource guidance and all restricted-action limits remain. It adds no push/deploy/spend/delete/credential authority. Interpreter separately reports owner-approved MCP-only autoapproval setting saved; effective state in existing sessions is unverified and does not override a denied action.
|
||||
|
||||
## 2026-10-22 — Six-priority campaign resumed via interpreter
|
||||
|
||||
> "yeah lets get the fleet spun up and address all of these" — owner, relayed by interpreter at 2026-10-22T14:02:34Z; not typed in dispatch.
|
||||
|
||||
@@ -50,3 +50,9 @@ Resume existing client worker for current-HEAD active URL-media cancellation rep
|
||||
> "yeah lets get the fleet spun up and address all of these" — owner, relayed by interpreter at 2026-10-22T14:02:34Z; not typed in dispatch.
|
||||
|
||||
The relay identifies six priorities: Firefox deterministic CI failure; remaining active HEAD/HLS/torrent cancellation; playback reliability promises; feed/view tombstone stale-refresh; Amber sessions; mobile usability. This supersedes the prior idle/local-review and quiet-hours hold for this foreground campaign. Routine investigation, evidence-routed fixes, tests/builds and normal local unstable landing are approved. No push/deploy/spend/production data/real wallet/credential/live signer/scheduler authority. Track all six in docs/agents/CAMPAIGN-2026-10-22.md. Preserve worktrees, use three existing workers with disjoint scope, stage before fixes, serialize browser/full gates, keep app.js single-writer. Final integrated local build/preview and honest live/manual limits required.
|
||||
|
||||
## 2026-10-23 — Existing schedule activation reported
|
||||
|
||||
> "All seven jobs shown above" — owner, relayed by interpreter12:32:43Z; not typed in dispatch.
|
||||
|
||||
Interpreter reports installed/readback-verified existing seven schedules: checkin,hygiene,triage,summary,winddown,restart,resources. This supersedes earlier no-scheduler-activation status only for these jobs, with resources every15minutes while laptop awake. Installation and execution not independently verified by dispatch. Preserve one-heavy-job limit and all restricted-action boundaries; no dispatch cron/settings edits.
|
||||
@@ -183,3 +183,10 @@
|
||||
- HLS reproduction independently12=7pass/5fail/0cancelled/naturalexit1/165.17ms atd04d501e: orphan GET/timer/promise, stale replacement/late cache/body-read disposal. Temporary controller HLS handoff+manifest-helper optional cancellation ownership and strict spec/integrity/tests routed; no player/engine/torrent/RangeGET scope. Storage4 retains heavy slot; client focused/mutants/lint only, QA4 held. Evidence test_logs/TEST_LOG_2026-10-22_20-54-00_dispatch-hls.md.
|
||||
- Client HLS routing ACK20:55:32 consumed; focused implementation only. Repro docsfb0e671d independentlyQUEUED behind alive drain277174, not landed. Health burst86.1C/clientCPU58% then77.1C/client0% within14s, RAM64.2%/swap51%; no sustained-over85 finding or extra heavy launch. Storage4 sole gate,QA4 held.
|
||||
- Storage4 product888807ba durably7edc7ea8/closure265ce426 durably8317da4d independently status/log/ancestry0. Worker normalgateexit0/discovery436(no independent436start audit),build29cbe6f0/artifactPASS; released20:58Z. QA4 sole bounded mobile browser granted21:04:02 on verified frozen artifact/provenance; HLS next full after QA release. Clientcandidatecdc398b0 merged79a9a130 inspected; dispatch direct new suite35/35 naturalexit0/321.06635ms, worker129focused/19mutants/lint11. No HLS full/build/browser/landing yet.
|
||||
|
||||
## 2026-10-23 — Mail recovery and HLS gate allocation
|
||||
|
||||
- Actual mailbox consumed: QA4 previous localserverEPERM/release with runtime0/browser0; client clean79a9a130 preserved; interpreter reports owner-approved MCP autoapproval config only, not independently verified/effective and no config touched here. Cron still pending/uninstalled.
|
||||
- Wake log client21:04:05 submitted/leftcomposer but unread55642s, recovery12:31:27 submitted1Enter; mail now consumed. Busy interpreter wake deferred/already-read, no waiter typing.
|
||||
- Fleethealth77→71C/RAM68.3%/~21GiB available/load1.04/8/swap90% only advisory stable/slightly falling; queue0. Granted sole HLS full/normal12:32:43Z, sequentialbuild aftergreen/freshhealth, no browser overlap. QA HOLD; sandbox EPERM inspection/normal host escalation staged after release, no bypass/rejected-action retry. Results/integratedpreview/campaign unfinished.
|
||||
- Client HLS START12:33:14 at mergedd7184f07, clean/inputsequal workerreport; gate outcome pending. Health81.1C/RAM66.3%/swap89%. Interpreter reports owner quote "All seven jobs shown above" and all7 existing schedules installed/readback; recorded PROGRAM/GOAL/RULE with provenance, no independent install/execution/config-effect proof. No settings/cron mutation here; restricted authority unchanged. Busy interpreter wake waiter typed/submitted12:33:13, actual mail consumed.
|
||||
@@ -7,6 +7,10 @@ override a stale system prompt. Nothing filters them down to a window of days.
|
||||
The session manager adds a line here whenever a rule, a prompt, a tool's usage, or
|
||||
a landing rule changes.
|
||||
|
||||
## 2026-10-23
|
||||
|
||||
- Interpreter12:32:43Z reports owner quote "All seven jobs shown above" and installation/readback of existing checkin/hygiene/triage/summary/winddown/restart/resources schedules, scoped PATH/FLEET_CONFIG and preserved prior crontab. Record as relay/interpreter evidence, not independent verification or dispatch install authority. Earlier no-cron-installed statements are historical; no execution-success claim. One heavy job maximum and restricted-action boundaries remain; no denied-action retry or assumed effective MCP approval config.
|
||||
|
||||
## 2026-10-22
|
||||
|
||||
- Client9 HLS follow-on explicitly routed after HEAD productf41ac656/evidenced04d501e durable. Dispatch independently ran actual observer/controller/cache/probeHlsManifest scratch12=7pass/5fail/cancelled0/naturalexit1/165.17ms atd04d501e: final-owner GET1/abort0/timer1/unsettled, stale same-source rejoin and late cache; body-read disposal also fails, media slots0. Temporary ownership ONLY js/ui/urlHealthController.js HLS lifecycle handoff + js/services/hlsPlayback.js manifest-probe optional cancellation plumbing across fetch/body + unique focused lifecycle/helper tests and strict spec/integrity notes. Explicit Stage3 contract extends final-owner prompt null settlement/abort/timer-hook cleanup/no obsolete cache/fallback/mirror/badge writes to HLS fetch AND body-read, even ignored abort or absent AbortController. Shared/cross-controller/legacy/live success/error/timeout and same-source replacement identity preserved; existing helper callers/signature behavior preserved without lifecycle. No HLS engine/player/session/config changes, torrent/RangeGET/app.js excluded. Focused+mutants+lint only, no full/build/browser/submission until resource grant; storage4 currently sole full/normal slot, QA4 held.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env node
|
||||
// Bitvid policy: cron may work only while an interactive fleet is alive and unpaused.
|
||||
// Kept separate from sched.sh so kit updates cannot restore ignorePause behavior.
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { loadConfig, pidAlive, killProcessGroup, runProcessGroup } from './lib.mjs';
|
||||
|
||||
export function inactiveReason(c) {
|
||||
if (fs.existsSync(path.join(c.stateDir, 'PAUSED'))) return 'fleet paused';
|
||||
for (const area of c.areas) {
|
||||
try {
|
||||
const run = JSON.parse(fs.readFileSync(path.join(c.stateDir, 'runs', `${area.id}.json`), 'utf8'));
|
||||
if (run.status === 'running' && Number.isInteger(run.pid) && run.pid > 1 && pidAlive(run.pid)) return null;
|
||||
} catch { /* Missing or invalid state is not permission to launch. */ }
|
||||
}
|
||||
return 'fleet down';
|
||||
}
|
||||
|
||||
export async function guardSchedule(c, job, { scheduler = path.join(path.dirname(fileURLToPath(import.meta.url)), 'sched.sh'), pollMs = 1000 } = {}) {
|
||||
if (!c.schedules.some((entry) => entry.name === job)) throw new Error(`Unknown scheduled job: ${job}`);
|
||||
const reason = inactiveReason(c);
|
||||
if (reason) return { status: 0, reason };
|
||||
let poll;
|
||||
let force;
|
||||
let stopped;
|
||||
const result = await runProcessGroup({
|
||||
cmd: '/bin/bash', args: [scheduler, job], cwd: c.repo, env: process.env,
|
||||
onSpawn(pid) {
|
||||
poll = setInterval(() => {
|
||||
const why = inactiveReason(c);
|
||||
if (!why || stopped) return;
|
||||
stopped = why;
|
||||
// Only the process group this invocation created. Nested fleet runners
|
||||
// receive TERM and forward it to their own agent process groups.
|
||||
killProcessGroup(pid, 'SIGTERM');
|
||||
force = setTimeout(() => killProcessGroup(pid, 'SIGKILL'), 3000);
|
||||
}, pollMs);
|
||||
},
|
||||
}).finally(() => {
|
||||
clearInterval(poll);
|
||||
clearTimeout(force);
|
||||
});
|
||||
return { status: stopped ? 0 : (result.status ?? 1), reason: stopped ?? null };
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
try {
|
||||
const c = loadConfig();
|
||||
const result = await guardSchedule(c, process.argv[2]);
|
||||
process.exitCode = result.status;
|
||||
} catch (error) {
|
||||
console.error(`Bitvid schedule guard: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { guardSchedule } from '../bin/bitvid-schedule-guard.mjs';
|
||||
|
||||
const moduleUrl = new URL('../bin/bitvid-schedule-guard.mjs', import.meta.url).href;
|
||||
function fixture(t) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'bitvid-schedule-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const c = { repo: root, stateDir: root, areas: [{ id: 'dispatch' }], schedules: [{ name: 'resources' }, { name: 'restart' }] };
|
||||
fs.mkdirSync(path.join(root, 'runs'));
|
||||
const record = path.join(root, 'runs', 'dispatch.json');
|
||||
const scheduler = path.join(root, 'schedule.sh');
|
||||
fs.writeFileSync(scheduler, 'echo started\nprintf yes > "'+root+'/launched"\n');
|
||||
const live = () => fs.writeFileSync(record, JSON.stringify({ status: 'running', pid: process.pid }));
|
||||
return { c, root, record, scheduler, live };
|
||||
}
|
||||
|
||||
for (const job of ['resources', 'restart']) {
|
||||
test(`${job} does not launch while down`, async (t) => {
|
||||
const f = fixture(t);
|
||||
assert.deepEqual(await guardSchedule(f.c, job, { scheduler: f.scheduler }), { status: 0, reason: 'fleet down' });
|
||||
assert.equal(fs.existsSync(path.join(f.root, 'launched')), false);
|
||||
});
|
||||
test(`${job} cannot bypass pause even with a live fleet`, async (t) => {
|
||||
const f = fixture(t); f.live(); fs.writeFileSync(path.join(f.root, 'PAUSED'), 'owner pause');
|
||||
assert.deepEqual(await guardSchedule(f.c, job, { scheduler: f.scheduler }), { status: 0, reason: 'fleet paused' });
|
||||
assert.equal(fs.existsSync(path.join(f.root, 'launched')), false);
|
||||
});
|
||||
}
|
||||
|
||||
test('stopped record pointing at a live unrelated process cannot start a job', async (t) => {
|
||||
const f = fixture(t);
|
||||
fs.writeFileSync(f.record, JSON.stringify({ status: 'stopped', pid: process.pid }));
|
||||
assert.equal((await guardSchedule(f.c, 'resources', { scheduler: f.scheduler })).reason, 'fleet down');
|
||||
});
|
||||
|
||||
test('live unpaused fleet launches a short job and exits naturally', async (t) => {
|
||||
const f = fixture(t); f.live();
|
||||
assert.deepEqual(await guardSchedule(f.c, 'resources', { scheduler: f.scheduler }), { status: 0, reason: null });
|
||||
assert.equal(fs.readFileSync(path.join(f.root, 'launched'), 'utf8'), 'yes');
|
||||
});
|
||||
|
||||
for (const transition of ['pause', 'down']) {
|
||||
test(`already-running scheduled child is stopped on ${transition}`, { timeout: 5000 }, async (t) => {
|
||||
const f = fixture(t); f.live();
|
||||
fs.writeFileSync(f.scheduler, 'echo started\nexec sleep 600\n');
|
||||
const code = `import {guardSchedule} from ${JSON.stringify(moduleUrl)}; const r=await guardSchedule(${JSON.stringify(f.c)},'resources',${JSON.stringify({scheduler:f.scheduler,pollMs:25})}); console.log(JSON.stringify(r)); process.exitCode=r.status;`;
|
||||
const child = spawn(process.execPath, ['--input-type=module', '-e', code], { stdio: ['ignore','pipe','pipe'] });
|
||||
t.after(() => { if (child.exitCode === null) child.kill('SIGTERM'); });
|
||||
let output = '';
|
||||
const exited = new Promise((resolve, reject) => { child.once('error',reject); child.once('close',(status)=>resolve(status)); });
|
||||
await new Promise((resolve, reject) => {
|
||||
child.once('error', reject);
|
||||
child.stdout.on('data', chunk => { output += chunk; if (output.includes('started\n')) resolve(); });
|
||||
child.once('exit', () => { if (!output.includes('started\n')) reject(new Error('child exited before ready')); });
|
||||
});
|
||||
if (transition === 'pause') fs.writeFileSync(path.join(f.root,'PAUSED'),'pause');
|
||||
else fs.writeFileSync(f.record, JSON.stringify({status:'stopped',pid:process.pid}));
|
||||
assert.equal(await exited, 0);
|
||||
assert.match(output, transition === 'pause' ? /fleet paused/ : /fleet down/);
|
||||
assert.doesNotThrow(() => process.kill(process.pid, 0));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user