instar 1.3.781 → 1.3.783

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.
Files changed (63) hide show
  1. package/dist/core/IntelligenceRouter.d.ts +9 -14
  2. package/dist/core/IntelligenceRouter.d.ts.map +1 -1
  3. package/dist/core/IntelligenceRouter.js +117 -56
  4. package/dist/core/IntelligenceRouter.js.map +1 -1
  5. package/dist/core/MeteredSpendGate.d.ts +119 -0
  6. package/dist/core/MeteredSpendGate.d.ts.map +1 -0
  7. package/dist/core/MeteredSpendGate.js +228 -0
  8. package/dist/core/MeteredSpendGate.js.map +1 -0
  9. package/dist/core/MeteredSpendLedger.d.ts +154 -0
  10. package/dist/core/MeteredSpendLedger.d.ts.map +1 -0
  11. package/dist/core/MeteredSpendLedger.js +434 -0
  12. package/dist/core/MeteredSpendLedger.js.map +1 -0
  13. package/dist/core/PinAttemptStore.d.ts +35 -0
  14. package/dist/core/PinAttemptStore.d.ts.map +1 -0
  15. package/dist/core/PinAttemptStore.js +85 -0
  16. package/dist/core/PinAttemptStore.js.map +1 -0
  17. package/dist/core/PostUpdateMigrator.d.ts +7 -0
  18. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  19. package/dist/core/PostUpdateMigrator.js +24 -0
  20. package/dist/core/PostUpdateMigrator.js.map +1 -1
  21. package/dist/core/RenderedPlanStore.d.ts +61 -0
  22. package/dist/core/RenderedPlanStore.d.ts.map +1 -0
  23. package/dist/core/RenderedPlanStore.js +92 -0
  24. package/dist/core/RenderedPlanStore.js.map +1 -0
  25. package/dist/core/RoutingSpendCapsStore.d.ts +113 -0
  26. package/dist/core/RoutingSpendCapsStore.d.ts.map +1 -0
  27. package/dist/core/RoutingSpendCapsStore.js +241 -0
  28. package/dist/core/RoutingSpendCapsStore.js.map +1 -0
  29. package/dist/core/SpendAlertResolver.d.ts +77 -0
  30. package/dist/core/SpendAlertResolver.d.ts.map +1 -0
  31. package/dist/core/SpendAlertResolver.js +141 -0
  32. package/dist/core/SpendAlertResolver.js.map +1 -0
  33. package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
  34. package/dist/core/WriteDomainRegistry.js +17 -0
  35. package/dist/core/WriteDomainRegistry.js.map +1 -1
  36. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  37. package/dist/core/devGatedFeatures.js +9 -0
  38. package/dist/core/devGatedFeatures.js.map +1 -1
  39. package/dist/core/routingSpendView.d.ts +10 -2
  40. package/dist/core/routingSpendView.d.ts.map +1 -1
  41. package/dist/core/routingSpendView.js +18 -10
  42. package/dist/core/routingSpendView.js.map +1 -1
  43. package/dist/core/types.d.ts +24 -2
  44. package/dist/core/types.d.ts.map +1 -1
  45. package/dist/core/types.js.map +1 -1
  46. package/dist/server/AgentServer.d.ts +9 -0
  47. package/dist/server/AgentServer.d.ts.map +1 -1
  48. package/dist/server/AgentServer.js +177 -0
  49. package/dist/server/AgentServer.js.map +1 -1
  50. package/dist/server/routes.d.ts +10 -0
  51. package/dist/server/routes.d.ts.map +1 -1
  52. package/dist/server/routes.js +196 -4
  53. package/dist/server/routes.js.map +1 -1
  54. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  55. package/dist/testing/selfActionRegistry.js +78 -0
  56. package/dist/testing/selfActionRegistry.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/data/builtin-manifest.json +64 -64
  59. package/upgrades/1.3.782.md +60 -0
  60. package/upgrades/1.3.783.md +66 -0
  61. package/upgrades/nature-routing-a2.2-enforcing.eli16.md +50 -0
  62. package/upgrades/side-effects/nature-routing-a2.2-enforcing.md +142 -0
  63. package/upgrades/side-effects/routing-spend-increment-b.md +44 -0
@@ -85,6 +85,15 @@ import os from 'node:os';
85
85
  import { TokenLedger } from '../monitoring/TokenLedger.js';
86
86
  import { FeatureMetricsLedger } from '../monitoring/FeatureMetricsLedger.js';
87
87
  import { RoutingPriceAuthority } from '../core/routingPriceAuthority.js';
88
+ import { MeteredSpendLedger } from '../core/MeteredSpendLedger.js';
89
+ import { RoutingSpendCapsStore } from '../core/RoutingSpendCapsStore.js';
90
+ import { MeteredSpendGate } from '../core/MeteredSpendGate.js';
91
+ import { RenderedPlanStore } from '../core/RenderedPlanStore.js';
92
+ import { PinAttemptStore } from '../core/PinAttemptStore.js';
93
+ import { SpendAlertResolver, buildStalePriceAlert } from '../core/SpendAlertResolver.js';
94
+ import { DEFAULT_FRESHNESS_SLA_DAYS } from '../core/routingPriceAuthority.js';
95
+ import { METERED_ROUTING_DOORS } from '../data/llmBenchCoverage.js';
96
+ import { DEFAULT_METERED_CAPS } from '../core/routingSpendView.js';
88
97
  import { A2ADeliveryTracker } from '../threadline/A2ADeliveryTracker.js';
89
98
  import { setFeatureMetricsRecorder } from '../core/CircuitBreakingIntelligenceProvider.js';
90
99
  import { TokenLedgerPoller } from '../monitoring/TokenLedgerPoller.js';
@@ -170,6 +179,15 @@ export class AgentServer {
170
179
  tokenLedger = null;
171
180
  featureMetricsLedger = null;
172
181
  routingPriceAuthority = null;
182
+ /** Increment B money layer — all null unless routingSpend.money.enabled === true (FD-16). */
183
+ meteredSpendLedger = null;
184
+ routingSpendCapsStore = null;
185
+ meteredSpendGate = null;
186
+ spendPlanStore = null;
187
+ pinAttemptStore = null;
188
+ meteredSweepTimer = null;
189
+ spendAlertResolver = null;
190
+ spendAlertTimer = null;
173
191
  featureMetricsPruneTimer = null;
174
192
  a2aDeliveryTracker = null;
175
193
  tokenLedgerPoller = null;
@@ -624,6 +642,150 @@ export class AgentServer {
624
642
  this.routingPriceAuthority = null;
625
643
  }
626
644
  }
645
+ // Durable per-IP PIN-attempt lockout (S2-1) — hardens EVERY PIN route
646
+ // (mandates, money surfaces, external-hog arm) so a restart no longer
647
+ // resets brute-force lockout. Independent of the money layer.
648
+ try {
649
+ this.pinAttemptStore = new PinAttemptStore({ stateDir: options.config.stateDir });
650
+ }
651
+ catch (err) {
652
+ // @silent-fallback-ok: loud warn + degrade to the pre-existing in-memory
653
+ // lockout (defence-in-depth layer, never the auth decision itself).
654
+ console.warn('[instar] pin-attempt store init failed (non-fatal — in-memory lockout only):', err);
655
+ this.pinAttemptStore = null;
656
+ }
657
+ // ── Increment B MONEY layer (routing-control-room-spend §Layer 3 / Surface 2).
658
+ // EXPLICIT enable only (`routingSpend.money.enabled === true`) — a documented
659
+ // DARK_GATE_EXCLUSIONS action-bearing case (FD-16): NEVER resolveDevAgentGate.
660
+ // Even enabled, every door stays deny-by-default until a per-door PIN go-live.
661
+ const moneyCfg = options.config.routingSpend?.money;
662
+ if (moneyCfg?.enabled === true) {
663
+ try {
664
+ this.meteredSpendLedger = new MeteredSpendLedger({
665
+ stateDir: options.config.stateDir,
666
+ reserveTtlMs: moneyCfg.reserveTtlMs,
667
+ });
668
+ this.routingSpendCapsStore = new RoutingSpendCapsStore({
669
+ stateDir: options.config.stateDir,
670
+ knownKeyRefs: new Set(Object.keys(DEFAULT_METERED_CAPS)),
671
+ knownDoors: new Set([...METERED_ROUTING_DOORS]),
672
+ });
673
+ this.spendPlanStore = new RenderedPlanStore();
674
+ const machineId = options.config.machineId ?? 'single-machine';
675
+ const poolOn = options.config.multiMachine?.sessionPool?.enabled === true;
676
+ this.meteredSpendGate = this.routingPriceAuthority
677
+ ? new MeteredSpendGate({
678
+ ledger: this.meteredSpendLedger,
679
+ prices: this.routingPriceAuthority,
680
+ capsStore: this.routingSpendCapsStore,
681
+ machineId,
682
+ // Single-machine trivially self-confirms (it IS the pool). On a
683
+ // multi-machine pool the REAL designation re-confirmation plumbing
684
+ // lands with the metered dispatch seam — until then the gate
685
+ // SELF-FENCES (fails closed) there, the safe direction, matching
686
+ // the spec's own "paid routing is single-machine until D".
687
+ leaseConfirmedAgoMs: () => (poolOn ? null : 0),
688
+ })
689
+ : null;
690
+ // Reserve-expiry sweep: at boot + cadence (money-side; takes the per-key mutex).
691
+ // @silent-fallback-ok: a failed sweep pass retries on the next cadence tick;
692
+ // un-expired reserves only ever OVER-count committed (the safe direction).
693
+ const sweep = () => void this.meteredSpendLedger?.sweepExpired().catch(() => { });
694
+ sweep();
695
+ this.meteredSweepTimer = setInterval(sweep, 5 * 60 * 1000);
696
+ this.meteredSweepTimer.unref?.();
697
+ // Stale-price / observed-drift alerts ride Increment B (C5-5: stale pricing
698
+ // changes money ADMISSION behavior, so its alarm belongs to the money
699
+ // increment) on the minimal resolver foundation — ladder + lifeline fallback.
700
+ const alertsCfg = options.config.routingSpend;
701
+ const tg = this.telegramAdapter;
702
+ if (tg) {
703
+ const persistPath = path.join(options.config.stateDir, 'state', 'routing-spend-alert-topic.json');
704
+ this.spendAlertResolver = new SpendAlertResolver({
705
+ configuredTopicId: () => {
706
+ const id = alertsCfg?.alerts?.telegramTopicId;
707
+ return typeof id === 'number' && Number.isFinite(id) ? id : undefined;
708
+ },
709
+ readPersistedTopicId: () => {
710
+ try {
711
+ const raw = JSON.parse(fs.readFileSync(persistPath, 'utf-8'));
712
+ return typeof raw.topicId === 'number' ? raw.topicId : undefined;
713
+ }
714
+ catch {
715
+ // @silent-fallback-ok: no persisted record yet — rung 2 simply misses.
716
+ return undefined;
717
+ }
718
+ },
719
+ persistTopicId: (topicId) => {
720
+ try {
721
+ const tmp = persistPath + '.tmp';
722
+ fs.writeFileSync(tmp, JSON.stringify({ topicId }), { mode: 0o600 });
723
+ fs.renameSync(tmp, persistPath);
724
+ }
725
+ catch {
726
+ // @silent-fallback-ok: a failed persist means the next resolve re-runs
727
+ // the ladder; creation stays fenced, so no duplicate can result.
728
+ }
729
+ },
730
+ // Single machine trivially self-confirms (it IS the serving lease); on a
731
+ // multi-machine pool the confirmed-lease plumbing rides the C increment —
732
+ // until then this machine does NOT create (fail toward the lifeline).
733
+ servingLeaseConfirmedAgoMs: () => (poolOn ? null : 0),
734
+ createTopic: async () => {
735
+ const t = await tg.createForumTopic('💰 Routing & Spend Alerts', undefined, {
736
+ origin: 'system',
737
+ bounded: true,
738
+ label: 'routing-spend-alerts',
739
+ });
740
+ return t.topicId;
741
+ },
742
+ sendToTopic: async (topicId, text) => {
743
+ await tg.sendToTopic(topicId, text);
744
+ return true;
745
+ },
746
+ lifelineTopicId: () => tg.getLifelineTopicId(),
747
+ });
748
+ const staleCheck = () => {
749
+ try {
750
+ const prices = this.routingPriceAuthority;
751
+ const caps = this.routingSpendCapsStore;
752
+ const resolver = this.spendAlertResolver;
753
+ if (!prices || !caps || !resolver)
754
+ return;
755
+ prices.reloadIfChanged();
756
+ const store = caps.read();
757
+ for (const [door, g] of Object.entries(store.goLive)) {
758
+ if (!g.enabled)
759
+ continue; // staleness only alarms where money can move
760
+ const res = prices.resolve(door, '__staleness-probe__', Date.now());
761
+ if (res.priceStale && res.newestPointAgeDays !== null) {
762
+ const meta = prices.doorMetaFor(door);
763
+ void resolver.emit(buildStalePriceAlert(door, res.newestPointAgeDays, meta?.freshnessSlaDays ?? DEFAULT_FRESHNESS_SLA_DAYS));
764
+ }
765
+ }
766
+ }
767
+ catch {
768
+ // @silent-fallback-ok: the alert cadence is observability — a failed
769
+ // pass retries next tick; it must never disturb the server loop.
770
+ }
771
+ };
772
+ const staleIntervalMs = Math.max(1, alertsCfg?.money?.priceStaleCheckIntervalHours ?? 6) * 60 * 60 * 1000;
773
+ staleCheck();
774
+ this.spendAlertTimer = setInterval(staleCheck, staleIntervalMs);
775
+ this.spendAlertTimer.unref?.();
776
+ }
777
+ }
778
+ catch (err) {
779
+ // @silent-fallback-ok: NOT a silent fallback — fail CLOSED, loudly: a
780
+ // money-layer init failure leaves every component null, the routes 503,
781
+ // and no metered admission is possible anywhere.
782
+ console.error('[instar] routing-spend MONEY layer init FAILED (fail-closed — money routes 503):', err);
783
+ this.meteredSpendLedger = null;
784
+ this.routingSpendCapsStore = null;
785
+ this.meteredSpendGate = null;
786
+ this.spendPlanStore = null;
787
+ }
788
+ }
627
789
  // Observable Intelligence × Responsible Resource: the audit trail is kept
628
790
  // long enough to see behaviour/performance trends, then aged out — never
629
791
  // hoarded forever. Default 30d; tune via monitoring.featureMetrics.retentionDays
@@ -1959,6 +2121,11 @@ export class AgentServer {
1959
2121
  tokenLedger: this.tokenLedger,
1960
2122
  featureMetricsLedger: this.featureMetricsLedger,
1961
2123
  routingPriceAuthority: this.routingPriceAuthority,
2124
+ meteredSpendLedger: this.meteredSpendLedger,
2125
+ routingSpendCapsStore: this.routingSpendCapsStore,
2126
+ meteredSpendGate: this.meteredSpendGate,
2127
+ spendPlanStore: this.spendPlanStore,
2128
+ pinAttemptStore: this.pinAttemptStore,
1962
2129
  resourceLedger: this.resourceLedger,
1963
2130
  processFootprintMonitor: this.processFootprintMonitor,
1964
2131
  approvalLedger: this.approvalLedger,
@@ -3740,6 +3907,16 @@ export class AgentServer {
3740
3907
  clearInterval(this.featureMetricsPruneTimer);
3741
3908
  }
3742
3909
  catch { /* @silent-fallback-ok: timer teardown is best-effort cleanup at shutdown */ }
3910
+ try {
3911
+ if (this.meteredSweepTimer)
3912
+ clearInterval(this.meteredSweepTimer);
3913
+ }
3914
+ catch { /* @silent-fallback-ok: timer teardown is best-effort cleanup at shutdown */ }
3915
+ try {
3916
+ if (this.spendAlertTimer)
3917
+ clearInterval(this.spendAlertTimer);
3918
+ }
3919
+ catch { /* @silent-fallback-ok: timer teardown is best-effort cleanup at shutdown */ }
3743
3920
  this.featureMetricsPruneTimer = null;
3744
3921
  }
3745
3922
  if (this.tokenLedger) {