@karmaniverous/jeeves-meta 0.15.7 → 0.15.8

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.
@@ -9356,6 +9356,8 @@ const serviceConfigSchema = metaConfigSchema.extend({
9356
9356
  watcherHealthIntervalMs: z.number().int().min(0).default(60_000),
9357
9357
  /** Logging configuration. */
9358
9358
  logging: loggingSchema.default(() => loggingSchema.parse({})),
9359
+ /** Max number of all-fresh candidates to scan per tick in Tier 2 invalidation. */
9360
+ tier2ScanLimit: z.number().int().min(1).default(50),
9359
9361
  /**
9360
9362
  * Auto-seed policy: declarative rules for auto-creating .meta/ directories.
9361
9363
  * Rules are evaluated in order; last match wins for steer/crossRefs.
@@ -10789,20 +10791,14 @@ function selectPhaseCandidate(metas, depthWeight) {
10789
10791
  return rankPhaseCandidates(metas, depthWeight)[0] ?? null;
10790
10792
  }
10791
10793
  /**
10792
- * Select the stalest all-fresh, non-disabled, non-locked meta for Tier 2
10793
- * invalidation. These are metas that Tier 1 considers fully fresh but may
10794
- * have structural or steer changes detectable only via I/O.
10795
- *
10796
- * @param metas - Phase candidate inputs (after Tier 1 filtering).
10797
- * @returns The stalest all-fresh candidate, or null if none exist.
10794
+ * Select all fully-fresh, non-disabled, non-locked metas sorted by staleness
10795
+ * (descending stalest first) for Tier 2 invalidation scanning.
10798
10796
  */
10799
- function selectTier2Candidate(metas) {
10800
- const eligible = metas
10797
+ function selectAllTier2Candidates(metas) {
10798
+ return metas
10801
10799
  .filter((m) => !m.locked && !m.disabled && isFullyFresh(m.phaseState))
10802
- .sort((a, b) => b.actualStaleness - a.actualStaleness);
10803
- if (eligible.length === 0)
10804
- return null;
10805
- return { node: eligible[0].node, meta: eligible[0].meta };
10800
+ .sort((a, b) => b.actualStaleness - a.actualStaleness)
10801
+ .map((m) => ({ node: m.node, meta: m.meta }));
10806
10802
  }
10807
10803
 
10808
10804
  /**
@@ -11185,8 +11181,9 @@ async function orchestratePhase(config, executor, watcher, targetPath, onProgres
11185
11181
  // Select best phase candidate
11186
11182
  const winner = selectPhaseCandidate(candidates, config.depthWeight);
11187
11183
  if (!winner) {
11188
- // ── Tier 2 fallback: deep invalidation on stalest all-fresh meta ──
11189
- return orchestrateTier2(candidates, config, executor, watcher, onProgress, logger);
11184
+ // Tier 2 is now handled by the scheduler; orchestratePhase only handles
11185
+ // targeted (override) paths and Tier 1 corpus-wide selection.
11186
+ return { executed: false };
11190
11187
  }
11191
11188
  // Acquire lock
11192
11189
  if (!acquireLock(winner.node.metaPath)) {
@@ -11252,48 +11249,6 @@ async function orchestrateTargeted(config, executor, watcher, targetPath, onProg
11252
11249
  releaseLock(node.metaPath);
11253
11250
  }
11254
11251
  }
11255
- /**
11256
- * Tier 2 invalidation fallback: pick the stalest all-fresh meta,
11257
- * run computeInvalidation (structure hash, steer, cross-refs), and
11258
- * either execute the owed phase or bump _generatedAt.
11259
- */
11260
- async function orchestrateTier2(candidates, config, executor, watcher, onProgress, logger) {
11261
- const tier2 = selectTier2Candidate(candidates);
11262
- if (!tier2)
11263
- return { executed: false };
11264
- if (!acquireLock(tier2.node.metaPath)) {
11265
- logger?.debug({ path: tier2.node.metaPath }, 'Tier 2 candidate is locked, skipping');
11266
- return { executed: false };
11267
- }
11268
- try {
11269
- const currentMeta = await readMetaJson(tier2.node.metaPath);
11270
- const { scopeFiles } = await getScopeFiles(tier2.node, watcher);
11271
- const { phaseState, structureHash } = await computeInvalidation(currentMeta, scopeFiles, config, tier2.node);
11272
- const owedPhase = getOwedPhase(phaseState);
11273
- if (owedPhase) {
11274
- // Something changed — persist invalidated state and execute owed phase
11275
- await persistPhaseState({
11276
- metaPath: tier2.node.metaPath,
11277
- current: currentMeta,
11278
- config,
11279
- structureHash,
11280
- }, phaseState, {});
11281
- return await executePhase(tier2.node, currentMeta, phaseState, owedPhase, config, executor, watcher, structureHash, onProgress, logger);
11282
- }
11283
- // Nothing changed — bump _generatedAt to delay re-checking
11284
- await persistPhaseState({
11285
- metaPath: tier2.node.metaPath,
11286
- current: currentMeta,
11287
- config,
11288
- structureHash,
11289
- }, phaseState, { _generatedAt: new Date().toISOString() });
11290
- logger?.debug({ path: tier2.node.ownerPath }, 'Tier 2: no invalidation detected, bumped _generatedAt');
11291
- return { executed: false };
11292
- }
11293
- finally {
11294
- releaseLock(tier2.node.metaPath);
11295
- }
11296
- }
11297
11252
  /**
11298
11253
  * Execute exactly one phase on a meta.
11299
11254
  */
@@ -12271,7 +12226,7 @@ class Scheduler {
12271
12226
  const candidates = buildPhaseCandidates(result.entries, this.config.architectEvery);
12272
12227
  const winner = selectPhaseCandidate(candidates, this.config.depthWeight);
12273
12228
  if (!winner)
12274
- return null;
12229
+ return await this.discoverTier2Phase(candidates);
12275
12230
  return {
12276
12231
  path: winner.node.metaPath,
12277
12232
  phase: winner.owedPhase,
@@ -12283,6 +12238,57 @@ class Scheduler {
12283
12238
  return null;
12284
12239
  }
12285
12240
  }
12241
+ /**
12242
+ * Tier 2 invalidation: iterate all-fresh candidates (stalest first),
12243
+ * run computeInvalidation, and return the first that produces an owed phase.
12244
+ */
12245
+ async discoverTier2Phase(candidates) {
12246
+ const allTier2 = selectAllTier2Candidates(candidates);
12247
+ const limit = this.config.tier2ScanLimit;
12248
+ const tier2Candidates = allTier2.slice(0, limit);
12249
+ if (allTier2.length > limit) {
12250
+ this.logger.debug({ total: allTier2.length, limit }, 'Tier 2 scan limit reached, scanning subset');
12251
+ }
12252
+ let dirty = false;
12253
+ for (const t2 of tier2Candidates) {
12254
+ if (!acquireLock(t2.node.metaPath))
12255
+ continue;
12256
+ try {
12257
+ const currentMeta = await readMetaJson(t2.node.metaPath);
12258
+ const { scopeFiles } = await getScopeFiles(t2.node, this.watcher);
12259
+ const result = await computeInvalidation(currentMeta, scopeFiles, this.config, t2.node);
12260
+ const owedPhase = getOwedPhase(result.phaseState);
12261
+ if (owedPhase) {
12262
+ await persistPhaseState({
12263
+ metaPath: t2.node.metaPath,
12264
+ current: currentMeta,
12265
+ config: this.config,
12266
+ structureHash: result.structureHash,
12267
+ }, result.phaseState, {});
12268
+ this.cache.invalidate();
12269
+ return {
12270
+ path: t2.node.metaPath,
12271
+ phase: owedPhase,
12272
+ band: getPriorityBand(result.phaseState),
12273
+ };
12274
+ }
12275
+ // No invalidation — bump _generatedAt to delay re-checking
12276
+ await persistPhaseState({
12277
+ metaPath: t2.node.metaPath,
12278
+ current: currentMeta,
12279
+ config: this.config,
12280
+ structureHash: result.structureHash,
12281
+ }, result.phaseState, { _generatedAt: new Date().toISOString() });
12282
+ dirty = true;
12283
+ }
12284
+ finally {
12285
+ releaseLock(t2.node.metaPath);
12286
+ }
12287
+ }
12288
+ if (dirty)
12289
+ this.cache.invalidate();
12290
+ return null;
12291
+ }
12286
12292
  }
12287
12293
 
12288
12294
  /**
package/dist/index.js CHANGED
@@ -9050,6 +9050,8 @@ const serviceConfigSchema = metaConfigSchema.extend({
9050
9050
  watcherHealthIntervalMs: z.number().int().min(0).default(60_000),
9051
9051
  /** Logging configuration. */
9052
9052
  logging: loggingSchema.default(() => loggingSchema.parse({})),
9053
+ /** Max number of all-fresh candidates to scan per tick in Tier 2 invalidation. */
9054
+ tier2ScanLimit: z.number().int().min(1).default(50),
9053
9055
  /**
9054
9056
  * Auto-seed policy: declarative rules for auto-creating .meta/ directories.
9055
9057
  * Rules are evaluated in order; last match wins for steer/crossRefs.
@@ -10340,20 +10342,14 @@ function selectPhaseCandidate(metas, depthWeight) {
10340
10342
  return rankPhaseCandidates(metas, depthWeight)[0] ?? null;
10341
10343
  }
10342
10344
  /**
10343
- * Select the stalest all-fresh, non-disabled, non-locked meta for Tier 2
10344
- * invalidation. These are metas that Tier 1 considers fully fresh but may
10345
- * have structural or steer changes detectable only via I/O.
10346
- *
10347
- * @param metas - Phase candidate inputs (after Tier 1 filtering).
10348
- * @returns The stalest all-fresh candidate, or null if none exist.
10345
+ * Select all fully-fresh, non-disabled, non-locked metas sorted by staleness
10346
+ * (descending stalest first) for Tier 2 invalidation scanning.
10349
10347
  */
10350
- function selectTier2Candidate(metas) {
10351
- const eligible = metas
10348
+ function selectAllTier2Candidates(metas) {
10349
+ return metas
10352
10350
  .filter((m) => !m.locked && !m.disabled && isFullyFresh(m.phaseState))
10353
- .sort((a, b) => b.actualStaleness - a.actualStaleness);
10354
- if (eligible.length === 0)
10355
- return null;
10356
- return { node: eligible[0].node, meta: eligible[0].meta };
10351
+ .sort((a, b) => b.actualStaleness - a.actualStaleness)
10352
+ .map((m) => ({ node: m.node, meta: m.meta }));
10357
10353
  }
10358
10354
 
10359
10355
  /**
@@ -10736,8 +10732,9 @@ async function orchestratePhase(config, executor, watcher, targetPath, onProgres
10736
10732
  // Select best phase candidate
10737
10733
  const winner = selectPhaseCandidate(candidates, config.depthWeight);
10738
10734
  if (!winner) {
10739
- // ── Tier 2 fallback: deep invalidation on stalest all-fresh meta ──
10740
- return orchestrateTier2(candidates, config, executor, watcher, onProgress, logger);
10735
+ // Tier 2 is now handled by the scheduler; orchestratePhase only handles
10736
+ // targeted (override) paths and Tier 1 corpus-wide selection.
10737
+ return { executed: false };
10741
10738
  }
10742
10739
  // Acquire lock
10743
10740
  if (!acquireLock(winner.node.metaPath)) {
@@ -10803,48 +10800,6 @@ async function orchestrateTargeted(config, executor, watcher, targetPath, onProg
10803
10800
  releaseLock(node.metaPath);
10804
10801
  }
10805
10802
  }
10806
- /**
10807
- * Tier 2 invalidation fallback: pick the stalest all-fresh meta,
10808
- * run computeInvalidation (structure hash, steer, cross-refs), and
10809
- * either execute the owed phase or bump _generatedAt.
10810
- */
10811
- async function orchestrateTier2(candidates, config, executor, watcher, onProgress, logger) {
10812
- const tier2 = selectTier2Candidate(candidates);
10813
- if (!tier2)
10814
- return { executed: false };
10815
- if (!acquireLock(tier2.node.metaPath)) {
10816
- logger?.debug({ path: tier2.node.metaPath }, 'Tier 2 candidate is locked, skipping');
10817
- return { executed: false };
10818
- }
10819
- try {
10820
- const currentMeta = await readMetaJson(tier2.node.metaPath);
10821
- const { scopeFiles } = await getScopeFiles(tier2.node, watcher);
10822
- const { phaseState, structureHash } = await computeInvalidation(currentMeta, scopeFiles, config, tier2.node);
10823
- const owedPhase = getOwedPhase(phaseState);
10824
- if (owedPhase) {
10825
- // Something changed — persist invalidated state and execute owed phase
10826
- await persistPhaseState({
10827
- metaPath: tier2.node.metaPath,
10828
- current: currentMeta,
10829
- config,
10830
- structureHash,
10831
- }, phaseState, {});
10832
- return await executePhase(tier2.node, currentMeta, phaseState, owedPhase, config, executor, watcher, structureHash, onProgress, logger);
10833
- }
10834
- // Nothing changed — bump _generatedAt to delay re-checking
10835
- await persistPhaseState({
10836
- metaPath: tier2.node.metaPath,
10837
- current: currentMeta,
10838
- config,
10839
- structureHash,
10840
- }, phaseState, { _generatedAt: new Date().toISOString() });
10841
- logger?.debug({ path: tier2.node.ownerPath }, 'Tier 2: no invalidation detected, bumped _generatedAt');
10842
- return { executed: false };
10843
- }
10844
- finally {
10845
- releaseLock(tier2.node.metaPath);
10846
- }
10847
- }
10848
10803
  /**
10849
10804
  * Execute exactly one phase on a meta.
10850
10805
  */
@@ -11822,7 +11777,7 @@ class Scheduler {
11822
11777
  const candidates = buildPhaseCandidates(result.entries, this.config.architectEvery);
11823
11778
  const winner = selectPhaseCandidate(candidates, this.config.depthWeight);
11824
11779
  if (!winner)
11825
- return null;
11780
+ return await this.discoverTier2Phase(candidates);
11826
11781
  return {
11827
11782
  path: winner.node.metaPath,
11828
11783
  phase: winner.owedPhase,
@@ -11834,6 +11789,57 @@ class Scheduler {
11834
11789
  return null;
11835
11790
  }
11836
11791
  }
11792
+ /**
11793
+ * Tier 2 invalidation: iterate all-fresh candidates (stalest first),
11794
+ * run computeInvalidation, and return the first that produces an owed phase.
11795
+ */
11796
+ async discoverTier2Phase(candidates) {
11797
+ const allTier2 = selectAllTier2Candidates(candidates);
11798
+ const limit = this.config.tier2ScanLimit;
11799
+ const tier2Candidates = allTier2.slice(0, limit);
11800
+ if (allTier2.length > limit) {
11801
+ this.logger.debug({ total: allTier2.length, limit }, 'Tier 2 scan limit reached, scanning subset');
11802
+ }
11803
+ let dirty = false;
11804
+ for (const t2 of tier2Candidates) {
11805
+ if (!acquireLock(t2.node.metaPath))
11806
+ continue;
11807
+ try {
11808
+ const currentMeta = await readMetaJson(t2.node.metaPath);
11809
+ const { scopeFiles } = await getScopeFiles(t2.node, this.watcher);
11810
+ const result = await computeInvalidation(currentMeta, scopeFiles, this.config, t2.node);
11811
+ const owedPhase = getOwedPhase(result.phaseState);
11812
+ if (owedPhase) {
11813
+ await persistPhaseState({
11814
+ metaPath: t2.node.metaPath,
11815
+ current: currentMeta,
11816
+ config: this.config,
11817
+ structureHash: result.structureHash,
11818
+ }, result.phaseState, {});
11819
+ this.cache.invalidate();
11820
+ return {
11821
+ path: t2.node.metaPath,
11822
+ phase: owedPhase,
11823
+ band: getPriorityBand(result.phaseState),
11824
+ };
11825
+ }
11826
+ // No invalidation — bump _generatedAt to delay re-checking
11827
+ await persistPhaseState({
11828
+ metaPath: t2.node.metaPath,
11829
+ current: currentMeta,
11830
+ config: this.config,
11831
+ structureHash: result.structureHash,
11832
+ }, result.phaseState, { _generatedAt: new Date().toISOString() });
11833
+ dirty = true;
11834
+ }
11835
+ finally {
11836
+ releaseLock(t2.node.metaPath);
11837
+ }
11838
+ }
11839
+ if (dirty)
11840
+ this.cache.invalidate();
11841
+ return null;
11842
+ }
11837
11843
  }
11838
11844
 
11839
11845
  /**
@@ -5,5 +5,5 @@
5
5
  */
6
6
  export { type DerivationInputs, derivePhaseState } from './derivePhaseState.js';
7
7
  export { type ArchitectInvalidator, computeInvalidation, type InvalidationResult, type StalenessInputs, } from './invalidate.js';
8
- export { buildPhaseCandidates, type PhaseCandidate, type PhaseCandidateInput, rankPhaseCandidates, selectPhaseCandidate, selectTier2Candidate, type Tier2Candidate, } from './phaseScheduler.js';
8
+ export { buildPhaseCandidates, type PhaseCandidate, type PhaseCandidateInput, rankPhaseCandidates, selectAllTier2Candidates, selectPhaseCandidate, selectTier2Candidate, type Tier2Candidate, } from './phaseScheduler.js';
9
9
  export { architectSuccess, builderSuccess, criticSuccess, enforceInvariant, freshPhaseState, getOwedPhase, getPriorityBand, initialPhaseState, invalidateArchitect, invalidateBuilder, isFullyFresh, phaseFailed, phaseRunning, retryAllFailed, retryPhase, } from './phaseTransitions.js';
@@ -69,3 +69,8 @@ export interface Tier2Candidate {
69
69
  * @returns The stalest all-fresh candidate, or null if none exist.
70
70
  */
71
71
  export declare function selectTier2Candidate(metas: PhaseCandidateInput[]): Tier2Candidate | null;
72
+ /**
73
+ * Select all fully-fresh, non-disabled, non-locked metas sorted by staleness
74
+ * (descending — stalest first) for Tier 2 invalidation scanning.
75
+ */
76
+ export declare function selectAllTier2Candidates(metas: PhaseCandidateInput[]): Tier2Candidate[];
@@ -63,4 +63,9 @@ export declare class Scheduler {
63
63
  * with weighted staleness as tiebreaker within a band.
64
64
  */
65
65
  private discoverNextPhase;
66
+ /**
67
+ * Tier 2 invalidation: iterate all-fresh candidates (stalest first),
68
+ * run computeInvalidation, and return the first that produces an owed phase.
69
+ */
70
+ private discoverTier2Phase;
66
71
  }
@@ -44,6 +44,7 @@ export declare const serviceConfigSchema: z.ZodObject<{
44
44
  level: z.ZodDefault<z.ZodString>;
45
45
  file: z.ZodOptional<z.ZodString>;
46
46
  }, z.core.$strip>>;
47
+ tier2ScanLimit: z.ZodDefault<z.ZodNumber>;
47
48
  autoSeed: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
48
49
  match: z.ZodString;
49
50
  steer: z.ZodOptional<z.ZodString>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-meta",
3
- "version": "0.15.7",
3
+ "version": "0.15.8",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Fastify HTTP service for the Jeeves Meta synthesis engine",
6
6
  "license": "BSD-3-Clause",