chainlesschain 0.81.0 → 0.143.0
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.
- package/bin/chainlesschain.js +0 -0
- package/package.json +1 -1
- package/src/commands/a2a.js +62 -0
- package/src/commands/activitypub.js +61 -0
- package/src/commands/agent-network.js +254 -1
- package/src/commands/agent.js +117 -0
- package/src/commands/audit.js +302 -0
- package/src/commands/automation.js +271 -1
- package/src/commands/bi.js +61 -0
- package/src/commands/bm25.js +78 -0
- package/src/commands/browse.js +64 -0
- package/src/commands/ccron.js +78 -0
- package/src/commands/codegen.js +224 -0
- package/src/commands/collab.js +341 -0
- package/src/commands/compliance.js +1075 -0
- package/src/commands/compt.js +78 -0
- package/src/commands/consol.js +231 -0
- package/src/commands/cowork.js +263 -0
- package/src/commands/crosschain.js +62 -0
- package/src/commands/dao.js +62 -0
- package/src/commands/dbevo.js +284 -0
- package/src/commands/dev.js +252 -0
- package/src/commands/did.js +358 -0
- package/src/commands/dlp.js +61 -0
- package/src/commands/economy.js +56 -0
- package/src/commands/encrypt.js +341 -0
- package/src/commands/evolution.js +56 -0
- package/src/commands/evomap.js +61 -0
- package/src/commands/export.js +256 -1
- package/src/commands/fflag.js +178 -0
- package/src/commands/fusion.js +258 -0
- package/src/commands/git.js +45 -0
- package/src/commands/governance.js +325 -0
- package/src/commands/hardening.js +411 -0
- package/src/commands/hmemory.js +56 -0
- package/src/commands/hook.js +148 -0
- package/src/commands/import.js +252 -0
- package/src/commands/incentive.js +322 -0
- package/src/commands/inference.js +42 -0
- package/src/commands/infra.js +244 -0
- package/src/commands/instinct.js +260 -0
- package/src/commands/ipfs.js +318 -0
- package/src/commands/itbudget.js +45 -0
- package/src/commands/kg.js +387 -0
- package/src/commands/llm.js +263 -0
- package/src/commands/lowcode.js +44 -0
- package/src/commands/matrix.js +62 -0
- package/src/commands/mcp.js +221 -0
- package/src/commands/mcpscaf.js +41 -0
- package/src/commands/meminj.js +41 -0
- package/src/commands/memory.js +248 -0
- package/src/commands/multimodal.js +296 -0
- package/src/commands/nlprog.js +356 -0
- package/src/commands/nostr.js +62 -0
- package/src/commands/note.js +244 -0
- package/src/commands/ops.js +354 -0
- package/src/commands/orchestrate.js +166 -0
- package/src/commands/orchgov.js +45 -0
- package/src/commands/org.js +277 -0
- package/src/commands/p2p.js +390 -0
- package/src/commands/pdfp.js +78 -0
- package/src/commands/perception.js +290 -0
- package/src/commands/perf.js +39 -0
- package/src/commands/perm.js +45 -0
- package/src/commands/permmem.js +251 -0
- package/src/commands/pipeline.js +57 -1
- package/src/commands/planmode.js +45 -0
- package/src/commands/plugin-ecosystem.js +273 -0
- package/src/commands/pqc.js +393 -0
- package/src/commands/promcomp.js +82 -0
- package/src/commands/quantization.js +351 -0
- package/src/commands/rcache.js +271 -0
- package/src/commands/recommend.js +382 -0
- package/src/commands/runtime.js +307 -0
- package/src/commands/scim.js +262 -0
- package/src/commands/seshhook.js +41 -0
- package/src/commands/seshsearch.js +41 -0
- package/src/commands/seshtail.js +41 -0
- package/src/commands/seshu.js +41 -0
- package/src/commands/session.js +258 -0
- package/src/commands/sganal.js +78 -0
- package/src/commands/siem.js +40 -0
- package/src/commands/skill.js +267 -1
- package/src/commands/slotfill.js +41 -0
- package/src/commands/social.js +290 -0
- package/src/commands/sso.js +186 -1
- package/src/commands/svccont.js +45 -0
- package/src/commands/sync.js +256 -0
- package/src/commands/tech.js +338 -0
- package/src/commands/tenant.js +351 -0
- package/src/commands/tms.js +45 -0
- package/src/commands/tokens.js +269 -0
- package/src/commands/topiccls.js +45 -0
- package/src/commands/trust.js +249 -0
- package/src/commands/uprof.js +45 -0
- package/src/commands/vcheck.js +78 -0
- package/src/commands/wallet.js +277 -0
- package/src/commands/webfetch.js +41 -0
- package/src/commands/workflow.js +171 -0
- package/src/commands/zkp.js +62 -0
- package/src/harness/prompt-compressor.js +331 -0
- package/src/index.js +65 -1
- package/src/lib/a2a-protocol.js +105 -0
- package/src/lib/activitypub-bridge.js +105 -0
- package/src/lib/agent-coordinator.js +325 -0
- package/src/lib/agent-economy.js +105 -0
- package/src/lib/agent-network.js +387 -0
- package/src/lib/agent-router.js +395 -0
- package/src/lib/aiops.js +478 -0
- package/src/lib/app-builder.js +105 -0
- package/src/lib/audit-logger.js +379 -0
- package/src/lib/automation-engine.js +330 -0
- package/src/lib/autonomous-agent.js +105 -0
- package/src/lib/autonomous-developer.js +350 -0
- package/src/lib/bi-engine.js +105 -0
- package/src/lib/bm25-search.js +81 -0
- package/src/lib/browser-automation.js +105 -0
- package/src/lib/code-agent.js +323 -0
- package/src/lib/collaboration-governance.js +364 -0
- package/src/lib/community-governance.js +436 -0
- package/src/lib/compliance-framework-reporter.js +105 -0
- package/src/lib/compliance-manager.js +434 -0
- package/src/lib/compression-telemetry.js +81 -0
- package/src/lib/content-recommendation.js +469 -0
- package/src/lib/content-recommender.js +105 -0
- package/src/lib/cowork-cron.js +81 -0
- package/src/lib/cowork-task-runner.js +105 -0
- package/src/lib/cross-chain.js +105 -0
- package/src/lib/crypto-manager.js +350 -0
- package/src/lib/dao-governance.js +105 -0
- package/src/lib/dbevo.js +338 -0
- package/src/lib/decentral-infra.js +340 -0
- package/src/lib/did-manager.js +367 -0
- package/src/lib/dlp-engine.js +105 -0
- package/src/lib/evolution-system.js +105 -0
- package/src/lib/evomap-manager.js +105 -0
- package/src/lib/execution-backend.js +105 -0
- package/src/lib/feature-flags.js +85 -0
- package/src/lib/git-integration.js +105 -0
- package/src/lib/hardening-manager.js +348 -0
- package/src/lib/hierarchical-memory.js +105 -0
- package/src/lib/hook-manager.js +380 -0
- package/src/lib/inference-network.js +105 -0
- package/src/lib/instinct-manager.js +332 -0
- package/src/lib/ipfs-storage.js +334 -0
- package/src/lib/iteration-budget.js +105 -0
- package/src/lib/knowledge-exporter.js +381 -0
- package/src/lib/knowledge-graph.js +432 -0
- package/src/lib/knowledge-importer.js +379 -0
- package/src/lib/llm-providers.js +391 -0
- package/src/lib/matrix-bridge.js +105 -0
- package/src/lib/mcp-registry.js +333 -0
- package/src/lib/mcp-scaffold.js +81 -0
- package/src/lib/memory-injection.js +81 -0
- package/src/lib/memory-manager.js +330 -0
- package/src/lib/multimodal.js +346 -0
- package/src/lib/nl-programming.js +343 -0
- package/src/lib/nostr-bridge.js +105 -0
- package/src/lib/note-versioning.js +327 -0
- package/src/lib/orchestrator.js +105 -0
- package/src/lib/org-manager.js +323 -0
- package/src/lib/p2p-manager.js +387 -0
- package/src/lib/pdf-parser.js +81 -0
- package/src/lib/perception.js +346 -0
- package/src/lib/perf-tuning.js +109 -1
- package/src/lib/permanent-memory.js +320 -0
- package/src/lib/permission-engine.js +81 -0
- package/src/lib/pipeline-orchestrator.js +105 -0
- package/src/lib/plan-mode.js +81 -0
- package/src/lib/plugin-ecosystem.js +377 -0
- package/src/lib/pqc-manager.js +368 -0
- package/src/lib/prompt-compressor.js +1 -10
- package/src/lib/protocol-fusion.js +417 -0
- package/src/lib/quantization.js +325 -0
- package/src/lib/response-cache.js +327 -0
- package/src/lib/scim-manager.js +329 -0
- package/src/lib/service-container.js +81 -0
- package/src/lib/session-consolidator.js +105 -0
- package/src/lib/session-hooks.js +81 -0
- package/src/lib/session-manager.js +329 -0
- package/src/lib/session-search.js +81 -0
- package/src/lib/session-tail.js +81 -0
- package/src/lib/session-usage.js +83 -0
- package/src/lib/siem-exporter.js +105 -0
- package/src/lib/skill-loader.js +377 -0
- package/src/lib/slot-filler.js +81 -0
- package/src/lib/social-graph-analytics.js +81 -0
- package/src/lib/social-graph.js +81 -0
- package/src/lib/social-manager.js +326 -0
- package/src/lib/sso-manager.js +332 -0
- package/src/lib/sub-agent-registry.js +110 -0
- package/src/lib/sync-manager.js +326 -0
- package/src/lib/task-model-selector.js +81 -0
- package/src/lib/tech-learning-engine.js +369 -0
- package/src/lib/tenant-saas.js +460 -0
- package/src/lib/threat-intel.js +335 -0
- package/src/lib/todo-manager.js +105 -0
- package/src/lib/token-incentive.js +293 -0
- package/src/lib/token-tracker.js +329 -0
- package/src/lib/topic-classifier.js +105 -0
- package/src/lib/trust-security.js +390 -0
- package/src/lib/ueba.js +389 -0
- package/src/lib/universal-runtime.js +325 -0
- package/src/lib/user-profile.js +81 -0
- package/src/lib/version-checker.js +81 -0
- package/src/lib/wallet-manager.js +326 -0
- package/src/lib/web-fetch.js +81 -0
- package/src/lib/workflow-engine.js +322 -0
- package/src/lib/zkp-engine.js +105 -0
|
@@ -511,3 +511,296 @@ export function _resetState() {
|
|
|
511
511
|
_contributions.clear();
|
|
512
512
|
_seq = 0;
|
|
513
513
|
}
|
|
514
|
+
|
|
515
|
+
// ═══════════════════════════════════════════════════════════════
|
|
516
|
+
// Phase 66 V2 — Account + Claim lifecycle, per-user claim cap
|
|
517
|
+
// ═══════════════════════════════════════════════════════════════
|
|
518
|
+
|
|
519
|
+
export const ACCOUNT_STATUS_V2 = Object.freeze({
|
|
520
|
+
ACTIVE: "active",
|
|
521
|
+
FROZEN: "frozen",
|
|
522
|
+
CLOSED: "closed",
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
export const CLAIM_STATUS_V2 = Object.freeze({
|
|
526
|
+
PENDING: "pending",
|
|
527
|
+
APPROVED: "approved",
|
|
528
|
+
PAID: "paid",
|
|
529
|
+
REJECTED: "rejected",
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
export const TOKEN_DEFAULT_MAX_PENDING_CLAIMS_PER_USER = 50;
|
|
533
|
+
export const TOKEN_DEFAULT_CLAIM_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
534
|
+
export const TOKEN_DEFAULT_MAX_CLAIM_AMOUNT = 10000;
|
|
535
|
+
|
|
536
|
+
let _maxPendingClaimsPerUser = TOKEN_DEFAULT_MAX_PENDING_CLAIMS_PER_USER;
|
|
537
|
+
let _claimExpiryMs = TOKEN_DEFAULT_CLAIM_EXPIRY_MS;
|
|
538
|
+
let _maxClaimAmount = TOKEN_DEFAULT_MAX_CLAIM_AMOUNT;
|
|
539
|
+
|
|
540
|
+
const _accountStatesV2 = new Map();
|
|
541
|
+
const _claimStatesV2 = new Map();
|
|
542
|
+
|
|
543
|
+
const ACCOUNT_TRANSITIONS_V2 = new Map([
|
|
544
|
+
["active", new Set(["frozen", "closed"])],
|
|
545
|
+
["frozen", new Set(["active", "closed"])],
|
|
546
|
+
]);
|
|
547
|
+
const ACCOUNT_TERMINALS_V2 = new Set(["closed"]);
|
|
548
|
+
|
|
549
|
+
const CLAIM_TRANSITIONS_V2 = new Map([
|
|
550
|
+
["pending", new Set(["approved", "rejected"])],
|
|
551
|
+
["approved", new Set(["paid", "rejected"])],
|
|
552
|
+
]);
|
|
553
|
+
const CLAIM_TERMINALS_V2 = new Set(["paid", "rejected"]);
|
|
554
|
+
|
|
555
|
+
function _positiveInt(n, label) {
|
|
556
|
+
const v = Number(n);
|
|
557
|
+
if (!Number.isFinite(v) || v <= 0) {
|
|
558
|
+
throw new Error(`${label} must be a positive integer`);
|
|
559
|
+
}
|
|
560
|
+
return Math.floor(v);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function setMaxPendingClaimsPerUser(n) {
|
|
564
|
+
_maxPendingClaimsPerUser = _positiveInt(n, "maxPendingClaimsPerUser");
|
|
565
|
+
return _maxPendingClaimsPerUser;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function setClaimExpiryMs(ms) {
|
|
569
|
+
_claimExpiryMs = _positiveInt(ms, "claimExpiryMs");
|
|
570
|
+
return _claimExpiryMs;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export function setMaxClaimAmount(n) {
|
|
574
|
+
const v = Number(n);
|
|
575
|
+
if (!Number.isFinite(v) || v <= 0) {
|
|
576
|
+
throw new Error("maxClaimAmount must be a positive number");
|
|
577
|
+
}
|
|
578
|
+
_maxClaimAmount = v;
|
|
579
|
+
return _maxClaimAmount;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export function getMaxPendingClaimsPerUser() {
|
|
583
|
+
return _maxPendingClaimsPerUser;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function getClaimExpiryMs() {
|
|
587
|
+
return _claimExpiryMs;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function getMaxClaimAmount() {
|
|
591
|
+
return _maxClaimAmount;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export function getPendingClaimCount(userId) {
|
|
595
|
+
let count = 0;
|
|
596
|
+
for (const entry of _claimStatesV2.values()) {
|
|
597
|
+
if (entry.status === CLAIM_STATUS_V2.PENDING) {
|
|
598
|
+
if (!userId || entry.userId === userId) count += 1;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return count;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/* ── Account V2 ─────────────────────────────────────────────── */
|
|
605
|
+
|
|
606
|
+
export function registerAccountV2(db, { accountId, metadata } = {}) {
|
|
607
|
+
if (!accountId) throw new Error("accountId is required");
|
|
608
|
+
if (_accountStatesV2.has(accountId)) {
|
|
609
|
+
throw new Error(`Account already registered: ${accountId}`);
|
|
610
|
+
}
|
|
611
|
+
const now = Date.now();
|
|
612
|
+
const entry = {
|
|
613
|
+
accountId,
|
|
614
|
+
status: ACCOUNT_STATUS_V2.ACTIVE,
|
|
615
|
+
reason: null,
|
|
616
|
+
metadata: metadata ? { ...metadata } : {},
|
|
617
|
+
createdAt: now,
|
|
618
|
+
updatedAt: now,
|
|
619
|
+
};
|
|
620
|
+
_accountStatesV2.set(accountId, entry);
|
|
621
|
+
return { ...entry };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export function getAccountStatusV2(accountId) {
|
|
625
|
+
const entry = _accountStatesV2.get(accountId);
|
|
626
|
+
return entry ? { ...entry } : null;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export function setAccountStatusV2(db, accountId, newStatus, patch = {}) {
|
|
630
|
+
const entry = _accountStatesV2.get(accountId);
|
|
631
|
+
if (!entry) throw new Error(`Account not found: ${accountId}`);
|
|
632
|
+
if (!Object.values(ACCOUNT_STATUS_V2).includes(newStatus)) {
|
|
633
|
+
throw new Error(`Invalid account status: ${newStatus}`);
|
|
634
|
+
}
|
|
635
|
+
if (ACCOUNT_TERMINALS_V2.has(entry.status)) {
|
|
636
|
+
throw new Error(`Account is terminal: ${entry.status}`);
|
|
637
|
+
}
|
|
638
|
+
const allowed = ACCOUNT_TRANSITIONS_V2.get(entry.status) || new Set();
|
|
639
|
+
if (!allowed.has(newStatus)) {
|
|
640
|
+
throw new Error(`Invalid transition: ${entry.status} → ${newStatus}`);
|
|
641
|
+
}
|
|
642
|
+
entry.status = newStatus;
|
|
643
|
+
entry.updatedAt = Date.now();
|
|
644
|
+
if (patch.reason !== undefined) entry.reason = patch.reason;
|
|
645
|
+
if (patch.metadata) entry.metadata = { ...entry.metadata, ...patch.metadata };
|
|
646
|
+
return { ...entry };
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export function freezeAccount(db, accountId, reason) {
|
|
650
|
+
return setAccountStatusV2(db, accountId, ACCOUNT_STATUS_V2.FROZEN, {
|
|
651
|
+
reason,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export function unfreezeAccount(db, accountId, reason) {
|
|
656
|
+
return setAccountStatusV2(db, accountId, ACCOUNT_STATUS_V2.ACTIVE, {
|
|
657
|
+
reason,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export function closeAccount(db, accountId, reason) {
|
|
662
|
+
return setAccountStatusV2(db, accountId, ACCOUNT_STATUS_V2.CLOSED, {
|
|
663
|
+
reason,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/* ── Claim V2 ───────────────────────────────────────────────── */
|
|
668
|
+
|
|
669
|
+
export function submitClaimV2(
|
|
670
|
+
db,
|
|
671
|
+
{ claimId, userId, amount, contributionId, metadata } = {},
|
|
672
|
+
) {
|
|
673
|
+
if (!claimId) throw new Error("claimId is required");
|
|
674
|
+
if (!userId) throw new Error("userId is required");
|
|
675
|
+
const v = Number(amount);
|
|
676
|
+
if (!Number.isFinite(v) || v <= 0) {
|
|
677
|
+
throw new Error(`Invalid amount: ${amount} (must be > 0)`);
|
|
678
|
+
}
|
|
679
|
+
if (v > _maxClaimAmount) {
|
|
680
|
+
throw new Error(`Amount exceeds maxClaimAmount: ${v} > ${_maxClaimAmount}`);
|
|
681
|
+
}
|
|
682
|
+
if (_claimStatesV2.has(claimId)) {
|
|
683
|
+
throw new Error(`Claim already registered: ${claimId}`);
|
|
684
|
+
}
|
|
685
|
+
// Reject if account exists in V2 state and is frozen/closed
|
|
686
|
+
const accountEntry = _accountStatesV2.get(userId);
|
|
687
|
+
if (accountEntry && accountEntry.status !== ACCOUNT_STATUS_V2.ACTIVE) {
|
|
688
|
+
throw new Error(`Account not active: ${accountEntry.status}`);
|
|
689
|
+
}
|
|
690
|
+
// Enforce per-user pending cap
|
|
691
|
+
const pendingCount = getPendingClaimCount(userId);
|
|
692
|
+
if (pendingCount >= _maxPendingClaimsPerUser) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Max pending claims reached (${pendingCount}/${_maxPendingClaimsPerUser}) for user ${userId}`,
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
const now = Date.now();
|
|
698
|
+
const entry = {
|
|
699
|
+
claimId,
|
|
700
|
+
userId,
|
|
701
|
+
amount: v,
|
|
702
|
+
contributionId: contributionId || null,
|
|
703
|
+
status: CLAIM_STATUS_V2.PENDING,
|
|
704
|
+
reason: null,
|
|
705
|
+
metadata: metadata ? { ...metadata } : {},
|
|
706
|
+
createdAt: now,
|
|
707
|
+
updatedAt: now,
|
|
708
|
+
paidAt: null,
|
|
709
|
+
};
|
|
710
|
+
_claimStatesV2.set(claimId, entry);
|
|
711
|
+
return { ...entry };
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export function getClaimStatusV2(claimId) {
|
|
715
|
+
const entry = _claimStatesV2.get(claimId);
|
|
716
|
+
return entry ? { ...entry } : null;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
export function setClaimStatusV2(db, claimId, newStatus, patch = {}) {
|
|
720
|
+
const entry = _claimStatesV2.get(claimId);
|
|
721
|
+
if (!entry) throw new Error(`Claim not found: ${claimId}`);
|
|
722
|
+
if (!Object.values(CLAIM_STATUS_V2).includes(newStatus)) {
|
|
723
|
+
throw new Error(`Invalid claim status: ${newStatus}`);
|
|
724
|
+
}
|
|
725
|
+
if (CLAIM_TERMINALS_V2.has(entry.status)) {
|
|
726
|
+
throw new Error(`Claim is terminal: ${entry.status}`);
|
|
727
|
+
}
|
|
728
|
+
const allowed = CLAIM_TRANSITIONS_V2.get(entry.status) || new Set();
|
|
729
|
+
if (!allowed.has(newStatus)) {
|
|
730
|
+
throw new Error(`Invalid transition: ${entry.status} → ${newStatus}`);
|
|
731
|
+
}
|
|
732
|
+
entry.status = newStatus;
|
|
733
|
+
entry.updatedAt = Date.now();
|
|
734
|
+
if (newStatus === CLAIM_STATUS_V2.PAID) entry.paidAt = entry.updatedAt;
|
|
735
|
+
if (patch.reason !== undefined) entry.reason = patch.reason;
|
|
736
|
+
if (patch.metadata) entry.metadata = { ...entry.metadata, ...patch.metadata };
|
|
737
|
+
return { ...entry };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export function approveClaim(db, claimId, reason) {
|
|
741
|
+
return setClaimStatusV2(db, claimId, CLAIM_STATUS_V2.APPROVED, { reason });
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export function rejectClaim(db, claimId, reason) {
|
|
745
|
+
return setClaimStatusV2(db, claimId, CLAIM_STATUS_V2.REJECTED, { reason });
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
export function payClaim(db, claimId, reason) {
|
|
749
|
+
return setClaimStatusV2(db, claimId, CLAIM_STATUS_V2.PAID, { reason });
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export function autoExpireUnclaimedClaims(db, nowMs = Date.now()) {
|
|
753
|
+
const expired = [];
|
|
754
|
+
for (const entry of _claimStatesV2.values()) {
|
|
755
|
+
if (entry.status !== CLAIM_STATUS_V2.PENDING) continue;
|
|
756
|
+
if (nowMs - entry.createdAt > _claimExpiryMs) {
|
|
757
|
+
entry.status = CLAIM_STATUS_V2.REJECTED;
|
|
758
|
+
entry.reason = "expired";
|
|
759
|
+
entry.updatedAt = nowMs;
|
|
760
|
+
expired.push({ ...entry });
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return expired;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/* ── Stats V2 ───────────────────────────────────────────────── */
|
|
767
|
+
|
|
768
|
+
export function getTokenStatsV2() {
|
|
769
|
+
const accountsByStatus = { active: 0, frozen: 0, closed: 0 };
|
|
770
|
+
const claimsByStatus = { pending: 0, approved: 0, paid: 0, rejected: 0 };
|
|
771
|
+
let totalClaimedAmount = 0;
|
|
772
|
+
let totalPaidAmount = 0;
|
|
773
|
+
|
|
774
|
+
for (const entry of _accountStatesV2.values()) {
|
|
775
|
+
if (accountsByStatus[entry.status] !== undefined)
|
|
776
|
+
accountsByStatus[entry.status] += 1;
|
|
777
|
+
}
|
|
778
|
+
for (const entry of _claimStatesV2.values()) {
|
|
779
|
+
if (claimsByStatus[entry.status] !== undefined)
|
|
780
|
+
claimsByStatus[entry.status] += 1;
|
|
781
|
+
totalClaimedAmount += entry.amount;
|
|
782
|
+
if (entry.status === CLAIM_STATUS_V2.PAID) totalPaidAmount += entry.amount;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return {
|
|
786
|
+
totalAccounts: _accountStatesV2.size,
|
|
787
|
+
totalClaims: _claimStatesV2.size,
|
|
788
|
+
totalClaimedAmount: Number(totalClaimedAmount.toFixed(4)),
|
|
789
|
+
totalPaidAmount: Number(totalPaidAmount.toFixed(4)),
|
|
790
|
+
maxPendingClaimsPerUser: _maxPendingClaimsPerUser,
|
|
791
|
+
claimExpiryMs: _claimExpiryMs,
|
|
792
|
+
maxClaimAmount: _maxClaimAmount,
|
|
793
|
+
accountsByStatus,
|
|
794
|
+
claimsByStatus,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/* ── Reset V2 (tests) ───────────────────────────────────────── */
|
|
799
|
+
|
|
800
|
+
export function _resetStateV2() {
|
|
801
|
+
_accountStatesV2.clear();
|
|
802
|
+
_claimStatesV2.clear();
|
|
803
|
+
_maxPendingClaimsPerUser = TOKEN_DEFAULT_MAX_PENDING_CLAIMS_PER_USER;
|
|
804
|
+
_claimExpiryMs = TOKEN_DEFAULT_CLAIM_EXPIRY_MS;
|
|
805
|
+
_maxClaimAmount = TOKEN_DEFAULT_MAX_CLAIM_AMOUNT;
|
|
806
|
+
}
|
package/src/lib/token-tracker.js
CHANGED
|
@@ -198,3 +198,332 @@ export function getTodayStats(db) {
|
|
|
198
198
|
startDate: new Date().toISOString().slice(0, 10),
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
|
+
|
|
202
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
203
|
+
* V2 Surface — Token tracker governance layer.
|
|
204
|
+
* Tracks per-owner budget maturity + per-budget usage-record lifecycle
|
|
205
|
+
* independent of legacy SQLite token_usage table.
|
|
206
|
+
* ═══════════════════════════════════════════════════════════════ */
|
|
207
|
+
|
|
208
|
+
export const BUDGET_MATURITY_V2 = Object.freeze({
|
|
209
|
+
PLANNING: "planning",
|
|
210
|
+
ACTIVE: "active",
|
|
211
|
+
SUSPENDED: "suspended",
|
|
212
|
+
ARCHIVED: "archived",
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
export const USAGE_RECORD_LIFECYCLE_V2 = Object.freeze({
|
|
216
|
+
PENDING: "pending",
|
|
217
|
+
RECORDED: "recorded",
|
|
218
|
+
BILLED: "billed",
|
|
219
|
+
REJECTED: "rejected",
|
|
220
|
+
REFUNDED: "refunded",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const BUDGET_TRANSITIONS_V2 = new Map([
|
|
224
|
+
["planning", new Set(["active", "archived"])],
|
|
225
|
+
["active", new Set(["suspended", "archived"])],
|
|
226
|
+
["suspended", new Set(["active", "archived"])],
|
|
227
|
+
["archived", new Set()],
|
|
228
|
+
]);
|
|
229
|
+
const BUDGET_TERMINALS_V2 = new Set(["archived"]);
|
|
230
|
+
|
|
231
|
+
const RECORD_TRANSITIONS_V2 = new Map([
|
|
232
|
+
["pending", new Set(["recorded", "rejected"])],
|
|
233
|
+
["recorded", new Set(["billed", "rejected", "refunded"])],
|
|
234
|
+
["billed", new Set()],
|
|
235
|
+
["rejected", new Set()],
|
|
236
|
+
["refunded", new Set()],
|
|
237
|
+
]);
|
|
238
|
+
const RECORD_TERMINALS_V2 = new Set(["billed", "rejected", "refunded"]);
|
|
239
|
+
|
|
240
|
+
export const TOKEN_DEFAULT_MAX_ACTIVE_BUDGETS_PER_OWNER = 10;
|
|
241
|
+
export const TOKEN_DEFAULT_MAX_PENDING_RECORDS_PER_BUDGET = 500;
|
|
242
|
+
export const TOKEN_DEFAULT_BUDGET_IDLE_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
|
|
243
|
+
export const TOKEN_DEFAULT_RECORD_STUCK_MS = 1000 * 60 * 60; // 1 hour
|
|
244
|
+
|
|
245
|
+
const _budgetsV2 = new Map();
|
|
246
|
+
const _recordsV2 = new Map();
|
|
247
|
+
let _maxActiveBudgetsPerOwnerV2 = TOKEN_DEFAULT_MAX_ACTIVE_BUDGETS_PER_OWNER;
|
|
248
|
+
let _maxPendingRecordsPerBudgetV2 =
|
|
249
|
+
TOKEN_DEFAULT_MAX_PENDING_RECORDS_PER_BUDGET;
|
|
250
|
+
let _budgetIdleMsV2 = TOKEN_DEFAULT_BUDGET_IDLE_MS;
|
|
251
|
+
let _recordStuckMsV2 = TOKEN_DEFAULT_RECORD_STUCK_MS;
|
|
252
|
+
|
|
253
|
+
function _posIntTokenV2(n, label) {
|
|
254
|
+
const v = Math.floor(Number(n));
|
|
255
|
+
if (!Number.isFinite(v) || v <= 0)
|
|
256
|
+
throw new Error(`${label} must be a positive integer`);
|
|
257
|
+
return v;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function getMaxActiveBudgetsPerOwnerV2() {
|
|
261
|
+
return _maxActiveBudgetsPerOwnerV2;
|
|
262
|
+
}
|
|
263
|
+
export function setMaxActiveBudgetsPerOwnerV2(n) {
|
|
264
|
+
_maxActiveBudgetsPerOwnerV2 = _posIntTokenV2(n, "maxActiveBudgetsPerOwner");
|
|
265
|
+
}
|
|
266
|
+
export function getMaxPendingRecordsPerBudgetV2() {
|
|
267
|
+
return _maxPendingRecordsPerBudgetV2;
|
|
268
|
+
}
|
|
269
|
+
export function setMaxPendingRecordsPerBudgetV2(n) {
|
|
270
|
+
_maxPendingRecordsPerBudgetV2 = _posIntTokenV2(
|
|
271
|
+
n,
|
|
272
|
+
"maxPendingRecordsPerBudget",
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
export function getBudgetIdleMsV2() {
|
|
276
|
+
return _budgetIdleMsV2;
|
|
277
|
+
}
|
|
278
|
+
export function setBudgetIdleMsV2(n) {
|
|
279
|
+
_budgetIdleMsV2 = _posIntTokenV2(n, "budgetIdleMs");
|
|
280
|
+
}
|
|
281
|
+
export function getRecordStuckMsV2() {
|
|
282
|
+
return _recordStuckMsV2;
|
|
283
|
+
}
|
|
284
|
+
export function setRecordStuckMsV2(n) {
|
|
285
|
+
_recordStuckMsV2 = _posIntTokenV2(n, "recordStuckMs");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function getActiveBudgetCountV2(ownerId) {
|
|
289
|
+
let n = 0;
|
|
290
|
+
for (const b of _budgetsV2.values()) {
|
|
291
|
+
if (b.ownerId === ownerId && b.status === "active") n += 1;
|
|
292
|
+
}
|
|
293
|
+
return n;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function getPendingRecordCountV2(budgetId) {
|
|
297
|
+
let n = 0;
|
|
298
|
+
for (const r of _recordsV2.values()) {
|
|
299
|
+
if (
|
|
300
|
+
r.budgetId === budgetId &&
|
|
301
|
+
(r.status === "pending" || r.status === "recorded")
|
|
302
|
+
)
|
|
303
|
+
n += 1;
|
|
304
|
+
}
|
|
305
|
+
return n;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function _copyBudgetV2(b) {
|
|
309
|
+
return { ...b, metadata: { ...b.metadata } };
|
|
310
|
+
}
|
|
311
|
+
function _copyRecordV2(r) {
|
|
312
|
+
return { ...r, metadata: { ...r.metadata } };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function registerBudgetV2(
|
|
316
|
+
id,
|
|
317
|
+
{ ownerId, label, metadata = {}, now = Date.now() } = {},
|
|
318
|
+
) {
|
|
319
|
+
if (!id || typeof id !== "string") throw new Error("id must be a string");
|
|
320
|
+
if (!ownerId || typeof ownerId !== "string")
|
|
321
|
+
throw new Error("ownerId must be a string");
|
|
322
|
+
if (!label || typeof label !== "string")
|
|
323
|
+
throw new Error("label must be a string");
|
|
324
|
+
if (_budgetsV2.has(id)) throw new Error(`budget ${id} already exists`);
|
|
325
|
+
const b = {
|
|
326
|
+
id,
|
|
327
|
+
ownerId,
|
|
328
|
+
label,
|
|
329
|
+
status: "planning",
|
|
330
|
+
createdAt: now,
|
|
331
|
+
lastSeenAt: now,
|
|
332
|
+
activatedAt: null,
|
|
333
|
+
archivedAt: null,
|
|
334
|
+
metadata: { ...metadata },
|
|
335
|
+
};
|
|
336
|
+
_budgetsV2.set(id, b);
|
|
337
|
+
return _copyBudgetV2(b);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function getBudgetV2(id) {
|
|
341
|
+
const b = _budgetsV2.get(id);
|
|
342
|
+
return b ? _copyBudgetV2(b) : null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function listBudgetsV2({ ownerId, status } = {}) {
|
|
346
|
+
const out = [];
|
|
347
|
+
for (const b of _budgetsV2.values()) {
|
|
348
|
+
if (ownerId && b.ownerId !== ownerId) continue;
|
|
349
|
+
if (status && b.status !== status) continue;
|
|
350
|
+
out.push(_copyBudgetV2(b));
|
|
351
|
+
}
|
|
352
|
+
return out;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function setBudgetStatusV2(id, next, { now = Date.now() } = {}) {
|
|
356
|
+
const b = _budgetsV2.get(id);
|
|
357
|
+
if (!b) throw new Error(`budget ${id} not found`);
|
|
358
|
+
if (!BUDGET_TRANSITIONS_V2.has(next))
|
|
359
|
+
throw new Error(`unknown budget status: ${next}`);
|
|
360
|
+
if (BUDGET_TERMINALS_V2.has(b.status))
|
|
361
|
+
throw new Error(`budget ${id} is in terminal state ${b.status}`);
|
|
362
|
+
const allowed = BUDGET_TRANSITIONS_V2.get(b.status);
|
|
363
|
+
if (!allowed.has(next))
|
|
364
|
+
throw new Error(`cannot transition budget from ${b.status} to ${next}`);
|
|
365
|
+
if (next === "active") {
|
|
366
|
+
if (b.status === "planning") {
|
|
367
|
+
const count = getActiveBudgetCountV2(b.ownerId);
|
|
368
|
+
if (count >= _maxActiveBudgetsPerOwnerV2)
|
|
369
|
+
throw new Error(
|
|
370
|
+
`owner ${b.ownerId} already at active-budget cap (${_maxActiveBudgetsPerOwnerV2})`,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
if (!b.activatedAt) b.activatedAt = now;
|
|
374
|
+
}
|
|
375
|
+
if (next === "archived" && !b.archivedAt) b.archivedAt = now;
|
|
376
|
+
b.status = next;
|
|
377
|
+
b.lastSeenAt = now;
|
|
378
|
+
return _copyBudgetV2(b);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function activateBudgetV2(id, opts) {
|
|
382
|
+
return setBudgetStatusV2(id, "active", opts);
|
|
383
|
+
}
|
|
384
|
+
export function suspendBudgetV2(id, opts) {
|
|
385
|
+
return setBudgetStatusV2(id, "suspended", opts);
|
|
386
|
+
}
|
|
387
|
+
export function archiveBudgetV2(id, opts) {
|
|
388
|
+
return setBudgetStatusV2(id, "archived", opts);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function touchBudgetV2(id, { now = Date.now() } = {}) {
|
|
392
|
+
const b = _budgetsV2.get(id);
|
|
393
|
+
if (!b) throw new Error(`budget ${id} not found`);
|
|
394
|
+
b.lastSeenAt = now;
|
|
395
|
+
return _copyBudgetV2(b);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function createUsageRecordV2(
|
|
399
|
+
id,
|
|
400
|
+
{ budgetId, units, metadata = {}, now = Date.now() } = {},
|
|
401
|
+
) {
|
|
402
|
+
if (!id || typeof id !== "string") throw new Error("id must be a string");
|
|
403
|
+
if (!budgetId || typeof budgetId !== "string")
|
|
404
|
+
throw new Error("budgetId must be a string");
|
|
405
|
+
if (!Number.isFinite(units) || units < 0)
|
|
406
|
+
throw new Error("units must be a non-negative finite number");
|
|
407
|
+
if (_recordsV2.has(id)) throw new Error(`record ${id} already exists`);
|
|
408
|
+
const count = getPendingRecordCountV2(budgetId);
|
|
409
|
+
if (count >= _maxPendingRecordsPerBudgetV2)
|
|
410
|
+
throw new Error(
|
|
411
|
+
`budget ${budgetId} already at pending-record cap (${_maxPendingRecordsPerBudgetV2})`,
|
|
412
|
+
);
|
|
413
|
+
const r = {
|
|
414
|
+
id,
|
|
415
|
+
budgetId,
|
|
416
|
+
units,
|
|
417
|
+
status: "pending",
|
|
418
|
+
createdAt: now,
|
|
419
|
+
lastSeenAt: now,
|
|
420
|
+
recordedAt: null,
|
|
421
|
+
settledAt: null,
|
|
422
|
+
metadata: { ...metadata },
|
|
423
|
+
};
|
|
424
|
+
_recordsV2.set(id, r);
|
|
425
|
+
return _copyRecordV2(r);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function getUsageRecordV2(id) {
|
|
429
|
+
const r = _recordsV2.get(id);
|
|
430
|
+
return r ? _copyRecordV2(r) : null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function listUsageRecordsV2({ budgetId, status } = {}) {
|
|
434
|
+
const out = [];
|
|
435
|
+
for (const r of _recordsV2.values()) {
|
|
436
|
+
if (budgetId && r.budgetId !== budgetId) continue;
|
|
437
|
+
if (status && r.status !== status) continue;
|
|
438
|
+
out.push(_copyRecordV2(r));
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function setUsageRecordStatusV2(id, next, { now = Date.now() } = {}) {
|
|
444
|
+
const r = _recordsV2.get(id);
|
|
445
|
+
if (!r) throw new Error(`record ${id} not found`);
|
|
446
|
+
if (!RECORD_TRANSITIONS_V2.has(next))
|
|
447
|
+
throw new Error(`unknown record status: ${next}`);
|
|
448
|
+
if (RECORD_TERMINALS_V2.has(r.status))
|
|
449
|
+
throw new Error(`record ${id} is in terminal state ${r.status}`);
|
|
450
|
+
const allowed = RECORD_TRANSITIONS_V2.get(r.status);
|
|
451
|
+
if (!allowed.has(next))
|
|
452
|
+
throw new Error(`cannot transition record from ${r.status} to ${next}`);
|
|
453
|
+
if (next === "recorded" && !r.recordedAt) r.recordedAt = now;
|
|
454
|
+
if (RECORD_TERMINALS_V2.has(next) && !r.settledAt) r.settledAt = now;
|
|
455
|
+
r.status = next;
|
|
456
|
+
r.lastSeenAt = now;
|
|
457
|
+
return _copyRecordV2(r);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function recordUsageV2(id, opts) {
|
|
461
|
+
return setUsageRecordStatusV2(id, "recorded", opts);
|
|
462
|
+
}
|
|
463
|
+
export function billUsageV2(id, opts) {
|
|
464
|
+
return setUsageRecordStatusV2(id, "billed", opts);
|
|
465
|
+
}
|
|
466
|
+
export function rejectUsageV2(id, opts) {
|
|
467
|
+
return setUsageRecordStatusV2(id, "rejected", opts);
|
|
468
|
+
}
|
|
469
|
+
export function refundUsageV2(id, opts) {
|
|
470
|
+
return setUsageRecordStatusV2(id, "refunded", opts);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function autoSuspendIdleBudgetsV2({ now = Date.now() } = {}) {
|
|
474
|
+
const flipped = [];
|
|
475
|
+
for (const b of _budgetsV2.values()) {
|
|
476
|
+
if (b.status !== "active") continue;
|
|
477
|
+
if (now - b.lastSeenAt > _budgetIdleMsV2) {
|
|
478
|
+
b.status = "suspended";
|
|
479
|
+
b.lastSeenAt = now;
|
|
480
|
+
flipped.push(_copyBudgetV2(b));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return flipped;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function autoRejectStaleRecordsV2({ now = Date.now() } = {}) {
|
|
487
|
+
const flipped = [];
|
|
488
|
+
for (const r of _recordsV2.values()) {
|
|
489
|
+
if (r.status !== "pending" && r.status !== "recorded") continue;
|
|
490
|
+
if (now - r.lastSeenAt > _recordStuckMsV2) {
|
|
491
|
+
r.status = "rejected";
|
|
492
|
+
r.lastSeenAt = now;
|
|
493
|
+
if (!r.settledAt) r.settledAt = now;
|
|
494
|
+
flipped.push(_copyRecordV2(r));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return flipped;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function getTokenTrackerStatsV2() {
|
|
501
|
+
const budgetsByStatus = {};
|
|
502
|
+
for (const v of Object.values(BUDGET_MATURITY_V2)) budgetsByStatus[v] = 0;
|
|
503
|
+
for (const b of _budgetsV2.values()) budgetsByStatus[b.status] += 1;
|
|
504
|
+
|
|
505
|
+
const recordsByStatus = {};
|
|
506
|
+
for (const v of Object.values(USAGE_RECORD_LIFECYCLE_V2))
|
|
507
|
+
recordsByStatus[v] = 0;
|
|
508
|
+
for (const r of _recordsV2.values()) recordsByStatus[r.status] += 1;
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
totalBudgetsV2: _budgetsV2.size,
|
|
512
|
+
totalRecordsV2: _recordsV2.size,
|
|
513
|
+
maxActiveBudgetsPerOwner: _maxActiveBudgetsPerOwnerV2,
|
|
514
|
+
maxPendingRecordsPerBudget: _maxPendingRecordsPerBudgetV2,
|
|
515
|
+
budgetIdleMs: _budgetIdleMsV2,
|
|
516
|
+
recordStuckMs: _recordStuckMsV2,
|
|
517
|
+
budgetsByStatus,
|
|
518
|
+
recordsByStatus,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export function _resetStateTokenTrackerV2() {
|
|
523
|
+
_budgetsV2.clear();
|
|
524
|
+
_recordsV2.clear();
|
|
525
|
+
_maxActiveBudgetsPerOwnerV2 = TOKEN_DEFAULT_MAX_ACTIVE_BUDGETS_PER_OWNER;
|
|
526
|
+
_maxPendingRecordsPerBudgetV2 = TOKEN_DEFAULT_MAX_PENDING_RECORDS_PER_BUDGET;
|
|
527
|
+
_budgetIdleMsV2 = TOKEN_DEFAULT_BUDGET_IDLE_MS;
|
|
528
|
+
_recordStuckMsV2 = TOKEN_DEFAULT_RECORD_STUCK_MS;
|
|
529
|
+
}
|