row224: pace failed inline sync attempts (unrun)
This commit is contained in:
1 parent
212aef6161
commit
82d6cc62c6
4 files changed
+50
-6
No files matched your search
@@ -43,7 +43,7 @@ explicitly. After 253 and 216 land, rebase under a new branch name.
|
|||||||
|
|
||||||
## Verification ledger
|
## Verification ledger
|
||||||
|
|
||||||
No test or syntax-check run yet. Acceptance requires full bus/harness suites
|
No test or syntax-check run yet. Acceptance requires full bus/cli/harness suites
|
||||||
and every scripts/test-*.sh on Sage's gate; focused files and affected CLI/
|
and every scripts/test-*.sh on Sage's gate; focused files and affected CLI/
|
||||||
seat import-closure tests precede that only in an authorized CLEAR.
|
seat import-closure tests precede that only in an authorized CLEAR.
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ Library sources remain in the existing whole-directory release copy.
|
|||||||
Set TMPDIR=/mnt/storage/scratch/tmp. Focused new bus/harness files first,
|
Set TMPDIR=/mnt/storage/scratch/tmp. Focused new bus/harness files first,
|
||||||
then existing library.test.mjs, library-sync.test.mjs, project-runtime.test.mjs,
|
then existing library.test.mjs, library-sync.test.mjs, project-runtime.test.mjs,
|
||||||
harness creative/tools/spec-tools/bundle files and affected CLI launch tests.
|
harness creative/tools/spec-tools/bundle files and affected CLI launch tests.
|
||||||
Follow with the required whole bus/harness suites and Sage's full shell gate.
|
Follow with the required whole bus/cli/harness suites and Sage's full shell gate.
|
||||||
No timing claim yet: use the repository/Sage per-file limits and record every
|
No timing claim yet: use the repository/Sage per-file limits and record every
|
||||||
command, outcome, elapsed time and timeout before acceptance. No focused skip
|
command, outcome, elapsed time and timeout before acceptance. No focused skip
|
||||||
or pattern run substitutes for whole-file gate evidence.
|
or pattern run substitutes for whole-file gate evidence.
|
||||||
@@ -139,3 +139,32 @@ For `docs/SESSIONS.md`:
|
|||||||
Local-only draft pins 19c0a7adf/7cf39c390 are superseded by the
|
Local-only draft pins 19c0a7adf/7cf39c390 are superseded by the
|
||||||
log-free candidate. Saved as local branch della/row224-draft-with-logs before
|
log-free candidate. Saved as local branch della/row224-draft-with-logs before
|
||||||
rebuilding the unpublished candidate commit; no remote history changed.
|
rebuilding the unpublished candidate commit; no remote history changed.
|
||||||
|
|
||||||
|
## Dewey dewey-0852 and Sage sage-2819 — focused corrections
|
||||||
|
|
||||||
|
B1: inline freshness uses max(lastFetch, lastError.at), not lastFetch alone.
|
||||||
|
An error within 60 seconds counts as a recent attempt, so later lookups serve
|
||||||
|
the old served pin without another fetch. Keep inline sync: the brief requires
|
||||||
|
it and businesses without timed libraries have no sync timer. The extended
|
||||||
|
failed-fetch test makes reads at +1, +59,999 and +60,000 ms (one attempt total),
|
||||||
|
then +60,001 ms (second attempt). All reads must return the old commit.
|
||||||
|
|
||||||
|
Required mutant M-B1: remove lastError.at from recentAttempt. The repeated
|
||||||
|
reads must fail the attempted-count assertion. This mutant has NOT been run.
|
||||||
|
|
||||||
|
Residual (Sage will defer): the first inline fetch in each outage window can
|
||||||
|
still exceed the tool timeout (60 seconds, or the runner broker client’s
|
||||||
|
30 seconds). Bounding that fetch is out of scope for 224. Subsequent reads
|
||||||
|
are paced by persisted lastError.at. No claim of first-read outage latency.
|
||||||
|
|
||||||
|
N1: whole bus, cli and harness suites are required, plus the shell gate.
|
||||||
|
If the new thread-launch integration test skips due to sandboxProblem(),
|
||||||
|
name the skip; a skip is not launcher integration proof.
|
||||||
|
N2: cli/tests/thread-launch.test.mjs now depends on library/tests/helpers.mjs
|
||||||
|
mappingEntry() and library/tests/fixtures/mapping.json. Changes to that fixture
|
||||||
|
must rerun the CLI launch file.
|
||||||
|
N3: harness/tests/library-tools.test.mjs compares the type enum to mapping.mjs
|
||||||
|
TYPES, so adding a mapped type cannot silently leave the typed tool behind.
|
||||||
|
|
||||||
|
These edits are local code/docs only. No Node, RED/GREEN run, mutant, claim,
|
||||||
|
HTTPS Git or push. New local pin goes to Dewey/Mos/Sage for focused review.
|
||||||
@@ -98,7 +98,11 @@ export function createLibraryLookup({ dataRoot, sync, now = () => Date.now() })
|
|||||||
const source = { ...context, dataRoot };
|
const source = { ...context, dataRoot };
|
||||||
let state = await libraryModule.libraryStatus(source);
|
let state = await libraryModule.libraryStatus(source);
|
||||||
authorize();
|
authorize();
|
||||||
const age = state.lastFetch === null ? Infinity : now() - Date.parse(state.lastFetch);
|
// sage-2819 / dewey-0852: failed attempts also pace inline retries. Otherwise an
|
||||||
|
// outage makes every lookup refetch while lastFetch remains old or null.
|
||||||
|
const recentAttempt = Math.max(state.lastFetch === null ? -Infinity : Date.parse(state.lastFetch),
|
||||||
|
state.lastError === null ? -Infinity : Date.parse(state.lastError.at));
|
||||||
|
const age = now() - recentAttempt;
|
||||||
if (age > libraryModule.SYNC_INTERVAL_MS || age < 0) {
|
if (age > libraryModule.SYNC_INTERVAL_MS || age < 0) {
|
||||||
try { await sync(source); }
|
try { await sync(source); }
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
@@ -159,16 +159,25 @@ test('freshness boundary, unknown last fetch and failed fetch still use immutabl
|
|||||||
f.setState({ lastFetch: null });
|
f.setState({ lastFetch: null });
|
||||||
await f.call('library.universe', { universe: 'harbor' });
|
await f.call('library.universe', { universe: 'harbor' });
|
||||||
assert.equal(f.fetches(), 2);
|
assert.equal(f.fetches(), 2);
|
||||||
let attempted = 0;
|
let attempted = 0, clock = NOW;
|
||||||
const failing = createLibraryLookup({ dataRoot: f.dataRoot, now: () => NOW, sync: async () => {
|
const failing = createLibraryLookup({ dataRoot: f.dataRoot, now: () => clock, sync: async () => {
|
||||||
attempted++;
|
attempted++;
|
||||||
f.setState({ lastError: { code: 'library-fetch-failed', at: new Date(NOW).toISOString() } });
|
f.setState({ lastError: { code: 'library-fetch-failed', at: new Date(clock).toISOString() } });
|
||||||
return { code: 'library-fetch-failed' };
|
return { code: 'library-fetch-failed' };
|
||||||
} });
|
} });
|
||||||
f.setState({ lastFetch: null });
|
f.setState({ lastFetch: null });
|
||||||
const result = await failing(f.context, { method: 'get', args: GET, authorize() {} });
|
const result = await failing(f.context, { method: 'get', args: GET, authorize() {} });
|
||||||
assert.equal(attempted, 1);
|
assert.equal(attempted, 1);
|
||||||
assert.equal(result.commit, f.commit);
|
assert.equal(result.commit, f.commit);
|
||||||
|
for (const elapsed of [1, 59_999, 60_000]) {
|
||||||
|
clock = NOW + elapsed;
|
||||||
|
const retry = await failing(f.context, { method: 'get', args: GET, authorize() {} });
|
||||||
|
assert.equal(retry.commit, f.commit, 'serve the old pin during the failure cooldown');
|
||||||
|
assert.equal(attempted, 1, 'recent lastError prevents another sync even though lastFetch is null');
|
||||||
|
}
|
||||||
|
clock = NOW + 60_001;
|
||||||
|
assert.equal((await failing(f.context, { method: 'get', args: GET, authorize() {} })).commit, f.commit);
|
||||||
|
assert.equal(attempted, 2, 'retry only after the failed-attempt interval expires');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('an unreadable sync state fails closed; a busy sync keeps serving the existing pin', async t => {
|
test('an unreadable sync state fails closed; a busy sync keeps serving the existing pin', async t => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { mkdirSync, readFileSync } from 'node:fs';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { typedTools } from '../src/tools.mjs';
|
import { typedTools } from '../src/tools.mjs';
|
||||||
import { buildBundle } from '../src/bundle.mjs';
|
import { buildBundle } from '../src/bundle.mjs';
|
||||||
|
import { TYPES } from '../../library/src/mapping.mjs';
|
||||||
import { HARNESS, REPO_SKILLS, resolvedFor, scratch } from './helpers.mjs';
|
import { HARNESS, REPO_SKILLS, resolvedFor, scratch } from './helpers.mjs';
|
||||||
|
|
||||||
const LOOKUPS = ['library_find', 'library_get', 'library_search', 'library_manuscript', 'library_universe'];
|
const LOOKUPS = ['library_find', 'library_get', 'library_search', 'library_manuscript', 'library_universe'];
|
||||||
@@ -46,6 +47,7 @@ test('lookup schemas require a universe even for get, include paging, and cannot
|
|||||||
}
|
}
|
||||||
const byName = Object.fromEntries(tools.map(t => [t.name, t]));
|
const byName = Object.fromEntries(tools.map(t => [t.name, t]));
|
||||||
assert.deepEqual(byName.library_find.parameters.properties.scope.enum, ['canon', 'playground']);
|
assert.deepEqual(byName.library_find.parameters.properties.scope.enum, ['canon', 'playground']);
|
||||||
|
assert.deepEqual(byName.library_find.parameters.properties.type.enum, [...TYPES]);
|
||||||
assert.equal(byName.library_find.parameters.properties.offset.minimum, 0);
|
assert.equal(byName.library_find.parameters.properties.offset.minimum, 0);
|
||||||
assert.equal(byName.library_get.parameters.properties.from.minimum, 1);
|
assert.equal(byName.library_get.parameters.properties.from.minimum, 1);
|
||||||
assert.equal(byName.library_get.parameters.properties.links_from.minimum, 0);
|
assert.equal(byName.library_get.parameters.properties.links_from.minimum, 0);
|
||||||
|
|||||||
Reference in new issue
Block a user