@karmaniverous/jeeves-meta 0.15.6 → 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.
@@ -10788,6 +10790,16 @@ function rankPhaseCandidates(metas, depthWeight) {
10788
10790
  function selectPhaseCandidate(metas, depthWeight) {
10789
10791
  return rankPhaseCandidates(metas, depthWeight)[0] ?? null;
10790
10792
  }
10793
+ /**
10794
+ * Select all fully-fresh, non-disabled, non-locked metas sorted by staleness
10795
+ * (descending — stalest first) for Tier 2 invalidation scanning.
10796
+ */
10797
+ function selectAllTier2Candidates(metas) {
10798
+ return metas
10799
+ .filter((m) => !m.locked && !m.disabled && isFullyFresh(m.phaseState))
10800
+ .sort((a, b) => b.actualStaleness - a.actualStaleness)
10801
+ .map((m) => ({ node: m.node, meta: m.meta }));
10802
+ }
10791
10803
 
10792
10804
  /**
10793
10805
  * Shared error utilities.
@@ -11169,6 +11181,8 @@ async function orchestratePhase(config, executor, watcher, targetPath, onProgres
11169
11181
  // Select best phase candidate
11170
11182
  const winner = selectPhaseCandidate(candidates, config.depthWeight);
11171
11183
  if (!winner) {
11184
+ // Tier 2 is now handled by the scheduler; orchestratePhase only handles
11185
+ // targeted (override) paths and Tier 1 corpus-wide selection.
11172
11186
  return { executed: false };
11173
11187
  }
11174
11188
  // Acquire lock
@@ -12212,7 +12226,7 @@ class Scheduler {
12212
12226
  const candidates = buildPhaseCandidates(result.entries, this.config.architectEvery);
12213
12227
  const winner = selectPhaseCandidate(candidates, this.config.depthWeight);
12214
12228
  if (!winner)
12215
- return null;
12229
+ return await this.discoverTier2Phase(candidates);
12216
12230
  return {
12217
12231
  path: winner.node.metaPath,
12218
12232
  phase: winner.owedPhase,
@@ -12224,6 +12238,57 @@ class Scheduler {
12224
12238
  return null;
12225
12239
  }
12226
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
+ }
12227
12292
  }
12228
12293
 
12229
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.
@@ -10339,6 +10341,16 @@ function rankPhaseCandidates(metas, depthWeight) {
10339
10341
  function selectPhaseCandidate(metas, depthWeight) {
10340
10342
  return rankPhaseCandidates(metas, depthWeight)[0] ?? null;
10341
10343
  }
10344
+ /**
10345
+ * Select all fully-fresh, non-disabled, non-locked metas sorted by staleness
10346
+ * (descending — stalest first) for Tier 2 invalidation scanning.
10347
+ */
10348
+ function selectAllTier2Candidates(metas) {
10349
+ return metas
10350
+ .filter((m) => !m.locked && !m.disabled && isFullyFresh(m.phaseState))
10351
+ .sort((a, b) => b.actualStaleness - a.actualStaleness)
10352
+ .map((m) => ({ node: m.node, meta: m.meta }));
10353
+ }
10342
10354
 
10343
10355
  /**
10344
10356
  * Shared error utilities.
@@ -10720,6 +10732,8 @@ async function orchestratePhase(config, executor, watcher, targetPath, onProgres
10720
10732
  // Select best phase candidate
10721
10733
  const winner = selectPhaseCandidate(candidates, config.depthWeight);
10722
10734
  if (!winner) {
10735
+ // Tier 2 is now handled by the scheduler; orchestratePhase only handles
10736
+ // targeted (override) paths and Tier 1 corpus-wide selection.
10723
10737
  return { executed: false };
10724
10738
  }
10725
10739
  // Acquire lock
@@ -11763,7 +11777,7 @@ class Scheduler {
11763
11777
  const candidates = buildPhaseCandidates(result.entries, this.config.architectEvery);
11764
11778
  const winner = selectPhaseCandidate(candidates, this.config.depthWeight);
11765
11779
  if (!winner)
11766
- return null;
11780
+ return await this.discoverTier2Phase(candidates);
11767
11781
  return {
11768
11782
  path: winner.node.metaPath,
11769
11783
  phase: winner.owedPhase,
@@ -11775,6 +11789,57 @@ class Scheduler {
11775
11789
  return null;
11776
11790
  }
11777
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
+ }
11778
11843
  }
11779
11844
 
11780
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, } 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';
@@ -55,3 +55,22 @@ export declare function rankPhaseCandidates(metas: PhaseCandidateInput[], depthW
55
55
  * @returns The winning candidate, or null if no phase is ready.
56
56
  */
57
57
  export declare function selectPhaseCandidate(metas: PhaseCandidateInput[], depthWeight: number): PhaseCandidate | null;
58
+ /** Result of Tier 2 candidate selection. */
59
+ export interface Tier2Candidate {
60
+ node: MetaNode;
61
+ meta: MetaJson;
62
+ }
63
+ /**
64
+ * Select the stalest all-fresh, non-disabled, non-locked meta for Tier 2
65
+ * invalidation. These are metas that Tier 1 considers fully fresh but may
66
+ * have structural or steer changes detectable only via I/O.
67
+ *
68
+ * @param metas - Phase candidate inputs (after Tier 1 filtering).
69
+ * @returns The stalest all-fresh candidate, or null if none exist.
70
+ */
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.6",
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",