@evergreen-stellar/cli 0.1.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/bin.ts", "../../core/src/format.ts", "../../core/src/ttl.ts", "../../core/src/rpc.ts", "../../core/src/temporary-policy.ts", "../../core/src/health.ts", "../../core/src/ed25519-signer.ts", "../../core/src/extend.ts", "../../core/src/network-config.ts", "../../core/src/write-guard.ts", "../../core/src/config.ts", "../../core/src/rent.ts", "../../core/src/rent-quoter.ts", "../../core/src/scan-contract.ts", "../../core/src/extend-rpc.ts", "../../core/src/optimizer-evidence.ts", "../../core/src/optimizer.ts", "../../core/src/engine-execution-plan.ts", "../src/extend.ts", "../src/optimizer.ts", "../src/cost.ts", "../src/scan.ts", "../src/command.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\nimport process from 'node:process';\nimport console from 'node:console';\nimport { readFile } from 'node:fs/promises';\nimport { rpc, Networks } from '@stellar/stellar-sdk';\nimport {\n connectTestnet,\n createSimulatingQuoter,\n estimateRent,\n readStateArchivalSettings,\n resolveExtendTarget,\n planExtension,\n executeExtensions,\n prepareExtension,\n submitExtension,\n confirmExtension,\n createEd25519Signer,\n scanContract,\n createRpcReader,\n} from '@evergreen-stellar/core';\nimport type { PreparedExtension } from '@evergreen-stellar/core';\nimport { extensionPreview } from './extend.js';\nimport type { ExtendReport, ExtendRequest } from './extend.js';\nimport type { ScanResult, Stroops } from '@evergreen-stellar/shared-types';\nimport type { CostLine } from './cost.js';\nimport { runCli } from './command.js';\nimport { EXIT_ERROR } from './scan.js';\n\nconst DEFAULT_RPC = 'https://soroban-testnet.stellar.org';\n/** Base inclusion fee per operation, added so the total is what actually leaves the account. */\nconst BASE_FEE_STROOPS = 100n;\n\n/**\n * Price an extend by simulating it. Lives here rather than in `command.ts`\n * because it is the one part that touches the network \u2014 the command stays a\n * pure function of its dependencies, which is what makes it testable offline.\n *\n * The user asks for \"N more ledgers\". `extendTo` is an absolute target, so the\n * conversion happens HERE and never reaches the user's vocabulary. The ceiling\n * comes from the network, because the primer is explicit that state-archival\n * settings are configuration rather than constants to hardcode.\n */\nasync function priceExtend(\n rpcUrl: string,\n sourceAccountId: string | undefined,\n args: { scan: ScanResult; additionalLedgers: number },\n): Promise<CostLine> {\n const server = new rpc.Server(rpcUrl);\n const settings = await readStateArchivalSettings(server);\n const quoter = createSimulatingQuoter(server, {\n ...(sourceAccountId === undefined ? {} : { sourceAccountId }),\n networkPassphrase: Networks.TESTNET,\n });\n\n // EACH entry gets its own target. `extendTo` is absolute, so entries with\n // different remaining TTL need different targets to receive the same\n // increment \u2014 a single shared target silently over-extends the entry with\n // least headroom, which is exactly what it must not do.\n let cappedEntryCount = 0;\n const targets: Record<string, number> = {};\n for (const [entryKey, entry] of Object.entries(args.scan.entries)) {\n if (entry.ttl.status !== 'known') continue;\n const resolved = resolveExtendTarget({\n currentRemainingLedgers: entry.ttl.remainingLedgers,\n additionalLedgers: args.additionalLedgers,\n maxEntryTtl: settings.maxEntryTtl,\n });\n if (resolved.wasCapped) cappedEntryCount += 1;\n targets[entryKey] = resolved.extendToLedgers;\n }\n\n const { estimate } = await estimateRent(args.scan, { extendToLedgers: targets }, quoter);\n const priced = Object.keys(estimate.estimatedRentStroopsByEntry);\n let resourceTotal = 0n;\n for (const entryKey of priced) {\n const [q] = await quoter.quoteDetailed({\n entryKeys: [entryKey],\n extendToLedgers: targets[entryKey]!,\n });\n resourceTotal += BigInt(q!.minResourceFeeStroops);\n }\n const rent = BigInt(estimate.totalEstimatedRentStroops);\n const total = resourceTotal + BASE_FEE_STROOPS * BigInt(priced.length);\n\n return {\n totalStroops: total.toString() as Stroops,\n rentStroops: rent.toString() as Stroops,\n otherStroops: (total - rent).toString() as Stroops,\n entryCount: priced.length,\n additionalLedgers: args.additionalLedgers,\n cappedEntryCount,\n maxEntryTtl: settings.maxEntryTtl,\n pricedAtLedger: settings.observedAtLedger,\n rentByEntry: estimate.estimatedRentStroopsByEntry,\n };\n}\n\nasync function main(): Promise<number> {\n const rpcUrl = process.env.SOROBAN_RPC_URL ?? DEFAULT_RPC;\n // Colour only for a human at a terminal. Piped output, CI logs and captured\n // evidence stay clean, and NO_COLOR is honoured (no-color.org). The health\n // WORD prints either way \u2014 colour is never the only carrier of the state.\n const color = process.stdout.isTTY === true && process.env.NO_COLOR === undefined;\n const output = await runCli(process.argv.slice(2), {\n extend: {\n ...(process.env.EVERGREEN_SOURCE_ACCOUNT\n ? { sourceAccount: process.env.EVERGREEN_SOURCE_ACCOUNT }\n : {}),\n readKeysFile: (path) => readFile(path, 'utf8'),\n preview: (text) => console.error(text),\n run: (request, preview) => runExtension(rpcUrl, request, preview),\n },\n connect: () => connectTestnet(rpcUrl),\n readStorageSettings: () =>\n readStateArchivalSettings(new rpc.Server(rpcUrl, { timeout: 10_000 })),\n readKeysFile: (path) => readFile(path, 'utf8'),\n now: () => new Date(),\n color,\n // Public key only; simulation never signs. Falls back to the well-known\n // testnet identity so `--cost` works without configuration.\n // No account is passed: simulation neither signs nor needs one to exist.\n // EVERGREEN_SOURCE_ACCOUNT stays available for anyone who wants a specific\n // identity in their own RPC logs.\n priceExtend: (args) => priceExtend(rpcUrl, process.env.EVERGREEN_SOURCE_ACCOUNT, args),\n });\n if (output.stdout) console.log(output.stdout);\n if (output.stderr) console.error(output.stderr);\n return output.exitCode;\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch(() => {\n // Never let a raw stack trace reach a user (docs/CONVENTIONS.md).\n console.error('\\n\u2716 Unexpected command failure.');\n process.exit(EXIT_ERROR);\n });\n\n/** Network/secret wiring only. The command and execution state machine test offline. */\nasync function runExtension(\n rpcUrl: string,\n request: ExtendRequest,\n preview: (text: string) => void,\n): Promise<ExtendReport> {\n const server = new rpc.Server(rpcUrl, { timeout: 10_000 });\n if ((await server.getNetwork()).passphrase !== Networks.TESTNET)\n throw new Error('RPC is not Testnet');\n const reader = createRpcReader(server);\n const scan = await scanContract(reader, { id: request.contractId }, request.dataKeys);\n const settings = await readStateArchivalSettings(server);\n const plan = planExtension(scan, {\n contractId: request.contractId,\n additionalLedgers: request.additionalLedgers,\n maxEntryTtl: settings.maxEntryTtl,\n dataKeys: request.dataKeys,\n includeCode: request.includeCode,\n });\n const previews: PreparedExtension[] = [];\n const result = await executeExtensions(\n plan,\n {\n payer: request.sourceAccount,\n submit: request.submit,\n ...(request.maxFeeStroops === undefined ? {} : { maxFeeStroops: request.maxFeeStroops }),\n },\n {\n prepare: (entry) => prepareExtension(server, entry, request.sourceAccount),\n signer: (prepared, remainingFeeStroops) =>\n createEd25519Signer({\n payer: request.sourceAccount,\n sourceAccount: request.sourceAccount,\n entryKey: prepared.entry.entryKey,\n extendToLedgers: prepared.entry.extendToLedgers,\n expectedHash: prepared.transactionHash,\n maxFeeStroops: remainingFeeStroops,\n readSecret: () => {\n // This callback is unreachable in simulation. Environment NAME is public; VALUE is never reported.\n const secret = request.secretEnv ? process.env[request.secretEnv] : undefined;\n if (!secret) throw new Error('Signing key is unavailable');\n return secret;\n },\n }),\n submit: (prepared, signed) => submitExtension(server, prepared, signed),\n confirm: (hash) => confirmExtension(server, hash),\n readAfter: async (entryKey) => {\n const after = await scanContract(reader, { id: request.contractId }, request.dataKeys);\n const entry = after.entries[entryKey];\n if (\n !entry ||\n entry.ttl.status !== 'known' ||\n after.issues.some((i) => i.entryKey === entryKey)\n )\n throw new Error('Post-read incomplete');\n return { observedAtLedger: entry.observedAtLedger, endsAtLedger: entry.ttl.endsAtLedger };\n },\n preview: async (prepared) => {\n previews.push(prepared);\n preview(\n `Mode: ${request.submit ? 'live (explicit submit)' : 'dry-run'}; requested increment: ${request.additionalLedgers}; aggregate budget: ${request.maxFeeStroops ?? 'not supplied (simulation only)'}\\n${extensionPreview(prepared)}`,\n );\n },\n now: () => new Date(),\n },\n );\n return { plan, result, previews };\n}\n", "/**\n * Evidence must render identically on every machine that reads it.\n *\n * `Number.prototype.toLocaleString()` with no argument uses the *runtime's*\n * locale, which comes from the reader's environment. The same scan produces:\n *\n * ```\n * en-US remaining: 1,682,587 ledgers\n * de-DE remaining: 1.682.587 ledgers\n * fr-FR remaining: 1 682 586 ledgers\n * ```\n *\n * The French form is the dangerous one: that separator is U+202F, a narrow\n * no-break space, so it *looks* like a space and fails any byte comparison\n * invisibly.\n *\n * This matters most on the artefact we least want questioned. Guinea-pig B's\n * crossing is the sprint's strongest claim and the thing a sceptical reviewer is\n * most likely to re-run themselves. If our committed numbers do not reproduce on\n * their machine, the finding they report is \"your evidence does not reproduce\" \u2014\n * and they will be right, for a reason that has nothing to do with the chain.\n *\n * `scripts/check-locale-pinning.mjs` fails the build on an unpinned call, so\n * this cannot quietly come back.\n */\nexport const EVIDENCE_LOCALE = 'en-US';\n\n/** Group a ledger count or stroop amount for display, identically everywhere. */\nexport function formatCount(value: number | bigint): string {\n return value.toLocaleString(EVIDENCE_LOCALE);\n}\n", "import { formatCount } from './format.js';\nimport type { LedgerEntryTTL, TTLObservation } from '@evergreen-stellar/shared-types';\n\n/**\n * TTL math. Pure \u2014 no SDK, no I/O, no clock.\n *\n * The boundary is INCLUSIVE and this is the single easiest thing to get wrong\n * here. `endsAtLedger` is the entry's final *live* ledger, so:\n *\n * remainingLedgers === 0 -> still live, on its last ledger\n * remainingLedgers < 0 -> expired\n *\n * Observed on testnet 2026-09-06 (`W1-D4-13`): an entry was present at ledger\n * 4,529,810 with remaining 0 and absent at 4,529,811. See\n * `docs/SOROBAN-PRIMER.md`. A `<= 0` guard reports a live entry as dead \u2014 wrong\n * by one ledger, in the dangerous direction, and silently.\n */\n\n/**\n * Ledgers close at ~5s on testnet (measured over 100,000 ledgers, 2026-09-05).\n *\n * A bare number cannot say how well it is known. Prefer `MEASURED_TESTNET_CADENCE`\n * and `projectEnd` below, which carry provenance and a band; this stays for the\n * simple display path and is the single place the figure is written down.\n */\nexport const SECONDS_PER_LEDGER = 5;\n\nexport function observeTTL(args: {\n liveUntilLedgerSeq: number | undefined;\n observedAtLedger: number;\n}): TTLObservation {\n // Absent for entry types that carry no TTL. Never fabricate a zero: \"unknown\"\n // and \"expiring now\" must not collapse into the same value.\n if (args.liveUntilLedgerSeq === undefined) return { status: 'unavailable' };\n return {\n status: 'known',\n endsAtLedger: args.liveUntilLedgerSeq,\n remainingLedgers: args.liveUntilLedgerSeq - args.observedAtLedger,\n };\n}\n\n/**\n * \u2500\u2500 Two comparisons, deliberately different. Do not \"fix\" one to match. \u2500\u2500\u2500\u2500\u2500\u2500\n *\n * These sit next to each other on purpose. `isLive` is inclusive at zero and\n * `needsAction` is inclusive at the threshold, so the same `==` case falls on\n * opposite sides. That looks like an off-by-one and is not:\n *\n * isLive(remaining >= 0) PROTOCOL FACT. Stellar defines the TTL\n * boundary as inclusive \u2014 an entry is\n * live AT liveUntilLedgerSeq. The chain\n * decides this; we only report it.\n *\n * needsAction(remaining <= threshold) POLICY CHOICE. How much margin we\n * insist on. Decided 2026-09-10: the\n * threshold is a safety margin, and\n * TOUCHING the margin is already the\n * failure we exist to prevent. One step\n * from danger is not margin.\n *\n * They answer different questions \u2014 *\"is this entry alive?\"* versus *\"should\n * we act?\"* \u2014 so agreement between them was never the property to preserve.\n * Changing `needsAction` is a product decision. Changing `isLive` is claiming\n * the chain works differently than it does.\n */\n\n/**\n * Has the entry passed its final live ledger? The primitive both predicates\n * below are built from, so the boundary is written down in exactly one place.\n * Everything else CALLS this \u2014 nothing restates `remaining < 0`.\n */\nexport function hasExpired(remainingLedgers: number): boolean {\n return remainingLedgers < 0;\n}\n\n/** Is the entry still live? Zero remaining is its final live ledger, not death. */\nexport function isLive(ttl: TTLObservation): boolean | undefined {\n if (ttl.status === 'unavailable') return undefined;\n return !hasExpired(ttl.remainingLedgers);\n}\n\n/**\n * Is this a usable threshold at all? Validation, not policy \u2014 it asks whether\n * the number is well-formed, never whether an entry is in trouble. Named so\n * callers do not hand-write a bounds check and trip the one-home lint rule\n * with a comparison that was never a copy of anything.\n */\nexport function isValidThreshold(thresholdLedgers: number): boolean {\n return Number.isInteger(thresholdLedgers) && thresholdLedgers >= 0;\n}\n\n/**\n * Should we act now? True once remaining *reaches* the threshold, not after it\n * drops past. Read the threshold as **\"act once remaining reaches this\n * number\"** \u2014 it is the floor we refuse to touch, not a line we tolerate\n * sitting on. Costs at most one cron interval of earliness; buys a margin that\n * is never touched rather than merely rarely crossed.\n *\n * IF YOU ARE HERE TO CHANGE `<=` BACK TO `<`, READ THIS FIRST.\n *\n * The strongest argument for `<=` was not reasoned out, it fell out of an\n * existing test. Under `<`, a threshold of **0** fires only at `remaining < 0`\n * \u2014 that is, once the entry is already gone. A zero threshold meant \"act after\n * death\": a setting that fires exclusively when it is too late to do anything.\n * Nobody would have found that by reading the code; it surfaced because a test\n * had pinned the old behaviour and had to be re-examined. Under `<=` the same\n * setting fires on the final live ledger, which is the last moment an\n * `extendTTL` can still land. See `packages/cli/test/scan.test.ts`.\n *\n * This comparison also has EXACTLY ONE HOME, enforced by lint: `eslint.config.js`\n * exempts this file and forbids hand-written TTL threshold comparisons\n * everywhere else. That is not stylistic. A longhand copy in `exitCodeFor`\n * survived this policy change and made `evergreen-check` pass CI at the exact\n * ledger the engine alarmed.\n */\nexport function needsAction(remainingLedgers: number, thresholdLedgers: number): boolean {\n return remainingLedgers <= thresholdLedgers;\n}\n\n/**\n * Wall-clock estimate for display only. Never store this \u2014 the truth is a\n * ledger number, and cadence is measured rather than guaranteed.\n */\nexport function estimateEndsAt(ttl: TTLObservation, now: Date): Date | undefined {\n if (ttl.status === 'unavailable') return undefined;\n return new Date(now.getTime() + ttl.remainingLedgers * SECONDS_PER_LEDGER * 1000);\n}\n\n/**\n * Sum rent-bearing work per UNIQUE entry. Contracts built from identical Wasm\n * share one `ContractCode` entry, so counting per contract overcharges a\n * factory deployment by N. See `docs/SOROBAN-PRIMER.md`.\n */\nexport function uniqueEntryCount(entries: Readonly<Record<string, LedgerEntryTTL>>): number {\n return Object.keys(entries).length;\n}\n\n/**\n * \u2500\u2500 Ledger cadence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * A cadence is MEASURED, never guaranteed. Ledgers close at roughly five\n * seconds, but \"roughly\" is doing real work: at the protocol maximum of\n * 3,110,400 ledgers, a 0.02% cadence error moves a projected end date by more\n * than ten hours. A projection that hides that is precise-looking and wrong.\n *\n * So cadence travels with its provenance and its uncertainty, and every\n * projection derived from it carries a band rather than a bare instant.\n */\nexport interface LedgerCadence {\n readonly secondsPerLedger: number;\n /** How this number was obtained. Measurements age; assumptions never were true. */\n readonly source: 'measured' | 'assumed';\n /**\n * Half-width of the plausible band, in seconds per ledger. Never zero \u2014 a\n * cadence nobody has bounded is not a cadence anybody knows exactly.\n */\n readonly uncertaintySecondsPerLedger: number;\n /** Provenance, carried so a projection can explain where its number came from. */\n readonly basis: string;\n}\n\n/**\n * Testnet cadence as actually observed, with the honest width of that claim.\n *\n * Two independent measurements exist: 5.000 s/ledger over a 100,000-ledger\n * Horizon sample (2026-09-05) and 5.0008 s/ledger over 16.3 hours. The spread\n * between them \u2014 0.0008 s/ledger \u2014 is the uncertainty we can defend. It is a\n * SPREAD BETWEEN TWO MEASUREMENTS, not a confidence interval: two points bound\n * a range, they do not give a variance, exactly as three points did not give a\n * rent coefficient (`docs/SOROBAN-PRIMER.md`).\n */\nexport const MEASURED_TESTNET_CADENCE: LedgerCadence = {\n secondsPerLedger: SECONDS_PER_LEDGER,\n source: 'measured',\n uncertaintySecondsPerLedger: 0.0008,\n basis:\n '100,000-ledger Horizon sample 2026-09-05 (5.000 s) and an independent 16.3 h ' +\n 're-measurement (5.0008 s); the band is the spread between two measurements, not a variance',\n};\n\n/** One observed ledger close, as Horizon and RPC report it. */\nexport interface LedgerCloseSample {\n readonly ledgerSeq: number;\n /** Close time in whole epoch SECONDS \u2014 the chain reports integers, not millis. */\n readonly closeTimeSeconds: number;\n}\n\n/**\n * Derive cadence from observed closes. Pure: callers fetch the samples, this\n * does the arithmetic, so the measurement is testable without a network.\n *\n * The point estimate comes from the endpoints, where per-ledger noise cancels.\n * The band is the wider of two honest floors:\n *\n * - the observed spread of per-interval rates, which is what DRIFT looks like;\n * - a quantization floor of 1/N s/ledger, because close times are whole\n * seconds, so a window of N ledgers cannot resolve cadence finer than that.\n *\n * The floor is why two samples never report zero uncertainty. A single interval\n * cannot bound drift, and reporting 0 would be the confidently-wrong answer.\n */\nexport function measureCadence(samples: readonly LedgerCloseSample[]): LedgerCadence {\n const sorted = [...new Map(samples.map((s) => [s.ledgerSeq, s])).values()]\n .filter(\n (s) =>\n Number.isInteger(s.ledgerSeq) && s.ledgerSeq >= 0 && Number.isFinite(s.closeTimeSeconds),\n )\n .sort((a, b) => a.ledgerSeq - b.ledgerSeq);\n\n if (sorted.length < 2) {\n throw new Error('measureCadence needs at least two distinct ledger close samples');\n }\n\n const first = sorted[0]!;\n const last = sorted[sorted.length - 1]!;\n const spanLedgers = last.ledgerSeq - first.ledgerSeq;\n const spanSeconds = last.closeTimeSeconds - first.closeTimeSeconds;\n if (spanLedgers <= 0 || spanSeconds <= 0) {\n throw new Error('measureCadence needs samples that advance in both ledger and time');\n }\n\n const secondsPerLedger = spanSeconds / spanLedgers;\n\n let widestDeviation = 0;\n for (let i = 1; i < sorted.length; i += 1) {\n const previous = sorted[i - 1]!;\n const current = sorted[i]!;\n const intervalLedgers = current.ledgerSeq - previous.ledgerSeq;\n const rate = (current.closeTimeSeconds - previous.closeTimeSeconds) / intervalLedgers;\n widestDeviation = Math.max(widestDeviation, Math.abs(rate - secondsPerLedger));\n }\n\n // Close times are whole seconds, so both endpoints carry +/-0.5 s of rounding.\n const quantizationFloor = 1 / spanLedgers;\n\n return {\n secondsPerLedger,\n source: 'measured',\n uncertaintySecondsPerLedger: Math.max(widestDeviation, quantizationFloor),\n basis:\n `${sorted.length} closes across ${formatCount(spanLedgers)} ledgers ` +\n `(${formatCount(first.ledgerSeq)}-${formatCount(last.ledgerSeq)})`,\n };\n}\n\n/**\n * \u2500\u2500 Projection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * What happens to an entry, when, and whether it can be recovered afterwards.\n *\n * Deliberately NOT called `projectedArchiveDate`, which is wrong twice:\n * *archive* is false for temporary entries, which are DELETED, and *date*\n * invites storing a wall-clock value where the truth is a ledger number. When\n * and what-happens are kept apart because they genuinely are apart.\n */\nexport type EndProjection =\n | {\n /** No TTL metadata: unknown, never a fabricated \"expiring now\". */\n readonly status: 'unavailable';\n readonly endBehavior: LedgerEntryTTL['endBehavior'];\n readonly isRestorableAfterEnd: boolean;\n }\n | {\n readonly status: 'known';\n /** Final LIVE ledger, inclusive. The truth; store this, not a date. */\n readonly endsAtLedger: number;\n readonly remainingLedgers: number;\n readonly endBehavior: LedgerEntryTTL['endBehavior'];\n /** Zero remaining is still live. Expiry begins at -1. */\n readonly isLive: boolean;\n /**\n * Whether the entry can be brought back after it ends. Archived entries\n * can be restored; temporary entries are gone. Machine-readable so no\n * display path has to re-derive it and get it wrong \u2014 telling a user\n * that restorable data is \"gone\" is a documented UX bug in this project.\n */\n readonly isRestorableAfterEnd: boolean;\n /** Display edge only. NEVER store this: cadence drifts, ledgers do not. */\n readonly estimatedEndsAt: Date;\n /** Band implied by cadence uncertainty; earliest <= estimated <= latest. */\n readonly earliestEndsAt: Date;\n readonly latestEndsAt: Date;\n readonly cadence: LedgerCadence;\n };\n\n/**\n * Project when an entry's final live ledger closes, and what happens then.\n *\n * `estimatedEndsAt` is when ledger `endsAtLedger` is expected to close. The\n * entry is live THROUGH that ledger; it is gone once the next one closes. That\n * is the inclusive boundary in wall-clock form, and it is the same off-by-one\n * that `remainingLedgers <= 0` gets wrong in ledger form.\n *\n * A negative `remainingLedgers` projects into the past, which is correct: the\n * entry already ended, and the estimate says roughly when.\n */\nexport function projectEnd(\n entry: {\n readonly ttl: TTLObservation;\n readonly endBehavior: LedgerEntryTTL['endBehavior'];\n },\n now: Date,\n cadence: LedgerCadence = MEASURED_TESTNET_CADENCE,\n): EndProjection {\n const isRestorableAfterEnd = entry.endBehavior === 'archived';\n\n if (entry.ttl.status === 'unavailable') {\n return { status: 'unavailable', endBehavior: entry.endBehavior, isRestorableAfterEnd };\n }\n if (!(cadence.secondsPerLedger > 0)) {\n throw new Error('Cadence must be a positive number of seconds per ledger');\n }\n if (!(cadence.uncertaintySecondsPerLedger >= 0)) {\n throw new Error('Cadence uncertainty cannot be negative');\n }\n\n const { endsAtLedger, remainingLedgers } = entry.ttl;\n const at = (secondsPerLedger: number): number =>\n now.getTime() + remainingLedgers * secondsPerLedger * 1000;\n\n const estimate = at(cadence.secondsPerLedger);\n // A slower cadence pushes a FUTURE end later and an already-past end earlier,\n // so the bounds swap sign with remainingLedgers. Take the extremes, not the\n // arms, or an expired entry reports an inverted band.\n const slow = at(cadence.secondsPerLedger + cadence.uncertaintySecondsPerLedger);\n const fast = at(cadence.secondsPerLedger - cadence.uncertaintySecondsPerLedger);\n\n return {\n status: 'known',\n endsAtLedger,\n remainingLedgers,\n endBehavior: entry.endBehavior,\n isLive: !hasExpired(remainingLedgers),\n isRestorableAfterEnd,\n estimatedEndsAt: new Date(estimate),\n earliestEndsAt: new Date(Math.min(slow, fast)),\n latestEndsAt: new Date(Math.max(slow, fast)),\n cadence,\n };\n}\n", "import { Contract, Networks, rpc, xdr } from '@stellar/stellar-sdk';\nimport type { ContractId, LedgerKey } from '@evergreen-stellar/shared-types';\n\n/**\n * The only place in the system that talks to the network.\n *\n * `LedgerEntryReader` is the seam: `scan` depends on this interface, never on\n * the SDK, so unit tests run against a mock and never touch RPC\n * (`AGENTS.md` hard rule 9).\n */\n\nexport interface RawLedgerEntry {\n readonly key: LedgerKey;\n /** Serialized LedgerEntryData. Optional only for legacy TTL-only readers. */\n readonly entryXdr?: string;\n /** Absent for entry types that carry no TTL \u2014 never coerce to a number. */\n readonly liveUntilLedgerSeq: number | undefined;\n}\n\nexport interface LedgerEntryReader {\n /** One round trip: `latestLedger` arrives with the entries, so callers must not fetch it separately. */\n read(keys: readonly LedgerKey[]): Promise<{\n readonly latestLedger: number;\n readonly entries: readonly RawLedgerEntry[];\n }>;\n}\n\nexport class NotTestnetError extends Error {\n constructor(actual: string) {\n // Hard rule 1. Compare the live passphrase rather than trusting a config\n // label that merely says \"testnet\".\n super(`Refusing to run: RPC network is \"${actual}\", not Stellar testnet.`);\n this.name = 'NotTestnetError';\n }\n}\n\n/**\n * Is this a well-formed Stellar contract address?\n *\n * Named so callers validate at the input boundary instead of discovering a typo\n * as an RPC issue three layers down. Answers shape only \u2014 a well-formed ID for\n * a contract that was never deployed is still well-formed.\n */\nexport function isValidContractId(contractId: string): boolean {\n try {\n new Contract(contractId);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Canonical base64 XDR LedgerKey for a contract's instance entry. */\nexport function instanceKey(contractId: ContractId): LedgerKey {\n return new Contract(contractId).getFootprint().toXDR('base64');\n}\n\n/** Canonical base64 XDR LedgerKey for a contract's Wasm code entry. */\nexport function codeKey(wasmHash: Uint8Array): LedgerKey {\n return xdr.LedgerKey.contractCode(new xdr.LedgerKeyContractCode({ hash: wasmHash })).toXDR(\n 'base64',\n );\n}\n\nexport function createRpcReader(server: rpc.Server): LedgerEntryReader {\n return {\n async read(keys) {\n const res = await server.getLedgerEntries(\n ...keys.map((k) => xdr.LedgerKey.fromXDR(k, 'base64')),\n );\n return {\n latestLedger: res.latestLedger,\n entries: res.entries.map((e) => ({\n key: e.key.toXDR('base64'),\n entryXdr: e.val.toXDR('base64'),\n liveUntilLedgerSeq: e.liveUntilLedgerSeq,\n })),\n };\n },\n };\n}\n\n/** Build a reader, refusing anything that is not testnet. */\nexport async function connectTestnet(rpcUrl: string): Promise<LedgerEntryReader> {\n const server = new rpc.Server(rpcUrl);\n const { passphrase } = await server.getNetwork();\n if (passphrase !== Networks.TESTNET) throw new NotTestnetError(passphrase);\n return createRpcReader(server);\n}\n", "import { Address, xdr } from '@stellar/stellar-sdk';\nimport type { EvergreenConfig } from '@evergreen-stellar/shared-types';\n\n/** Canonical declared key; only temporary ContractData can carry retention consent. */\nexport function temporaryKey(value: string, owner: string): string {\n const key = xdr.LedgerKey.fromXDR(value.trim(), 'base64');\n if (\n key.type !== 'contractData' ||\n key.contractData.durability.name !== 'temporary' ||\n key.contractData.key.type === 'scvLedgerKeyContractInstance' ||\n Address.fromScAddress(key.contractData.contract).toString() !== owner\n )\n throw new Error('Expected a temporary key owned by this contract');\n return key.toXDR('base64');\n}\n\nexport function temporaryConsent(\n config: EvergreenConfig,\n entryKey: string,\n owner: string,\n): { allowed: boolean; reason: string } {\n const disabled = {\n allowed: false,\n reason:\n 'Temporary retention disabled: every registration must explicitly opt in for this declared key.',\n };\n try {\n const key = temporaryKey(entryKey, owner);\n const rows = config.contracts.filter((c) => c.id === owner);\n if (!rows.length) return disabled;\n for (const row of rows) {\n if (!row.dataKeys?.some((k) => k.trim() === key)) return disabled;\n const policies =\n row.temporaryEntryPolicies?.filter((p) => temporaryKey(p.entryKey, owner) === key) ?? [];\n if (policies.length !== 1 || policies[0]?.autoExtend !== true) return disabled;\n }\n return {\n allowed: true,\n reason: 'Temporary retention explicitly enabled for this key by every registration.',\n };\n } catch {\n return disabled;\n }\n}\n", "import type {\n BumpThresholds,\n LedgerEntryTTL,\n ScanIssue,\n ScanResult,\n} from '@evergreen-stellar/shared-types';\nimport { hasExpired, isValidThreshold, needsAction } from './ttl.js';\n\n/**\n * Display health for one ledger entry (`W2-D10-01`).\n *\n * Distinct from `LivenessVerdict.severity`, and the distinction is not\n * cosmetic. They answer different questions:\n *\n * assessEntry \"how bad is this ENTRY's state?\" \u2014 a property of the chain\n * assertLiveness \"how bad is it that this RUN did \u2014 a property of a run\n * not act?\"\n *\n * A scan is not a run that failed to act. Feeding a scan to `assertLiveness`\n * with `records: []` would grade every low entry `no-action-recorded` /\n * critical, which is true of a run and nonsense as a description of a contract\n * someone just asked about. So the two grades stay separate \u2014 but the inputs\n * they share do NOT get restated: both call `needsAction` and `hasExpired`,\n * and the blast-radius rule lives here, once, for every display to call.\n */\n\n/**\n * Four states, not three. `unknown` exists because an entry whose TTL could not\n * be read is not healthy, not warning, and not critical \u2014 it is unread. Forcing\n * it into one of the other three is the collapse this codebase keeps refusing:\n * unknown and fine must never render as the same colour.\n */\nexport type EntryHealth = 'healthy' | 'warning' | 'critical' | 'unknown';\n\n/**\n * Whether this entry is shared \u2014 including the case where that **cannot be\n * known**.\n *\n * `undetermined` exists because a single-contract scan can never establish that\n * a `ContractCode` entry is unshared: the chain does not index reverse\n * dependencies from one contract query. So `false` there is not merely\n * unverified, it is **unverifiable by construction on this code path**, and\n * reporting it would be an unknown rendered as a negative \u2014 in the channel the\n * engine and dashboard consume, while the human channel says \"invisible here\".\n *\n * `exclusive` IS assertable for instance, persistent and temporary entries:\n * those ledger keys are derived from the contract itself, so one contract is\n * the whole census rather than a floor.\n */\nexport type SharingStatus = 'shared' | 'exclusive' | 'undetermined';\n\nexport interface EntryAssessment {\n readonly health: EntryHealth;\n readonly needsAction: boolean;\n readonly isExpired: boolean;\n /**\n * Contracts KNOWN to use this entry \u2014 the ones this scan was handed. A floor,\n * never a census.\n */\n readonly observedContractCount: number;\n /**\n * Lower bound on how many contracts this entry takes down with it. **Named as\n * a bound because it is one**: for a code entry seen from a single contract\n * the true radius may be any number, and `1` would read as a measurement.\n */\n readonly blastRadiusAtLeast: number;\n readonly sharingStatus: SharingStatus;\n /** Printable justification. Never a raw error. */\n readonly reason: string;\n}\n\n/**\n * Grade one entry, weighting by blast radius.\n *\n * **A shared code entry at three days is not one contract at three days, it is\n * N contracts at three days.** Severity that ignores that is confidently green\n * right up until every contract built from that Wasm dies at once \u2014 misleading\n * in the worst available direction.\n *\n * `critical` is reserved for states that are unrecoverable or widespread:\n *\n * - already expired \u2014 past `extendTTL`; needs restoring\n * - temporary and low \u2014 deletion is unrecoverable, unlike archival\n * - shared and low \u2014 N contracts, not one\n *\n * A single archived entry that is merely low is `warning`: it needs action, it\n * is recoverable, and it takes nothing else with it.\n */\n/**\n * Two horizons, because a warning and an action answer different questions.\n *\n * Decided by Fatih 2026-09-10. A single threshold forced a choice between\n * missing things and crying wolf: at 17,280 ledgers (~24 h) an entry crosses\n * into trouble with **exactly one scheduled run left** to act on it, so one\n * missed run \u2014 a rate limit, a bad deploy, a network hiccup \u2014 leaves no second\n * chance. That does not serve \"100% uptime\" with strict alerting; it is the\n * tightest value that still technically warns.\n *\n * So the tight value is kept and demoted to the URGENT tier, where its\n * tightness is a feature, and a wider horizon is added above it:\n *\n * WARNING 120,960 ledgers (~7 days) \u2014 six failed daily runs of margin\n * CRITICAL 17,280 ledgers (~1 day) \u2014 act now\n *\n * Separating them is also what resolves the false-alarm tension: the noisy\n * level and the urgent level stop being the same number.\n */\nexport interface HealthThresholds {\n /** Below this, say something. Wide enough to survive several failed runs. */\n readonly warnBelowLedgers: number;\n /** Below this, act now. Deliberately tight. */\n readonly criticalBelowLedgers: number;\n}\n\n/** ~7 days at the measured cadence. Six daily runs of margin. */\nexport const DEFAULT_WARN_LEDGERS = 120_960;\n/**\n * ~1 day. The previous single threshold, kept where tightness is the point.\n *\n * Must equal `evergreen.config.example.json`'s\n * `defaults.bumpWhenRemainingLedgersBelow` and the CLI's\n * `DEFAULT_THRESHOLD_LEDGERS`; `scripts/check-policy-constants.mjs` enforces it.\n * Unpinned until 2026-09-17, which is how #154 happened \u2014 `scan` graded against\n * one threshold and the engine against another, and they disagreed about\n * guinea-pig B minutes apart on identical chain state.\n */\nexport const DEFAULT_CRITICAL_LEDGERS = 17_280;\n\nexport const DEFAULT_THRESHOLDS: HealthThresholds = {\n warnBelowLedgers: DEFAULT_WARN_LEDGERS,\n criticalBelowLedgers: DEFAULT_CRITICAL_LEDGERS,\n};\n\n/** Validate policy ordering separately from observation classification. */\nfunction assertHealthThresholds(thresholds: HealthThresholds): void {\n if (\n !Number.isSafeInteger(thresholds.criticalBelowLedgers) ||\n !isValidThreshold(thresholds.criticalBelowLedgers)\n ) {\n throw new Error('criticalBelowLedgers must be a non-negative safe integer of ledgers');\n }\n if (\n !Number.isSafeInteger(thresholds.warnBelowLedgers) ||\n !isValidThreshold(thresholds.warnBelowLedgers)\n ) {\n throw new Error('warnBelowLedgers must be a non-negative safe integer of ledgers');\n }\n if (!needsAction(thresholds.criticalBelowLedgers, thresholds.warnBelowLedgers)) {\n throw new Error(\n 'warnBelowLedgers must be at least the action threshold (bumpWhenRemainingLedgersBelow)',\n );\n }\n}\n\n/**\n * Only the two health fields, so a caller that has no extension policy can still\n * resolve tiers. Narrowed 2026-09-14 when `scan` needed this: it grades entries\n * and never extends anything, and requiring `extendToLedgers` would have meant\n * passing a meaningless number into a policy call. A full `BumpThresholds` is\n * still assignable, so every existing caller is unaffected.\n */\ntype HealthPolicyInput = Pick<BumpThresholds, 'bumpWhenRemainingLedgersBelow' | 'warnBelowLedgers'>;\n\n/** Preserve omission: only an implicit warning can widen for a legacy action override. */\nexport function resolveHealthThresholds(\n defaults: HealthPolicyInput,\n overrides: Partial<HealthPolicyInput> = {},\n): HealthThresholds {\n const criticalBelowLedgers =\n overrides.bumpWhenRemainingLedgersBelow ?? defaults.bumpWhenRemainingLedgersBelow;\n const warnBelowLedgers =\n overrides.warnBelowLedgers ??\n defaults.warnBelowLedgers ??\n Math.max(DEFAULT_WARN_LEDGERS, criticalBelowLedgers);\n const resolved = { warnBelowLedgers, criticalBelowLedgers };\n assertHealthThresholds(resolved);\n return resolved;\n}\n\n/** Two-tier engine assessment. Impact may be critical without authorizing an action. */\nexport function assessEntryWithThresholds(\n entry: LedgerEntryTTL,\n thresholds: HealthThresholds,\n): EntryAssessment {\n assertHealthThresholds(thresholds);\n const assessment = assessEntry(entry, thresholds.warnBelowLedgers);\n if (entry.ttl.status === 'unavailable' || assessment.isExpired) return assessment;\n if (needsAction(entry.ttl.remainingLedgers, thresholds.criticalBelowLedgers)) {\n return {\n ...assessment,\n health: 'critical',\n needsAction: true,\n reason: `At or below action threshold (${thresholds.criticalBelowLedgers} ledgers). ${assessment.reason}`,\n };\n }\n if (assessment.health === 'healthy') return assessment;\n return {\n ...assessment,\n needsAction: false,\n reason: `${assessment.reason} Above action threshold (${thresholds.criticalBelowLedgers} ledgers); warning only, no bump needed.`,\n };\n}\n\nexport function assessEntry(entry: LedgerEntryTTL, thresholdLedgers: number): EntryAssessment {\n if (!isValidThreshold(thresholdLedgers)) {\n throw new Error('thresholdLedgers must be a non-negative integer of ledgers');\n }\n\n const observedContractCount = entry.contracts.length;\n // A code entry belongs to the Wasm, not the contract, so one observed\n // consumer proves nothing about the rest. Every other entry kind is keyed\n // from the contract itself, where one consumer IS the whole census.\n const sharingStatus: SharingStatus =\n observedContractCount > 1 ? 'shared' : entry.kind === 'code' ? 'undetermined' : 'exclusive';\n const shared = sharingStatus === 'shared';\n\n if (entry.ttl.status === 'unavailable') {\n return {\n health: 'unknown',\n needsAction: false,\n isExpired: false,\n observedContractCount,\n blastRadiusAtLeast: observedContractCount,\n sharingStatus,\n reason: 'No TTL metadata was returned, so this entry\u2019s health is unread \u2014 not healthy.',\n };\n }\n\n const { remainingLedgers } = entry.ttl;\n const isExpired = hasExpired(remainingLedgers);\n const act = needsAction(remainingLedgers, thresholdLedgers);\n\n if (isExpired) {\n return {\n health: 'critical',\n needsAction: true,\n isExpired: true,\n observedContractCount,\n blastRadiusAtLeast: observedContractCount,\n sharingStatus,\n reason:\n entry.endBehavior === 'deleted'\n ? 'Already deleted. Temporary entries are not recoverable.'\n : 'Already archived. Restore it with RestoreFootprintOp \u2014 extendTTL cannot reach it.',\n };\n }\n\n if (!act) {\n return {\n health: 'healthy',\n needsAction: false,\n isExpired: false,\n observedContractCount,\n blastRadiusAtLeast: observedContractCount,\n sharingStatus,\n reason: 'Above threshold.',\n };\n }\n\n if (entry.endBehavior === 'deleted') {\n return {\n health: 'critical',\n needsAction: true,\n isExpired: false,\n observedContractCount,\n blastRadiusAtLeast: observedContractCount,\n sharingStatus,\n reason: 'Low, and temporary \u2014 this data is DELETED at expiry, not archived. Unrecoverable.',\n };\n }\n\n if (shared) {\n return {\n health: 'critical',\n needsAction: true,\n isExpired: false,\n observedContractCount,\n blastRadiusAtLeast: observedContractCount,\n sharingStatus,\n reason: `Low, and shared by ${observedContractCount} contracts \u2014 every one of them fails together.`,\n };\n }\n\n return {\n health: 'warning',\n needsAction: true,\n isExpired: false,\n observedContractCount,\n blastRadiusAtLeast: observedContractCount,\n sharingStatus,\n reason:\n sharingStatus === 'undetermined'\n ? 'Low. This scan saw one contract on it, but a code entry may serve others it cannot see.'\n : 'Low, recoverable, and affects only this contract.',\n };\n}\n\n/** Loudest health present, for a one-line summary. `unknown` never reads as healthy. */\nconst HEALTH_RANK: Record<EntryHealth, number> = {\n critical: 0,\n unknown: 1,\n warning: 2,\n healthy: 3,\n};\n\nexport function worstHealth(assessments: readonly EntryAssessment[]): EntryHealth | undefined {\n if (assessments.length === 0) return undefined;\n return assessments.reduce<EntryHealth>(\n (worst, a) => (HEALTH_RANK[a.health] < HEALTH_RANK[worst] ? a.health : worst),\n 'healthy',\n );\n}\n\n/**\n * The caveats a scan must state, produced ONCE and consumed by every renderer.\n *\n * These are not read failures \u2014 they are bounds on what a successful read can\n * establish. They are returned as `ScanIssue`s so that a consumer asking the\n * obvious question, `issues.length === 0`, gets a correct answer.\n *\n * **The bug this exists to prevent:** the human channel printed two caveats\n * while the JSON reported `issues: []`, `isShared: false` and `blastRadius: 1`.\n * The code that ACTS got the confident version and the person who does not act\n * got the honest one \u2014 backwards, and in the field the product exists to\n * surface. Deriving both channels from this function is what makes the two\n * unable to disagree, rather than merely agreeing today.\n */\nexport function coverageIssues(\n scan: Pick<ScanResult, 'entries' | 'coverage'>,\n): readonly ScanIssue[] {\n const issues: ScanIssue[] = [];\n\n for (const [entryKey, entry] of Object.entries(scan.entries)) {\n if (assessEntry(entry, 0).sharingStatus !== 'undetermined') continue;\n issues.push({\n kind: 'sharing-undetermined',\n contracts: entry.contracts,\n entryKey,\n observedAtLedger: entry.observedAtLedger,\n message:\n 'Code entries are shared by every contract built from the same Wasm. This scan saw ' +\n `${entry.contracts.length}. Whether others depend on this entry cannot be determined from ` +\n 'a single-contract scan \u2014 pass them together to see the real blast radius.',\n });\n }\n\n const supplied = scan.coverage?.dataKeysSuppliedByContract ?? {};\n for (const [contract, count] of Object.entries(supplied)) {\n if (count > 0) continue;\n if (scan.coverage?.noDataKeysDeclaredByContract?.[contract] === true) continue;\n issues.push({\n kind: 'coverage-limited',\n contracts: [contract],\n message:\n 'No data keys were supplied, so any further entries are unread. A clean result covers ' +\n 'only what was asked for, never the whole contract.',\n });\n }\n\n return issues;\n}\n", "import { Keypair, Networks, StrKey, Transaction, TransactionBuilder } from '@stellar/stellar-sdk';\nimport type { Signer } from '@evergreen-stellar/shared-types';\nimport { extensionKey } from './extend.js';\n\nexport interface ExtensionPolicy {\n readonly sourceAccount: string;\n readonly entryKey: string;\n readonly extendToLedgers: number;\n readonly expectedHash: string;\n readonly maxFeeStroops: string;\n readonly now?: () => number;\n}\nexport function isValidPayerAccount(value: string): boolean {\n return StrKey.isValidEd25519PublicKey(value);\n}\nexport function stroopBudget(value: string): bigint {\n if (!/^[1-9]\\d*$/.test(value)) throw new Error('Fee budget must be positive integer stroops');\n return BigInt(value);\n}\n\n/** Check the decoded envelope, not the caller's description of its contents. */\nexport function validateExtensionEnvelope(\n transactionXdr: string,\n policy: ExtensionPolicy,\n): Transaction {\n const tx = TransactionBuilder.fromXDR(transactionXdr, Networks.TESTNET);\n if (\n !(tx instanceof Transaction) ||\n !StrKey.isValidEd25519PublicKey(policy.sourceAccount) ||\n tx.source !== policy.sourceAccount ||\n tx.signatures.length !== 0 ||\n tx.operations.length !== 1 ||\n tx.memo.type !== 'none' ||\n Buffer.from(tx.hash()).toString('hex') !== policy.expectedHash\n )\n throw new Error('Extension envelope does not match its approved identity');\n const op = tx.operations[0]!;\n const envelope = tx.toEnvelope();\n if (\n op.type !== 'extendFootprintTtl' ||\n op.source !== undefined ||\n op.extendTo !== policy.extendToLedgers ||\n !Number.isSafeInteger(op.extendTo) ||\n op.extendTo <= 0 ||\n envelope.type !== 'envelopeTypeTx' ||\n envelope.value.tx.ext.type !== 'sorobanData'\n )\n throw new Error('Only the selected TTL extension is permitted');\n const data = envelope.value.tx.ext.value;\n const footprint = data.resources.footprint;\n const key = extensionKey(policy.entryKey);\n if (\n !['contractData', 'contractCode'].includes(key.type) ||\n footprint.readWrite.length !== 0 ||\n footprint.readOnly.length !== 1 ||\n footprint.readOnly[0]!.toXDR('base64') !== policy.entryKey ||\n data.resourceFee < 0n ||\n BigInt(tx.fee) < data.resourceFee + 100n ||\n BigInt(tx.fee) > stroopBudget(policy.maxFeeStroops)\n )\n throw new Error('Extension footprint or fee is outside policy');\n const now = Math.floor((policy.now ?? (() => Date.now() / 1000))());\n if (\n !tx.timeBounds ||\n BigInt(tx.timeBounds.maxTime) <= BigInt(now) ||\n BigInt(tx.timeBounds.maxTime) > BigInt(now + 60) ||\n BigInt(tx.timeBounds.minTime) > BigInt(now) ||\n tx.extraSigners?.length ||\n tx.minAccountSequence !== undefined ||\n tx.ledgerBounds !== undefined\n )\n throw new Error('Extension validity bounds are outside policy');\n return tx;\n}\n\n/** Plain local key with software checks; not the W3 on-chain policy signer. */\nexport function createEd25519Signer(\n options: ExtensionPolicy & {\n readonly payer: string;\n readonly readSecret: () => string;\n },\n): Signer {\n return {\n payer: options.payer,\n identity: { kind: 'ed25519', account: options.sourceAccount },\n async signExtendTTL(request) {\n if (request.networkPassphrase !== Networks.TESTNET)\n throw new Error('Only Testnet signing is permitted');\n const tx = validateExtensionEnvelope(request.transactionXdr, options);\n // Decided by OUR control flow, never by inspecting the caught error. The\n // earlier draft matched `error.message === 'Wrong key'`, which works but\n // makes a security boundary depend on a string the SDK could also produce.\n // A flag cannot be spoofed by an upstream message.\n let accountMismatch = false;\n try {\n const key = Keypair.fromSecret(options.readSecret());\n if (key.publicKey() !== options.sourceAccount) {\n accountMismatch = true;\n throw new Error('Wrong key');\n }\n tx.sign(key);\n return tx.toXDR();\n } catch {\n // \uD83D\uDD34 THE MOST DELIBERATE SUPPRESSION IN THIS REPO \u2014 reviewed 2026-09-17.\n // `Keypair.fromSecret` receives the raw signing secret and SDK parse\n // errors echo their input, so nothing derived from the caught error may\n // reach the message. The catch stays blind ON PURPOSE: binding it would\n // satisfy `preserve-caught-error` by attaching a `cause`, which is exactly\n // the chain that must not carry key material.\n //\n // The distinction is still worth keeping. A wrong source account and a\n // malformed secret are different operator actions \u2014 reconfigure, versus\n // replace a corrupted key \u2014 and collapsing both lost that for no gain.\n throw new Error(\n accountMismatch\n ? 'Unable to sign the validated extension: the secret does not match the expected source account'\n : 'Unable to sign the validated extension',\n );\n }\n },\n };\n}\n", "import { Address, xdr } from '@stellar/stellar-sdk';\nimport type {\n BumpRecord,\n LedgerEntryTTL,\n ScanResult,\n Signer,\n} from '@evergreen-stellar/shared-types';\nimport type { ExtensionConfirmation, PreparedExtension } from './extend-rpc.js';\nimport { instanceKey, isValidContractId } from './rpc.js';\nimport { resolveExtendTarget } from './network-config.js';\nimport { hasExpired, needsAction } from './ttl.js';\nimport { assertWriteAllowed } from './write-guard.js';\n\nexport interface ExtensionOptions {\n readonly contractId: string;\n readonly additionalLedgers: number;\n readonly maxEntryTtl: number;\n readonly dataKeys?: readonly string[];\n readonly includeCode?: boolean;\n /** Explicit, per-contract consent to write to a protected decay subject. */\n readonly acknowledgeProtected?: readonly string[];\n}\nexport interface PlannedExtension {\n readonly entryKey: string;\n readonly kind: LedgerEntryTTL['kind'];\n readonly contracts: readonly string[];\n readonly before: { readonly observedAtLedger: number; readonly endsAtLedger: number };\n readonly extendToLedgers: number;\n readonly wasCapped: boolean;\n readonly skip: boolean;\n}\nexport interface ExtensionPlan {\n readonly contractId: string;\n readonly additionalLedgers: number;\n readonly entries: readonly PlannedExtension[];\n readonly warnings: readonly string[];\n}\n\n/** Strict base64 round trip: Buffer alone would silently repair invalid input. */\nexport function extensionKey(text: string): xdr.LedgerKey {\n if (!text || Buffer.from(text, 'base64').toString('base64') !== text)\n throw new Error('Invalid extension ledger key');\n const key = xdr.LedgerKey.fromXDR(text, 'base64');\n if (key.toXDR('base64') !== text) throw new Error('Invalid extension ledger key');\n return key;\n}\n\nexport function planExtension(scan: ScanResult, options: ExtensionOptions): ExtensionPlan {\n const { contractId, additionalLedgers, maxEntryTtl } = options;\n if (scan.network !== 'testnet' || !isValidContractId(contractId))\n throw new Error('Extension requires a valid Testnet contract');\n if (\n !Number.isSafeInteger(additionalLedgers) ||\n additionalLedgers <= 0 ||\n !Number.isSafeInteger(maxEntryTtl) ||\n maxEntryTtl <= 0 ||\n maxEntryTtl > 0xffff_ffff\n )\n throw new Error('Invalid extension increment or network ceiling');\n const keys = new Map<string, LedgerEntryTTL['kind']>([[instanceKey(contractId), 'instance']]);\n for (const supplied of options.dataKeys ?? []) {\n const text = supplied.trim();\n const key = extensionKey(text);\n if (\n key.type !== 'contractData' ||\n Address.fromScAddress(key.contractData.contract).toString() !== contractId ||\n key.contractData.key.type === 'scvLedgerKeyContractInstance'\n )\n throw new Error('Expected a data key belonging to this contract');\n keys.set(text, key.contractData.durability.name === 'temporary' ? 'temporary' : 'persistent');\n }\n if (options.includeCode) {\n const code = Object.entries(scan.entries).filter(\n ([, e]) => e.kind === 'code' && e.contracts.includes(contractId),\n );\n if (code.length !== 1) throw new Error('Cannot identify the selected code entry');\n const key = code[0]![0];\n if (extensionKey(key).type !== 'contractCode') throw new Error('Invalid code entry');\n keys.set(key, 'code');\n }\n // Refuse BEFORE any envelope is prepared, so a blocked write never leaves a\n // transaction hash lying around that someone could submit by hand.\n assertWriteAllowed({\n contractId,\n entryKeys: [...keys.keys()],\n scan,\n ...(options.acknowledgeProtected === undefined\n ? {}\n : { options: { acknowledgeProtected: options.acknowledgeProtected } }),\n });\n\n const entries = [...keys].map(([entryKey, kind]): PlannedExtension => {\n const entry = scan.entries[entryKey];\n if (\n !entry ||\n entry.kind !== kind ||\n !entry.contracts.includes(contractId) ||\n entry.ttl.status !== 'known' ||\n scan.issues.some(\n (i) =>\n i.kind !== 'coverage-limited' &&\n i.kind !== 'sharing-undetermined' &&\n (i.entryKey === entryKey || (!i.entryKey && i.contracts.includes(contractId))),\n )\n )\n throw new Error('A selected entry is missing or unreadable');\n const { endsAtLedger, remainingLedgers } = entry.ttl;\n if (\n !Number.isSafeInteger(entry.observedAtLedger) ||\n entry.observedAtLedger < 0 ||\n !Number.isSafeInteger(endsAtLedger) ||\n endsAtLedger > 0xffff_ffff ||\n remainingLedgers !== endsAtLedger - entry.observedAtLedger ||\n hasExpired(remainingLedgers) ||\n !Number.isSafeInteger(remainingLedgers + additionalLedgers)\n )\n throw new Error('A selected entry is expired or has invalid TTL metadata');\n const target = resolveExtendTarget({\n currentRemainingLedgers: remainingLedgers,\n additionalLedgers,\n maxEntryTtl,\n });\n return {\n entryKey,\n kind,\n contracts: [...new Set(entry.contracts)],\n before: { observedAtLedger: entry.observedAtLedger, endsAtLedger },\n extendToLedgers: target.extendToLedgers,\n wasCapped: target.wasCapped,\n skip: !needsAction(remainingLedgers, target.extendToLedgers - 1),\n };\n });\n return {\n contractId,\n additionalLedgers,\n entries,\n warnings: [\n 'Only selected keys are extended; storage is not enumerated and whole-contract protection is not established.',\n ...(options.includeCode\n ? ['Code can serve other contracts outside this scan; extending it affects all consumers.']\n : []),\n ...(entries.some((e) => e.wasCapped)\n ? ['The network ceiling limits the requested additional lifetime.']\n : []),\n ],\n };\n}\n\nexport interface ExtensionExecutionDependencies {\n prepare(entry: PlannedExtension): Promise<PreparedExtension>;\n /** Optional for manual compatibility; the engine supplies a scope-preserving refresh. */\n refresh?(entry: PlannedExtension): Promise<PlannedExtension>;\n /** Engine live runs require a real recorder; this hook must finish before send. */\n beforeSubmit?(prepared: PreparedExtension, signer: Signer['identity']): Promise<void>;\n signer(prepared: PreparedExtension, remainingFeeStroops: string): Signer;\n submit(prepared: PreparedExtension, signedXdr: string): Promise<{ status: string; hash: string }>;\n confirm(hash: string): Promise<ExtensionConfirmation>;\n readAfter(entryKey: string): Promise<{ observedAtLedger: number; endsAtLedger: number }>;\n preview(prepared: PreparedExtension): Promise<void>;\n now(): Date;\n}\nexport interface ExtensionExecutionResult {\n readonly ok: boolean;\n readonly mode: 'dry-run' | 'live';\n readonly records: readonly BumpRecord[];\n readonly skipped: readonly string[];\n readonly unattempted: readonly string[];\n /** Reserved envelope fee upper bound; not a claim about actual fees charged. */\n readonly committedFeeStroops: string;\n /** Sum of accepted prepared fee upper bounds, including in simulation. */\n readonly estimatedFeeStroops: string;\n}\n\nexport interface ExtensionExecutionOptions {\n readonly payer: string;\n readonly submit?: boolean;\n readonly maxFeeStroops?: string;\n readonly reason?: string;\n}\n\n/** Preserve the manual plan API while sharing its execution state machine. */\nexport function executeExtensions(\n plan: ExtensionPlan,\n options: ExtensionExecutionOptions,\n deps: ExtensionExecutionDependencies,\n): Promise<ExtensionExecutionResult> {\n return executeExtensionEntries(plan.entries, options, deps);\n}\n\n/** Execute exact entries sequentially; a possibly-sent transaction stops the selection. */\nexport async function executeExtensionEntries(\n entries: readonly PlannedExtension[],\n options: ExtensionExecutionOptions,\n deps: ExtensionExecutionDependencies,\n): Promise<ExtensionExecutionResult> {\n const live = options.submit === true;\n if (\n !options.payer ||\n (options.maxFeeStroops !== undefined && !/^[1-9]\\d*$/.test(options.maxFeeStroops)) ||\n (live && options.maxFeeStroops === undefined)\n ) {\n throw new Error('Live extension needs an explicit payer and fee budget');\n }\n if (new Set(entries.map((e) => e.entryKey)).size !== entries.length) {\n throw new Error('Duplicate execution entry keys');\n }\n const budget = options.maxFeeStroops === undefined ? undefined : BigInt(options.maxFeeStroops);\n let committed = 0n;\n const records: BumpRecord[] = [];\n const skipped: string[] = [];\n const visited = new Set<string>();\n let ok = true;\n for (const original of entries) {\n visited.add(original.entryKey);\n if (original.skip) {\n skipped.push(original.entryKey);\n continue;\n }\n let entry = original;\n const recordedAt = deps.now().toISOString();\n const base = () => ({\n entryKey: entry.entryKey,\n contracts: entry.contracts,\n payer: options.payer,\n before: entry.before,\n extendToLedgers: entry.extendToLedgers,\n recordedAt,\n reason: options.reason ?? 'Explicit manual extension',\n });\n let sent: { hash: string; signer: Signer['identity'] } | undefined;\n let knownSigner: Signer['identity'] | undefined;\n let confirmed = false;\n try {\n if (deps.refresh) {\n const fresh = await deps.refresh(original);\n if (\n fresh.entryKey !== original.entryKey ||\n fresh.kind !== original.kind ||\n fresh.contracts.join('\\0') !== original.contracts.join('\\0')\n ) {\n throw new Error('Refresh changed execution scope');\n }\n entry = fresh;\n if (entry.skip) {\n skipped.push(entry.entryKey);\n continue;\n }\n }\n const prepared = await deps.prepare(entry);\n if (\n prepared.entry.entryKey !== entry.entryKey ||\n prepared.entry.extendToLedgers !== entry.extendToLedgers ||\n !/^\\d+$/.test(prepared.feeStroops) ||\n (budget !== undefined && committed + BigInt(prepared.feeStroops) > budget)\n ) {\n throw new Error('Prepared selection or fee budget mismatch');\n }\n await deps.preview(prepared);\n if (!live) {\n records.push({ ...base(), mode: 'dry-run', outcome: 'simulated' });\n committed += BigInt(prepared.feeStroops);\n continue;\n }\n const signer = deps.signer(prepared, (budget! - committed).toString());\n if (signer.payer !== options.payer || signer.identity.account !== prepared.sourceAccount) {\n throw new Error('Signer identity mismatch');\n }\n knownSigner = signer.identity;\n const signed = await signer.signExtendTTL({\n networkPassphrase: 'Test SDF Network ; September 2015',\n transactionXdr: prepared.transactionXdr,\n });\n await deps.beforeSubmit?.(prepared, signer.identity);\n sent = { hash: prepared.transactionHash, signer: signer.identity };\n committed += BigInt(prepared.feeStroops);\n const response = await deps.submit(prepared, signed);\n if (response.hash !== sent.hash) throw new Error('Submission hash mismatch');\n if (response.status === 'ERROR') {\n confirmed = true;\n throw new Error('Submission rejected');\n }\n if (!['PENDING', 'DUPLICATE'].includes(response.status))\n throw new Error('Submission uncertain');\n const confirmation = await deps.confirm(sent.hash);\n if (confirmation.status === 'unconfirmed') throw new Error('Confirmation pending');\n confirmed = true;\n if (confirmation.status !== 'confirmed') throw new Error('Transaction failed');\n const after = await deps.readAfter(entry.entryKey);\n if (\n !Number.isSafeInteger(after.observedAtLedger) ||\n after.observedAtLedger < confirmation.ledger ||\n !Number.isSafeInteger(after.endsAtLedger) ||\n after.endsAtLedger <= entry.before.endsAtLedger ||\n after.endsAtLedger < confirmation.ledger + entry.extendToLedgers\n ) {\n throw new Error('TTL increase could not be verified');\n }\n records.push({\n ...base(),\n mode: 'live',\n outcome: 'succeeded',\n transactionHash: sent.hash,\n signer: sent.signer,\n after,\n });\n } catch {\n ok = false;\n if (sent && !confirmed) {\n records.push({\n ...base(),\n mode: 'live',\n outcome: 'submitted',\n transactionHash: sent.hash,\n signer: sent.signer,\n });\n } else if (live) {\n records.push({\n ...base(),\n outcome: 'failed',\n mode: 'live',\n ...(sent ? { transactionHash: sent.hash } : {}),\n ...(knownSigner ? { signer: knownSigner } : {}),\n error: {\n code: 'EXTENSION_FAILED',\n message: 'Extension rejected or post-state unverified. No replacement was submitted.',\n },\n });\n } else {\n records.push({\n ...base(),\n outcome: 'failed',\n mode: 'dry-run',\n error: {\n code: 'SIMULATION_FAILED',\n message: 'Extension preparation or fee validation failed. Nothing was submitted.',\n },\n });\n }\n break;\n }\n }\n return {\n ok,\n mode: live ? 'live' : 'dry-run',\n records,\n skipped,\n unattempted: entries.filter((e) => !visited.has(e.entryKey)).map((e) => e.entryKey),\n committedFeeStroops: live ? committed.toString() : '0',\n estimatedFeeStroops: committed.toString(),\n };\n}\n", "import { xdr, rpc } from '@stellar/stellar-sdk';\n\n/**\n * State-archival settings, read from the chain (`W2-D9-01`).\n *\n * The primer is explicit that these are **network configuration, not constants\n * to hardcode**. `max_entry_ttl` in particular is the protocol ceiling on any\n * single extend, and a hardcoded copy would be one more policy with two homes.\n */\n\n/** ConfigSetting key for STATE_ARCHIVAL. Recorded 2026-09-05, unchanged since. */\nexport const STATE_ARCHIVAL_CONFIG_KEY = 'AAAACAAAAAo=';\n\nexport interface StateArchivalSettings {\n /** Inclusive lifetime setting; an extend operation target must be <= this minus one. */\n readonly maxEntryTtl: number;\n readonly minTemporaryTtl: number;\n readonly minPersistentTtl: number;\n /**\n * Rent rate denominators. Observed 2026-09-10 as persistent 1215 and\n * temporary 2430 \u2014 **exactly 2:1**, which is the protocol stating outright\n * what the fee fixture measured as a 1.952x durability ratio. The residual\n * is the flat components, not noise in the measurement.\n */\n readonly persistentRentRateDenominator: string;\n readonly temporaryRentRateDenominator: string;\n readonly observedAtLedger: number;\n}\n\n/**\n * Read a field that the SDK exposes as a METHOD in its ESM build and as a\n * PLAIN PROPERTY in its CJS build.\n *\n * Found the hard way on 2026-09-10: the same decode worked under `node -e`\n * (CJS) and threw under Vitest (ESM). A parser that only works in one module\n * system is a parser that passes its tests and fails in the binary, or the\n * reverse \u2014 so this accepts both rather than betting on which build is loaded.\n */\nfunction field(source: unknown, name: string): unknown {\n if (source === null || typeof source !== 'object') return undefined;\n const value = (source as Record<string, unknown>)[name];\n return typeof value === 'function' ? (value as () => unknown).call(source) : value;\n}\n\nfunction firstNumber(source: unknown, ...names: string[]): number | undefined {\n for (const name of names) {\n const value = field(source, name);\n if (typeof value === 'number') return value;\n if (typeof value === 'bigint') return Number(value);\n }\n return undefined;\n}\n\n/** Decode the settings from a raw ConfigSetting entry payload. Pure; testable offline. */\nexport function parseStateArchivalSettings(\n entryXdr: string,\n observedAtLedger: number,\n): StateArchivalSettings {\n const data: unknown = xdr.LedgerEntryData.fromXDR(entryXdr, 'base64');\n const configSetting = field(data, 'configSetting');\n const settings = field(configSetting, 'stateArchivalSettings');\n const maxEntryTtl = firstNumber(settings, 'max_entry_ttl', 'maxEntryTtl');\n if (maxEntryTtl === undefined) {\n throw new Error('Entry is not a STATE_ARCHIVAL config setting');\n }\n const persistentDenominator =\n field(settings, 'persistent_rent_rate_denominator') ??\n field(settings, 'persistentRentRateDenominator');\n const temporaryDenominator =\n field(settings, 'temp_rent_rate_denominator') ?? field(settings, 'tempRentRateDenominator');\n return {\n maxEntryTtl,\n minTemporaryTtl: firstNumber(settings, 'min_temporary_ttl', 'minTemporaryTtl') ?? 0,\n minPersistentTtl: firstNumber(settings, 'min_persistent_ttl', 'minPersistentTtl') ?? 0,\n persistentRentRateDenominator: String(persistentDenominator),\n temporaryRentRateDenominator: String(temporaryDenominator),\n observedAtLedger,\n };\n}\n\nexport async function readStateArchivalSettings(\n server: rpc.Server,\n): Promise<StateArchivalSettings> {\n const response = await server.getLedgerEntries(\n xdr.LedgerKey.fromXDR(STATE_ARCHIVAL_CONFIG_KEY, 'base64'),\n );\n const entry = response.entries[0];\n if (!entry) throw new Error('Network returned no state-archival config entry');\n return parseStateArchivalSettings(entry.val.toXDR('base64'), response.latestLedger);\n}\n\nexport interface ResolvedTarget {\n /** The absolute target to pass as `extendTo`. Never a delta. */\n readonly extendToLedgers: number;\n /** True when the request was reduced to the protocol ceiling. */\n readonly wasCapped: boolean;\n /** What the caller asked for, before capping. */\n readonly requestedLedgers: number;\n}\n\n/**\n * Turn a user's \"give me N more ledgers\" into the absolute target the protocol\n * wants \u2014 and cap it at the ceiling.\n *\n * **The CLI absorbs this conversion so the user never meets it.** `--ledgers N`\n * is how a person thinks about headroom; `extendTo` is an SDK quirk, and\n * pushing it into the user's vocabulary is the opposite of what a CLI is for.\n *\n * **Capping is reported, never silent.** `current + N` can exceed\n * `max_entry_ttl`, and quietly handing back a smaller extension than requested\n * while reporting success is the same silent-shortfall shape as passing a delta\n * where a target belongs. Worse here, because **simulation does not clamp**:\n * asked for 4,000,000 against a 3,110,400 ceiling it quoted 345,853 stroops,\n * about 52% more than the capped extension actually costs (observed\n * 2026-09-10). An uncapped request would be quoted for something that cannot\n * happen.\n */\nexport function resolveExtendTarget(args: {\n readonly currentRemainingLedgers: number;\n readonly additionalLedgers: number;\n readonly maxEntryTtl: number;\n}): ResolvedTarget {\n const { currentRemainingLedgers, additionalLedgers, maxEntryTtl } = args;\n if (!Number.isInteger(additionalLedgers) || additionalLedgers <= 0) {\n throw new Error('--ledgers must be a positive integer of ledgers');\n }\n if (!Number.isInteger(maxEntryTtl) || maxEntryTtl <= 0) {\n throw new Error('maxEntryTtl must come from the network and be a positive integer');\n }\n // An already-expired entry has negative remaining; treat its base as 0 rather\n // than subtracting from the request.\n const base = Math.max(0, currentRemainingLedgers);\n const requested = base + additionalLedgers;\n // The setting includes the current live ledger. Core rejects an operation\n // target greater than maxEntryTTL - 1 (ExtendFootprintTTLOpFrame::doCheckValidForSoroban).\n const capped = Math.min(requested, maxEntryTtl - 1);\n return {\n extendToLedgers: capped,\n wasCapped: capped < requested,\n requestedLedgers: requested,\n };\n}\n", "import type { ContractId, LedgerKey, ScanResult } from '@evergreen-stellar/shared-types';\n\n/**\n * The deny-list for the WRITE path (`W2-D11-01` review, 2026-09-12).\n *\n * The config loader warns when a contract sits in both `contracts` and\n * `_doNotWatch`. **A warning is adequate for a scan and inadequate for a\n * write.** The extend path did not read config at all, so neither warning nor\n * enforcement reached it \u2014 demonstrated by planning a real extend against\n * guinea-pig B, which produced a prepared envelope and never mentioned that B\n * is a decay-proof subject.\n *\n * What is at stake is not a contract but a DATE. Guinea-pigs B and C are\n * calibrated to cross their thresholds unattended on 2026-09-20 and\n * 2026-09-25. A single extend moves that crossing past the sprint, silently,\n * and it cannot be re-armed inside it \u2014 the ageing is the evidence.\n *\n * So this refuses rather than warns, and it refuses by DEFAULT: a caller must\n * pass an explicit acknowledgement to proceed, and the acknowledgement names\n * the date it is spending.\n */\n\n/** Protected subjects. Deliberately in code, not config \u2014 config can be edited. */\nexport const PROTECTED_ENTRIES: ReadonlyArray<{\n readonly contractId: ContractId;\n readonly label: string;\n /**\n * When this entry crosses the ALERT THRESHOLD \u2014 `liveUntil - 17,280` \u2014 which\n * is when the engine should fire. NOT when it expires.\n *\n * The two were both called \"crossing\" until 2026-09-12, and they are exactly\n * 24 hours apart because `THRESHOLD_LEDGERS` is exactly one day. That made\n * the collision convincing: the drift check and the CLI disagreed by exactly\n * 24.0h on both B and C and both were right. Someone scheduling evidence\n * capture from this field alone arrives a day early for the expiry \u2014 and the\n * expiry is the unrepeatable event.\n */\n readonly alertThresholdOn: string;\n /** When the entry actually expires. THIS is the date to be present for. */\n readonly expiresOn: string;\n readonly why: string;\n}> = [\n {\n contractId: 'CCYGO7KQ6FCAZBZAUWAPCAX4RBDIPZK4BJR2KGKISEIGARTJPB7KLTTQ',\n label: 'guinea-pig B',\n alertThresholdOn: '2026-09-20',\n expiresOn: '2026-09-21',\n why: 'Natural-decay proof. Extending it moves the crossing past the sprint and the ageing cannot be recreated.',\n },\n {\n contractId: 'CCLW55OIEDHKS5DHDGEA3B2F2ZVOTRXZIOPO36SCMHNQV3VQEGRR33FL',\n label: 'guinea-pig C',\n alertThresholdOn: '2026-09-25',\n expiresOn: '2026-09-26',\n why: 'Backup natural-decay proof, the only second shot if B is missed.',\n },\n];\n\n/**\n * The shared `ContractCode` entry, protected BY ITS LEDGER KEY.\n *\n * A, B and C are built from one Wasm, so extending \"A's code\" extends B's and\n * C's too \u2014 the one operation that reaches the protected subjects without\n * naming them.\n *\n * **Consumer lists cannot catch this, and the first version of this guard\n * failed exactly there.** A scan of A alone reports the code entry with one\n * consumer, because the chain does not index reverse dependencies from a\n * single contract query \u2014 the same `undetermined` limit the JSON channel\n * already admits to. So `extend A --include-code` looked harmless: one\n * contract, no protected subject in the list, guard silent. Verified against\n * the real CLI on 2026-09-12, where it produced a prepared envelope.\n *\n * Keying on the entry itself removes the dependence on what a scan can see.\n * Derived from Wasm hash `c7e55f0a\u202698bfb`.\n */\nexport const SHARED_CODE_ENTRY_KEY = 'AAAAB8flXwrYnvsGALwVBIsVUJn6TZfO4WRm+hJEs9y86Yv7';\nexport const SHARED_CODE_UNTIL = '2026-09-26';\n\nexport class ProtectedEntryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ProtectedEntryError';\n }\n}\n\nexport interface WriteGuardOptions {\n /**\n * Explicit acknowledgement that a protected subject is being spent. Absent or\n * false refuses. Named rather than boolean-true so it cannot be set by\n * accident: the caller states WHICH contract they mean.\n */\n readonly acknowledgeProtected?: readonly ContractId[];\n /** Today, injected so the guard is testable without a clock. */\n readonly now?: Date;\n}\n\n/**\n * Refuse a write that would touch a protected subject.\n *\n * Called before any envelope is prepared, so a refused write never produces a\n * transaction hash that someone could submit by hand afterwards.\n */\nexport function assertWriteAllowed(args: {\n readonly contractId: ContractId;\n readonly entryKeys: readonly LedgerKey[];\n readonly scan: Pick<ScanResult, 'entries'>;\n readonly options?: WriteGuardOptions;\n}): void {\n const acknowledged = new Set(args.options?.acknowledgeProtected ?? []);\n\n // Every contract this write touches, including ones reached through a shared\n // entry rather than named on the command line. That indirection is the whole\n // hazard: `extend A --include-code` never mentions B or C.\n const touched = new Set<ContractId>([args.contractId]);\n for (const entryKey of args.entryKeys) {\n for (const consumer of args.scan.entries[entryKey]?.contracts ?? []) {\n touched.add(consumer);\n }\n }\n\n // Checked FIRST and by key, because this is the case a consumer list cannot\n // see. `--include-code` on any of the three reaches all three.\n if (args.entryKeys.includes(SHARED_CODE_ENTRY_KEY) && !acknowledged.has(SHARED_CODE_ENTRY_KEY)) {\n throw new ProtectedEntryError(\n 'Refusing to write: this would extend the SHARED ContractCode entry.\\n' +\n ' Guinea-pigs A, B and C are built from one Wasm and share this single entry,\\n' +\n ' so extending it extends all three \u2014 including both natural-decay proofs,\\n' +\n ' which cross on 2026-09-20 and 2026-09-25 and cannot be re-armed.\\n' +\n ' A scan of one contract CANNOT show you this: it reports one consumer,\\n' +\n ' because the chain does not index reverse dependencies from one query.\\n\\n' +\n ` It is scheduled for extension at W3-D18-02d, after ${SHARED_CODE_UNTIL}.\\n` +\n ` To override: --acknowledge-protected ${SHARED_CODE_ENTRY_KEY}`,\n );\n }\n\n for (const subject of PROTECTED_ENTRIES) {\n if (!touched.has(subject.contractId)) continue;\n if (acknowledged.has(subject.contractId)) continue;\n\n const viaShared = args.contractId !== subject.contractId;\n throw new ProtectedEntryError(\n `Refusing to write: this would touch ${subject.label} (${subject.contractId}).\\n` +\n (viaShared\n ? ` It is not the contract you named \u2014 it is reached through a SHARED ENTRY.\\n` +\n ` A, B and C are built from one Wasm, so extending code extends all three.\\n`\n : '') +\n ` ${subject.why}\\n` +\n ` It crosses the alert threshold on ${subject.alertThresholdOn} and EXPIRES on\\n` +\n ` ${subject.expiresOn} \u2014 be present for the second one, that is the unrepeatable\\n` +\n ` event. Extending it now\\n` +\n ` moves that crossing past the sprint, and the ageing cannot be recreated.\\n\\n` +\n ` If you genuinely intend this, pass the contract explicitly:\\n` +\n ` --acknowledge-protected ${subject.contractId}\\n` +\n ` The shared code entry is due to be extended at W3-D18-02d, after ${SHARED_CODE_UNTIL}.`,\n );\n }\n}\n", "import { formatCount } from './format.js';\nimport { temporaryKey } from './temporary-policy.js';\nimport type {\n EvergreenConfig,\n BumpThresholds,\n Stroops,\n ExecutionMode,\n PayerConfig,\n TestnetPassphrase,\n} from '@evergreen-stellar/shared-types';\nimport { DEFAULT_WARN_LEDGERS, resolveHealthThresholds } from './health.js';\nimport { needsAction, SECONDS_PER_LEDGER } from './ttl.js';\nimport { isValidPayerAccount } from './ed25519-signer.js';\n\n/**\n * Config loading (`W2-D13-01`). Pure: takes text, returns a validated config.\n * The caller reads the file, so this is testable without a filesystem.\n *\n * Three properties the shared types demand in comments and a loader has to\n * actually enforce, because a comment is not a mechanism:\n *\n * - **Omitted `mode` means dry-run.** Live is an explicit opt-in, never a\n * default and never inferred.\n * - **Every contract's payer must resolve**, checked at the input boundary\n * rather than discovered when a bump tries to sign.\n * - **Only testnet.** The passphrase is compared, not a label trusted.\n *\n * And one this project learned the hard way: **secrets are named here, never\n * stored here.** A config carrying a secret key would be committed by someone,\n * eventually, so the loader rejects anything that looks like one.\n */\n\nexport const TESTNET_PASSPHRASE: TestnetPassphrase = 'Test SDF Network ; September 2015';\n\nexport interface ConfigLoadResult {\n readonly config: EvergreenConfig;\n /** Non-fatal, but printed. An empty array is not the same as \"nothing to say\". */\n readonly warnings: readonly string[];\n}\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Documentation fields are `_`-prefixed by convention and carry no behaviour. */\nfunction isDocumentationKey(key: string): boolean {\n return key.startsWith('_');\n}\n\nfunction requireRecord(value: unknown, path: string): Record<string, unknown> {\n if (!isRecord(value)) throw new ConfigError(`${path} must be an object.`);\n return value;\n}\n\nfunction requireString(value: unknown, path: string): string {\n if (typeof value !== 'string' || value.length === 0) {\n throw new ConfigError(`${path} must be a non-empty string.`);\n }\n return value;\n}\n\nfunction requireLedgerCount(value: unknown, path: string): number {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {\n throw new ConfigError(`${path} must be a non-negative safe whole number of ledgers.`);\n }\n return value;\n}\n\nconst THRESHOLD_FIELDS = [\n 'warnBelowLedgers',\n 'bumpWhenRemainingLedgersBelow',\n 'extendToLedgers',\n] as const;\n\nfunction parseThresholdFields(value: unknown, path: string): Partial<BumpThresholds> {\n const raw = requireRecord(value, path);\n const parsed: { -readonly [K in keyof BumpThresholds]?: BumpThresholds[K] } = {};\n for (const key of Object.keys(raw)) {\n if (isDocumentationKey(key)) continue;\n if (!THRESHOLD_FIELDS.some((field) => field === key)) {\n throw new ConfigError(\n `${path}.${key} is not a supported threshold field. Use warnBelowLedgers for warning and bumpWhenRemainingLedgersBelow for action.`,\n );\n }\n }\n for (const field of THRESHOLD_FIELDS) {\n if (raw[field] !== undefined)\n parsed[field] = requireLedgerCount(raw[field], `${path}.${field}`);\n }\n return parsed;\n}\n\nfunction validateThresholdPair(\n defaults: BumpThresholds,\n overrides: Partial<BumpThresholds> | undefined,\n path: string,\n warnings: string[],\n): void {\n try {\n const pair = resolveHealthThresholds(defaults, overrides);\n if (\n defaults.warnBelowLedgers === undefined &&\n overrides?.warnBelowLedgers === undefined &&\n !needsAction(pair.criticalBelowLedgers, DEFAULT_WARN_LEDGERS) &&\n (path === 'defaults' || pair.criticalBelowLedgers !== defaults.bumpWhenRemainingLedgersBelow)\n ) {\n warnings.push(\n `${path}: warning omitted; derived warnBelowLedgers=${pair.warnBelowLedgers} to match the action threshold. Set an explicit warning horizon for an earlier warning.`,\n );\n }\n } catch (error) {\n throw new ConfigError(`${path}: ${(error as Error).message}`);\n }\n}\n\n/**\n * Anything that looks like a Stellar secret seed. Checked on every string in\n * the file rather than only where a secret might plausibly go \u2014 the point is to\n * catch it wherever someone pasted it.\n */\nconst SECRET_SEED = /\\bS[A-Z2-7]{55}\\b/;\n\nfunction assertNoSecrets(raw: string): void {\n if (SECRET_SEED.test(raw)) {\n throw new ConfigError(\n 'This config appears to contain a Stellar SECRET KEY.\\n' +\n 'Evergreen never stores secrets in config \u2014 name an environment variable instead\\n' +\n '(for example \"secretEnvVar\": \"EVERGREEN_SIGNER_SECRET\"). Rotate that key: a\\n' +\n 'secret written to a config file should be treated as already leaked.',\n );\n }\n}\n\nfunction parsePayer(value: unknown, id: string): PayerConfig {\n const payer = requireRecord(value, `payers.${id}`);\n const signer = payer.signer;\n if (signer === 'ed25519') {\n const sourceAccount =\n payer.sourceAccount === undefined\n ? undefined\n : requireString(payer.sourceAccount, `payers.${id}.sourceAccount`);\n if (sourceAccount !== undefined && !isValidPayerAccount(sourceAccount)) {\n throw new ConfigError(`payers.${id}.sourceAccount must be a public Ed25519 account.`);\n }\n const maxFeeStroops =\n payer.maxFeeStroops === undefined\n ? undefined\n : requireString(payer.maxFeeStroops, `payers.${id}.maxFeeStroops`);\n if (maxFeeStroops !== undefined && !/^[1-9]\\d*$/.test(maxFeeStroops)) {\n throw new ConfigError(`payers.${id}.maxFeeStroops must be positive decimal integer stroops.`);\n }\n return {\n signer,\n secretEnvVar: requireString(payer.secretEnvVar, `payers.${id}.secretEnvVar`),\n ...(sourceAccount === undefined ? {} : { sourceAccount }),\n ...(maxFeeStroops === undefined ? {} : { maxFeeStroops: maxFeeStroops as Stroops }),\n };\n }\n if (signer === 'policy') {\n return { signer, signerRef: requireString(payer.signerRef, `payers.${id}.signerRef`) };\n }\n throw new ConfigError(`payers.${id}.signer must be \"ed25519\" or \"policy\".`);\n}\n\n/**\n * The shortest action window a scheduler can actually serve.\n *\n * Two configurable numbers have to stand in a relation and nothing enforced it:\n * the action threshold decides how much warning the engine gets, and the\n * SCHEDULER decides how often it can act on that warning. Set a threshold\n * shorter than the scheduler's worst gap and the engine silently never fires\n * inside its own window \u2014 no error, no alarm, an entry archiving while every\n * run reports healthy.\n *\n * Measured 2026-09-14 across two independent workflows: GitHub Actions delivers\n * ~7.5% of a declared 15-minute cron, **worst observed gap 331 minutes**\n * (docs/evidence/2026-09-14-scheduler-cadence). The constraint comes from the\n * scheduler, not from the protocol, and the message says so \u2014 a user who reads\n * \"too low\" as a Soroban rule will go looking in the wrong documentation.\n *\n * Four worst-gaps is the floor: one to notice, and three to survive the failures\n * that made the gap worst in the first place. At 5 s/ledger that is 15,888\n * ledgers, which is why the 17,280 default (a full day) clears it and a\n * \"couple of hours\" threshold does not.\n */\n/**\n * MEASUREMENT \u2014 what the scheduler actually did. Not a policy.\n *\n * Sample: 20 `engine-cron` runs, 2026-09-12T19:19Z \u2192 2026-09-15T01:31Z.\n * Median gap 136 min; worst gap 369 min; declared cron interval 15 min, so the\n * scheduler delivers about **11% of its declared cadence**.\n *\n * **Update this whenever anyone measures.** It should always be true, and it\n * will keep rising: a running maximum over a growing sample only goes up. It was\n * 331 when first recorded on 2026-09-14 and 369 nine hours later.\n *\n * Nothing enforces a policy against this number directly \u2014 that is\n * `SCHEDULER_GAP_FLOOR_MINUTES` below, and the separation is deliberate.\n */\nexport const WORST_OBSERVED_SCHEDULER_GAP_MINUTES = 369;\n\n/**\n * FLOOR \u2014 what we are willing to allow. A decision, not an observation.\n *\n * Split from the measurement on 2026-09-15 because one constant was doing two\n * jobs with different update cadences. A floor that tracks the observed maximum\n * thrashes: every fresh measurement invalidates fixtures and retroactively fails\n * configurations that were correct the day before. That is a design defect, not\n * a value that needs updating faster.\n *\n * **Why 480 (8 hours):** a round operational boundary roughly 30% above the\n * current worst observation, chosen so ordinary drift cannot move it. It is not\n * derived from the measurement \u2014 deriving it is precisely what makes it move.\n *\n * **Review trigger, not automatic tracking:** if a measured gap ever exceeds 80%\n * of this floor (394 min), the headroom has been consumed and the floor needs a\n * deliberate decision. Do not raise it by reflex when a measurement lands.\n */\nexport const SCHEDULER_GAP_FLOOR_MINUTES = 480;\n\nconst MIN_ACTION_RUNS_IN_WINDOW = 4;\n\n/**\n * Deliberately still derived from **331**, the measurement as of 2026-09-14, and\n * frozen there pending a decision after Sep 26.\n *\n * \uD83D\uDD34 **Recorded finding, do not silently fix.** At the current measurement of 369\n * this window would be `ceil(369 \u00D7 4 \u00D7 60 / 5) = 17,712` ledgers, and the default\n * action threshold of 17,280 would fall **below** it \u2014 giving **3.9 scheduler\n * runs** of margin against the 4 this constant exists to guarantee. 17,280 only\n * ever cleared the old value by accident: it was chosen before the scheduler was\n * ever measured.\n *\n * It is not changed here because raising the number that governs *when the engine\n * acts* in the week of guinea-pig B's crossing is the wrong week to do it. B\n * crosses ~2026-09-20 and C ~2026-09-25; revisit after Sep 26.\n */\nconst ACTION_WINDOW_GAP_BASIS_MINUTES = 331;\nexport const MIN_SAFE_ACTION_WINDOW_LEDGERS = Math.ceil(\n (ACTION_WINDOW_GAP_BASIS_MINUTES * MIN_ACTION_RUNS_IN_WINDOW * 60) / SECONDS_PER_LEDGER,\n);\n/**\n * Warns rather than refuses. A short threshold is legitimate on a scheduler we\n * have not measured \u2014 someone self-hosting on a real cron gets minutes, not\n * hours \u2014 so refusing would block a correct configuration. But it is silent\n * failure if nobody says anything, and this is the config loader's one chance.\n */\nfunction warnIfBelowSchedulerFloor(\n // NOT a TTL policy comparison, and named so the lint rule can tell. This\n // compares a CONFIGURED WINDOW against a SCHEDULER FLOOR \u2014 neither side is a\n // remaining TTL. Renaming rather than disabling the rule: a suppression here\n // would be indistinguishable from a suppression on a real policy comparison.\n configuredActionLedgers: number,\n path: string,\n warnings: string[],\n): void {\n if (configuredActionLedgers >= MIN_SAFE_ACTION_WINDOW_LEDGERS) return;\n const hours = ((configuredActionLedgers * SECONDS_PER_LEDGER) / 3600).toFixed(1);\n warnings.push(\n `\u26A0 ${path}.bumpWhenRemainingLedgersBelow is ${formatCount(configuredActionLedgers)} ledgers ` +\n `(~${hours}h of warning), below the ${formatCount(MIN_SAFE_ACTION_WINDOW_LEDGERS)} ` +\n 'needed for the engine to act reliably.\\n' +\n ' This is a SCHEDULER limit, not a Soroban one. GitHub Actions was measured on ' +\n '2026-09-14 delivering ~7.5% of a declared 15-minute cron, worst gap 331 minutes.\\n' +\n ' A window this short can close between runs: the entry archives while every run ' +\n 'reports healthy, with no error anywhere.\\n' +\n ' Raise the threshold, or run the engine on a scheduler whose worst gap you have measured.',\n );\n}\n\nexport function loadConfig(raw: string): ConfigLoadResult {\n assertNoSecrets(raw);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // DELIBERATE SUPPRESSION \u2014 reviewed 2026-09-17, keep. V8's SyntaxError for\n // JSON.parse embeds a SNIPPET OF THE DOCUMENT (\"Unexpected token '}' \u2026 is not\n // valid JSON\"), and this document is a config that may carry payer accounts.\n // `assertNoSecrets(raw)` runs above but checks for seeds, not for everything\n // that should stay out of a log. The advice below is what an operator acts on.\n throw new ConfigError('Config is not valid JSON. Check for a trailing comma or a stray quote.');\n }\n const root = requireRecord(parsed, 'config');\n const warnings: string[] = [];\n\n const network = requireRecord(root.network, 'network');\n const networkPassphrase = requireString(network.networkPassphrase, 'network.networkPassphrase');\n if (networkPassphrase !== TESTNET_PASSPHRASE) {\n // Compare the passphrase, never trust a label that says \"testnet\".\n throw new ConfigError(\n `Refusing to load: network.networkPassphrase is \"${networkPassphrase}\", not Stellar testnet.\\n` +\n 'Evergreen runs against testnet only in this release.',\n );\n }\n\n const defaults = parseThresholdFields(root.defaults, 'defaults');\n const thresholds: BumpThresholds = {\n ...defaults,\n bumpWhenRemainingLedgersBelow: requireLedgerCount(\n defaults.bumpWhenRemainingLedgersBelow,\n 'defaults.bumpWhenRemainingLedgersBelow',\n ),\n extendToLedgers: requireLedgerCount(defaults.extendToLedgers, 'defaults.extendToLedgers'),\n };\n validateThresholdPair(thresholds, undefined, 'defaults', warnings);\n warnIfBelowSchedulerFloor(thresholds.bumpWhenRemainingLedgersBelow, 'defaults', warnings);\n\n const payersRaw = requireRecord(root.payers, 'payers');\n const payers: Record<string, PayerConfig> = {};\n for (const [id, value] of Object.entries(payersRaw)) {\n if (isDocumentationKey(id)) continue;\n payers[id] = parsePayer(value, id);\n }\n if (Object.keys(payers).length === 0)\n throw new ConfigError('payers must define at least one payer.');\n\n if (!Array.isArray(root.contracts)) throw new ConfigError('contracts must be an array.');\n const contracts = root.contracts.map((value, index) => {\n const contract = requireRecord(value, `contracts[${index}]`);\n const id = requireString(contract.id, `contracts[${index}].id`);\n if (id.startsWith('REPLACE_WITH')) {\n throw new ConfigError(\n `contracts[${index}].id is still the placeholder from the example config.\\n` +\n 'Replace it with a real contract ID before running.',\n );\n }\n const payer = requireString(contract.payer, `contracts[${index}].payer`);\n if (!(payer in payers)) {\n // Checked here rather than discovered when a bump tries to sign.\n throw new ConfigError(\n `contracts[${index}].payer is \"${payer}\", which is not defined in payers.\\n` +\n `Known payers: ${Object.keys(payers).join(', ')}`,\n );\n }\n const label = typeof contract.label === 'string' ? contract.label : undefined;\n // ADR-006 makes coverage part of the health answer, so a config that\n // dropped the caller's declaration would silently downgrade every\n // configured scan to \"unknown scope\". Carried through explicitly.\n if (contract.dataKeys !== undefined && !Array.isArray(contract.dataKeys)) {\n throw new ConfigError(`contracts[${index}].dataKeys must be an array of ledger keys.`);\n }\n const dataKeys = contract.dataKeys as readonly string[] | undefined;\n if (contract.noDataKeys !== undefined && typeof contract.noDataKeys !== 'boolean') {\n throw new ConfigError(`contracts[${index}].noDataKeys must be true or false.`);\n }\n let temporaryEntryPolicies: { entryKey: string; autoExtend: boolean }[] | undefined;\n if (contract.temporaryEntryPolicies !== undefined) {\n const path = `contracts[${index}].temporaryEntryPolicies`;\n try {\n if (!Array.isArray(contract.temporaryEntryPolicies)) throw new Error('Expected array');\n const seen = new Set<string>();\n temporaryEntryPolicies = contract.temporaryEntryPolicies.map((value) => {\n const policy = requireRecord(value, path);\n const entryKey = temporaryKey(requireString(policy.entryKey, path), id);\n if (\n typeof policy.autoExtend !== 'boolean' ||\n seen.has(entryKey) ||\n !dataKeys?.some((k) => typeof k === 'string' && k.trim() === entryKey)\n )\n throw new Error('Invalid or duplicate policy');\n seen.add(entryKey);\n return { entryKey, autoExtend: policy.autoExtend };\n });\n } catch {\n throw new ConfigError(\n `${path} requires unique declared temporary keys owned by this contract and explicit boolean autoExtend.`,\n );\n }\n }\n const noDataKeys = contract.noDataKeys as boolean | undefined;\n if (noDataKeys === true && dataKeys !== undefined && dataKeys.length > 0) {\n // The same contradiction the CLI rejects. Catching it at load time means\n // it fails once, at the boundary, rather than on every run.\n throw new ConfigError(\n `contracts[${index}] declares noDataKeys while also supplying dataKeys.\\n` +\n 'Those cannot both be true. Remove one.',\n );\n }\n const overrides =\n contract.thresholds === undefined\n ? undefined\n : parseThresholdFields(contract.thresholds, `contracts[${index}].thresholds`);\n validateThresholdPair(thresholds, overrides, `contracts[${index}].thresholds`, warnings);\n return {\n id,\n ...(label === undefined ? {} : { label }),\n payer,\n ...(dataKeys === undefined ? {} : { dataKeys }),\n ...(temporaryEntryPolicies === undefined ? {} : { temporaryEntryPolicies }),\n ...(noDataKeys === undefined ? {} : { noDataKeys }),\n ...(overrides === undefined ? {} : { thresholds: overrides }),\n };\n });\n\n // The decay-proof guard. `_doNotWatch` is documentation, so it cannot stop\n // anything by itself \u2014 but a contract sitting in BOTH lists is someone\n // half-way through the add/dry-run/confirm procedure, and that is exactly\n // when an unnoticed threshold mismatch destroys an unrepeatable proof.\n if (Array.isArray(root._doNotWatch)) {\n for (const entry of root._doNotWatch) {\n if (!isRecord(entry) || typeof entry.id !== 'string') continue;\n const watched = contracts.find((c) => c.id === entry.id);\n if (watched === undefined) continue;\n warnings.push(\n `\u26A0 ${watched.label ?? watched.id} is in BOTH \"contracts\" and \"_doNotWatch\".\\n` +\n ' It is listed as a deliberate natural-decay subject. The engine will act on it.\\n' +\n ' Verify the threshold in dry-run and confirm \"no action needed\" BEFORE going live \u2014\\n' +\n ' an early bump destroys ageing that cannot be recovered inside the sprint.\\n' +\n ' See docs/SETUP.md \u00A7 Putting B and C into the engine config.',\n );\n }\n }\n\n // Omission means dry-run. Live is explicit, never inferred.\n let mode: ExecutionMode = 'dry-run';\n if (root.mode !== undefined) {\n if (root.mode !== 'dry-run' && root.mode !== 'live') {\n throw new ConfigError('mode must be \"dry-run\" or \"live\" when present.');\n }\n mode = root.mode;\n if (mode === 'live') {\n warnings.push(\n '\u26A0 mode is \"live\": this config permits real transactions and real fees.\\n' +\n ' Dry-run is the default for a reason; confirm this is intended.',\n );\n }\n }\n\n let notifications: EvergreenConfig['notifications'];\n if (root.notifications !== undefined) {\n const value = requireRecord(root.notifications, 'notifications');\n for (const key of Object.keys(value)) {\n if (!['channel', 'toEnvVar'].includes(key) && !key.startsWith('_'))\n throw new ConfigError(`Unknown notifications field: ${key}`);\n }\n if (value.channel !== undefined && value.channel !== 'email')\n throw new ConfigError('notifications.channel must be email.');\n const toEnvVar = requireString(value.toEnvVar, 'notifications.toEnvVar');\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(toEnvVar))\n throw new ConfigError('notifications.toEnvVar must be an environment-variable name.');\n notifications = { channel: 'email', toEnvVar };\n }\n\n return {\n config: {\n network: { rpcUrl: requireString(network.rpcUrl, 'network.rpcUrl'), networkPassphrase },\n mode,\n defaults: thresholds,\n contracts,\n payers,\n ...(notifications === undefined ? {} : { notifications }),\n },\n warnings,\n };\n}\n", "import type { LedgerKey, RentEstimate, ScanResult, Stroops } from '@evergreen-stellar/shared-types';\nimport { hasExpired } from './ttl.js';\n\n/**\n * Rent estimation (`W2-D9-01`).\n *\n * **The price comes from the network, never from a local formula.** The\n * recorded fee fixture is explicit that three measurements do not determine how\n * rent scales with size \u2014 *\"do NOT fit a coefficient to this\"* \u2014 and the same\n * fixture records why: an earlier reading got the right NUMBER from the wrong\n * MECHANISM (2.8x, attributed to size, when byte-identical persistent and\n * temporary entries differ by 1.95x on durability alone). A wrong mechanism\n * that predicts the right number is worse than no mechanism, because it\n * survives casual checking.\n *\n * So this module does no pricing arithmetic of its own. It asks a `RentQuoter`\n * what the chain would charge and does the one thing that IS ours to get right:\n * summing per unique ledger key.\n */\n\n/** What extending one ledger key would cost. Priced by the network. */\nexport interface RentQuote {\n readonly entryKey: LedgerKey;\n readonly estimatedRentStroops: Stroops;\n}\n\n/**\n * The seam. Production asks the chain (`simulateTransaction`); tests use a\n * mock, so the model is verifiable without a network.\n */\nexport interface RentQuoter {\n quote(args: {\n readonly entryKeys: readonly LedgerKey[];\n readonly extendToLedgers: number;\n }): Promise<readonly RentQuote[]>;\n}\n\nexport interface RentEstimateResult {\n readonly estimate: RentEstimate;\n /** Keys deliberately left out, with the reason. Never silently dropped. */\n readonly excluded: readonly {\n readonly entryKey: LedgerKey;\n readonly reason: 'already-expired' | 'no-quote-returned';\n readonly detail: string;\n }[];\n}\n\nfunction assertStroops(value: string, entryKey: LedgerKey): Stroops {\n // Stroops is decimal text for lossless JSON. Validate rather than trust: a\n // malformed quote must not become a plausible-looking total.\n if (!/^\\d+$/.test(value)) {\n throw new Error(`Quote for ${entryKey} is not a non-negative integer of stroops: ${value}`);\n }\n return value as Stroops;\n}\n\n/**\n * Sum rent per UNIQUE ledger key.\n *\n * This is the whole correctness requirement. Contracts built from identical\n * Wasm share one `ContractCode` entry, so a per-contract sum charges it N times\n * \u2014 and it overcharges exactly the factory deployments most sensitive to cost,\n * silently, while looking arithmetically fine. `ScanResult.entries` is already\n * keyed by ledger key, so iterating IT rather than `contracts` is what makes\n * the double-count structurally impossible rather than merely avoided.\n *\n * Totals are summed as BigInt. Stroop values are decimal text precisely so\n * large sums stay lossless, and adding them as JS numbers would give that up\n * at the moment the number gets big enough to matter.\n */\nexport async function estimateRent(\n scan: ScanResult,\n args: {\n /**\n * One absolute target for every entry, or a per-entry map.\n *\n * **Per-entry is the correct shape for a delta request**, and getting this\n * wrong is not theoretical: a single `max` target across entries quoted the\n * shared code entry (690k remaining) up to 1.94M \u2014 a 1.25M-ledger\n * extension when 518k was asked for, and a bill of 2 XLM instead of 0.6.\n * Caught 2026-09-10 because the total was implausible, not because a test\n * failed. `extendTo` is absolute, so entries with different remaining TTL\n * need different targets to receive the same increment.\n */\n readonly extendToLedgers: number | Readonly<Record<LedgerKey, number>>;\n },\n quoter: RentQuoter,\n): Promise<RentEstimateResult> {\n const uniform = typeof args.extendToLedgers === 'number' ? args.extendToLedgers : undefined;\n const perEntry = typeof args.extendToLedgers === 'number' ? undefined : args.extendToLedgers;\n const targetFor = (entryKey: LedgerKey): number | undefined => uniform ?? perEntry?.[entryKey];\n if (uniform !== undefined && (!Number.isInteger(uniform) || uniform <= 0)) {\n throw new Error('extendToLedgers must be a positive integer of ledgers');\n }\n\n const excluded: {\n entryKey: LedgerKey;\n reason: 'already-expired' | 'no-quote-returned';\n detail: string;\n }[] = [];\n const quotable: LedgerKey[] = [];\n let estimatedAtLedger = 0;\n\n for (const [entryKey, entry] of Object.entries(scan.entries)) {\n estimatedAtLedger = Math.max(estimatedAtLedger, entry.observedAtLedger);\n if (entry.ttl.status === 'known' && hasExpired(entry.ttl.remainingLedgers)) {\n // `extendTTL` cannot reach an entry past its final live ledger. Quoting\n // one would produce a number for an operation that cannot be performed.\n excluded.push({\n entryKey,\n reason: 'already-expired',\n detail: 'Already past its final live ledger \u2014 needs RestoreFootprintOp, not an extend.',\n });\n continue;\n }\n quotable.push(entryKey);\n }\n\n // Group by target so entries sharing one are quoted together, and entries\n // with different targets are never blended into a single request.\n const byTarget = new Map<number, LedgerKey[]>();\n for (const entryKey of quotable) {\n const target = targetFor(entryKey);\n if (target === undefined || !Number.isInteger(target) || target <= 0) {\n throw new Error(`No positive extend target supplied for ${entryKey}`);\n }\n const group = byTarget.get(target);\n if (group) group.push(entryKey);\n else byTarget.set(target, [entryKey]);\n }\n const quotes: RentQuote[] = [];\n for (const [target, entryKeys] of byTarget) {\n quotes.push(...(await quoter.quote({ entryKeys, extendToLedgers: target })));\n }\n\n const byKey = new Map<LedgerKey, Stroops>();\n for (const quote of quotes) {\n if (!quotable.includes(quote.entryKey)) {\n throw new Error(`Quoter returned an unrequested entry: ${quote.entryKey}`);\n }\n if (byKey.has(quote.entryKey)) {\n throw new Error(`Quoter returned two prices for ${quote.entryKey}`);\n }\n byKey.set(quote.entryKey, assertStroops(quote.estimatedRentStroops, quote.entryKey));\n }\n\n const estimatedRentStroopsByEntry: Record<LedgerKey, Stroops> = {};\n let total = 0n;\n for (const entryKey of quotable) {\n const price = byKey.get(entryKey);\n if (price === undefined) {\n // A missing quote is not a zero. Report the gap; do not understate a bill.\n excluded.push({\n entryKey,\n reason: 'no-quote-returned',\n detail: 'The network returned no price for this entry; the total excludes it.',\n });\n continue;\n }\n estimatedRentStroopsByEntry[entryKey] = price;\n total += BigInt(price);\n }\n\n return {\n estimate: {\n estimatedAtLedger,\n // The shared type carries one number; report the largest target when\n // they differ, and the per-entry prices below are authoritative.\n extendToLedgers: uniform ?? Math.max(...byTarget.keys(), 0),\n estimatedRentStroopsByEntry,\n totalEstimatedRentStroops: total.toString() as Stroops,\n },\n excluded,\n };\n}\n\n/** Stroops are 1e-7 XLM. Display only \u2014 never store or sum the float. */\nexport function stroopsToXlm(stroops: Stroops): string {\n const value = BigInt(stroops);\n const whole = value / 10_000_000n;\n const fraction = (value % 10_000_000n).toString().padStart(7, '0');\n return `${whole.toString()}.${fraction}`;\n}\n", "import {\n Account,\n Keypair,\n Operation,\n SorobanDataBuilder,\n TransactionBuilder,\n rpc,\n xdr,\n} from '@stellar/stellar-sdk';\nimport type { LedgerKey, Stroops } from '@evergreen-stellar/shared-types';\nimport type { RentQuote, RentQuoter } from './rent.js';\n\n/**\n * The production `RentQuoter`: it asks the chain what an extend would cost\n * (`W2-D9-01`).\n *\n * **Simulation, never a local formula.** `simulateTransaction` prices a real\n * `ExtendFootprintTTLOp` against live network config. Reimplementing Soroban's\n * rent arithmetic here would mean inventing a mechanism, and the fee fixture is\n * explicit that our three measurements do not determine one \u2014 an earlier\n * reading got the right number from the wrong mechanism, which is worse than\n * no mechanism because it survives casual checking.\n *\n * **This never submits anything.** `simulateTransaction` is a read: no\n * signature, no sequence consumed, no chain state touched. The source account\n * is used for its public key only, which is why an unfunded or read-only\n * identity works.\n */\n\n/** `minResourceFee` bundles rent with a small non-refundable component. */\nexport interface QuoteBreakdown extends RentQuote {\n /** What simulation said the whole resource fee would be. */\n readonly minResourceFeeStroops: Stroops;\n /** The fixed cost that was cancelled out. Reported so the subtraction is visible. */\n readonly baselineFeeStroops: Stroops;\n}\n\nexport interface SimulatingQuoterOptions {\n /**\n * Optional. Simulation does not sign, does not consume a sequence number,\n * and \u2014 verified 2026-09-10 \u2014 **does not require the account to exist on\n * chain**: a freshly generated, never-funded key prices identically.\n *\n * So this defaults to a random synthetic key rather than a real identity.\n * The earlier version hardcoded our own testnet account, which a bundle\n * inspection found baked into the publishable artifact: not a secret, but it\n * put our account in every user's traffic and would have broken `--cost` for\n * everyone the day that account went away.\n */\n readonly sourceAccountId?: string;\n readonly networkPassphrase: string;\n}\n\n/**\n * A target any live entry already satisfies, so simulating it prices the\n * operation's FIXED cost with zero rent in it.\n */\nconst NO_OP_TARGET = 1;\n\nexport function createSimulatingQuoter(\n server: rpc.Server,\n options: SimulatingQuoterOptions,\n): RentQuoter & {\n quoteDetailed(args: {\n entryKeys: readonly LedgerKey[];\n extendToLedgers: number;\n }): Promise<readonly QuoteBreakdown[]>;\n} {\n // \u26A0\uFE0F SEAM: this is the QUOTING path. SUBMISSION is different and must stay\n // different.\n //\n // A simulation is never submitted, so its source account is a formality the\n // simulator does not check \u2014 verified 2026-09-10, a freshly generated key\n // prices identically to a real one. That is why the hardcoded account could\n // be deleted, and why no `getAccount` round trip happens here.\n //\n // **A real `extendTTL` needs a real account with its real sequence number.**\n // The submit path (`W2-D11-01`) must resolve one and must NOT be simplified\n // to match this function, however much the inconsistency looks like an\n // oversight. Doing so builds a transaction against sequence 0, which is\n // rejected \u2014 with an error about sequence numbers that nobody will connect\n // to a bundle-hygiene change made the day before.\n // One synthetic identity per quoter. Never signs, never funded, never fetched.\n const sourceAccountId = options.sourceAccountId ?? Keypair.random().publicKey();\n\n async function simulateFee(entryKey: LedgerKey, extendTo: number): Promise<bigint> {\n // Sequence number is irrelevant to a simulation that is never submitted, so\n // this skips a `getAccount` round trip per quote as well.\n const source = new Account(sourceAccountId, '0');\n const sorobanData = new SorobanDataBuilder()\n .setReadOnly([xdr.LedgerKey.fromXDR(entryKey, 'base64')])\n .build();\n const tx = new TransactionBuilder(source, {\n fee: '100',\n networkPassphrase: options.networkPassphrase,\n })\n .addOperation(Operation.extendFootprintTtl({ extendTo }))\n .setSorobanData(sorobanData)\n .setTimeout(30)\n .build();\n\n const simulated = await server.simulateTransaction(tx);\n if (rpc.Api.isSimulationError(simulated)) {\n throw new Error(`Simulation refused to price this entry: ${simulated.error}`);\n }\n return BigInt(simulated.minResourceFee ?? '0');\n }\n\n /**\n * Rent is the DIFFERENCE between two simulations of the same operation.\n *\n * `extendTo` is a target remaining TTL, not a delta \u2014 verified against the\n * chain 2026-09-10, where targets at or below an entry's current remaining\n * priced identically and only targets above it scaled. So simulating a\n * target the entry already satisfies gives the operation's fixed cost with\n * no rent in it, and subtracting that isolates rent EXACTLY.\n *\n * The earlier version subtracted a measured non-refundable constant instead,\n * and reported 9,349 stroops of \"rent\" for an extend that needed none. A\n * constant that is right for the transactions it was measured on is still a\n * fitted number \u2014 the same trap the fee fixture warns about. This needs no\n * constant at all: the fixed cost cancels, whatever it currently is.\n */\n async function quoteOne(entryKey: LedgerKey, extendToLedgers: number): Promise<QuoteBreakdown> {\n const baseline = await simulateFee(entryKey, NO_OP_TARGET);\n const atTarget = await simulateFee(entryKey, extendToLedgers);\n const rent = atTarget > baseline ? atTarget - baseline : 0n;\n return {\n entryKey,\n estimatedRentStroops: rent.toString() as Stroops,\n minResourceFeeStroops: atTarget.toString() as Stroops,\n baselineFeeStroops: baseline.toString() as Stroops,\n };\n }\n\n async function quoteDetailed(args: {\n entryKeys: readonly LedgerKey[];\n extendToLedgers: number;\n }): Promise<readonly QuoteBreakdown[]> {\n const out: QuoteBreakdown[] = [];\n // Sequential: each key is priced against its own footprint, and a batched\n // simulation would return one blended fee that cannot be attributed back.\n for (const entryKey of args.entryKeys) {\n out.push(await quoteOne(entryKey, args.extendToLedgers));\n }\n return out;\n }\n\n return {\n quoteDetailed,\n quote: (args) => quoteDetailed(args),\n };\n}\n", "import { Address, xdr } from '@stellar/stellar-sdk';\nimport type {\n ContractRef,\n LedgerEntryTTL,\n LedgerKey,\n ScanIssue,\n ScanResult,\n} from '@evergreen-stellar/shared-types';\nimport type { LedgerEntryReader } from './rpc.js';\nimport { codeKey, instanceKey } from './rpc.js';\nimport { observeTTL } from './ttl.js';\n\nconst MAX_KEYS_PER_READ = 200;\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction ledger(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 0xffff_ffff;\n}\n\n/**\n * Canonical base64 only: the text must survive a decode/encode round trip\n * unchanged. A lenient decoder silently \"repairs\" invalid characters or missing\n * padding, and a repaired key is a different key.\n *\n * `atob`/`btoa`, NOT Node's global `Buffer`. This used `Buffer` until\n * 2026-09-22, and `Buffer` does not exist in a browser: every key raised a\n * ReferenceError, was reported as \"RPC returned a malformed ledger key\", and a\n * scan of a live contract returned ZERO entries and a rent of \"0\" with no error\n * (#194, measured in headless Chrome 152). Every test ran on Node, where the\n * global is always present, so all of them passed while it shipped.\n * `test/browser-no-buffer.test.ts` now runs without it.\n */\nfunction isCanonicalBase64(text: string): boolean {\n try {\n return btoa(atob(text)) === text;\n } catch {\n return false;\n }\n}\n\nfunction parseKey(value: unknown): xdr.LedgerKey {\n if (typeof value !== 'string') throw new Error('Invalid ledger key');\n const text = value.trim();\n if (!text || !isCanonicalBase64(text)) throw new Error('Invalid ledger key');\n const key = xdr.LedgerKey.fromXDR(text, 'base64');\n if (key.toXDR('base64') !== text) throw new Error('Invalid ledger key');\n return key;\n}\n\nfunction payloadKey(value: xdr.LedgerEntryData): LedgerKey {\n if (value.type === 'contractData') {\n const data = value.contractData;\n return xdr.LedgerKey.contractData(\n new xdr.LedgerKeyContractData({\n contract: data.contract,\n key: data.key,\n durability: data.durability,\n }),\n ).toXDR('base64');\n }\n if (value.type === 'contractCode') return codeKey(value.contractCode.hash.value);\n throw new Error('Unsupported entry type');\n}\n\n/** Explicit known-key scope for one contract; never a storage enumeration. */\nexport interface ContractScanRequest {\n readonly contract: ContractRef;\n readonly dataKeys?: readonly LedgerKey[];\n readonly noDataKeys?: boolean;\n}\n\ntype ExpectedEntry = {\n readonly kind: LedgerEntryTTL['kind'];\n readonly contracts: string[];\n};\n\n/** Preserve the single-contract API on the same validated multi-contract path. */\nexport function scanContract(\n reader: LedgerEntryReader,\n contract: ContractRef,\n dataKeys: readonly LedgerKey[] = [],\n options: { readonly noDataKeys?: boolean } = {},\n): Promise<ScanResult> {\n return scanContracts(reader, [{ contract, dataKeys, ...options }]);\n}\n\n/** Read each canonical key once, preserving all known input consumers. */\nexport async function scanContracts(\n reader: LedgerEntryReader,\n requests: readonly ContractScanRequest[],\n): Promise<ScanResult> {\n const entries: Record<LedgerKey, LedgerEntryTTL> = {};\n const issues: ScanIssue[] = [];\n const supplied: Record<string, number> = {};\n const declarations: Record<string, boolean> = {};\n const contracts = new Map<string, ContractRef>();\n const result: ScanResult = {\n network: 'testnet',\n contracts: [],\n entries,\n issues,\n coverage: { mode: 'known-keys', dataKeysSuppliedByContract: supplied },\n };\n function issue(\n kind: ScanIssue['kind'],\n message: string,\n consumers: readonly string[],\n key?: string,\n observedAtLedger?: number,\n ): void {\n issues.push({\n kind,\n message,\n contracts: [...new Set(consumers)],\n ...(key === undefined ? {} : { entryKey: key }),\n ...(observedAtLedger === undefined ? {} : { observedAtLedger }),\n });\n }\n\n const groups = new Map<\n string,\n {\n instance: LedgerKey;\n data: Map<LedgerKey, LedgerEntryTTL['kind']>;\n hasData: boolean;\n noDataKeys: boolean;\n invalid: boolean;\n }\n >();\n if (!Array.isArray(requests)) {\n issue('invalid-response', 'Scan requests must be an array.', []);\n return result;\n }\n for (const request of requests) {\n if (!object(request) || !object(request.contract) || typeof request.contract.id !== 'string') {\n issue('invalid-response', 'Each scan request must contain a contract ID.', []);\n continue;\n }\n const { contract } = request;\n const id = request.contract.id;\n const previous = contracts.get(id);\n const label = typeof contract.label === 'string' ? contract.label : undefined;\n contracts.set(id, {\n id,\n ...(previous?.label !== undefined\n ? { label: previous.label }\n : label !== undefined\n ? { label }\n : {}),\n });\n let instance: LedgerKey;\n try {\n instance = instanceKey(id);\n } catch {\n issue('invalid-response', 'Invalid contract ID. Expected a Stellar contract address.', [id]);\n continue;\n }\n let group = groups.get(id);\n if (!group) {\n group = { instance, data: new Map(), hasData: false, noDataKeys: false, invalid: false };\n groups.set(id, group);\n }\n if (request.noDataKeys !== undefined && typeof request.noDataKeys !== 'boolean') {\n issue('invalid-response', 'noDataKeys must be a boolean caller assertion.', [id]);\n group.invalid = true;\n }\n group.noDataKeys ||= request.noDataKeys === true;\n const dataKeys = request.dataKeys === undefined ? [] : request.dataKeys;\n if (!Array.isArray(dataKeys)) {\n issue('invalid-response', 'dataKeys must be an array of serialized LedgerKeys.', [id]);\n group.invalid = true;\n continue;\n }\n group.hasData ||= dataKeys.length > 0;\n for (const input of dataKeys) {\n try {\n const key = parseKey(input);\n if (\n key.type !== 'contractData' ||\n key.contractData.key.type === 'scvLedgerKeyContractInstance' ||\n Address.fromScAddress(key.contractData.contract).toString() !== id\n )\n throw new Error('Wrong data key');\n const durability = key.contractData.durability.name;\n if (durability !== 'persistent' && durability !== 'temporary')\n throw new Error('Wrong durability');\n group.data.set(key.toXDR('base64'), durability);\n } catch {\n issue(\n 'invalid-response',\n 'Invalid data key: expected persistent/temporary ContractData for this contract.',\n [id],\n );\n }\n }\n }\n\n const instances = new Map<LedgerKey, ExpectedEntry>();\n const data = new Map<LedgerKey, ExpectedEntry>();\n function expectEntry(\n target: Map<LedgerKey, ExpectedEntry>,\n key: LedgerKey,\n kind: LedgerEntryTTL['kind'],\n id: string,\n ): void {\n const existing = target.get(key);\n if (!existing) target.set(key, { kind, contracts: [id] });\n else if (!existing.contracts.includes(id)) existing.contracts.push(id);\n }\n for (const [id, group] of groups) {\n supplied[id] = group.data.size;\n if (group.noDataKeys) declarations[id] = true;\n if (group.noDataKeys && group.hasData) {\n issue(\n 'invalid-response',\n 'Cannot declare no data keys while supplying data keys for the same contract.',\n [id],\n );\n group.invalid = true;\n }\n if (group.invalid) continue;\n expectEntry(instances, group.instance, 'instance', id);\n for (const [key, kind] of group.data) expectEntry(data, key, kind, id);\n }\n\n async function read(\n expected: ReadonlyMap<LedgerKey, ExpectedEntry>,\n ): Promise<Map<LedgerKey, xdr.LedgerEntryData>> {\n const decoded = new Map<LedgerKey, xdr.LedgerEntryData>();\n const keys = [...expected.keys()];\n for (let start = 0; start < keys.length; start += MAX_KEYS_PER_READ) {\n const batch = keys.slice(start, start + MAX_KEYS_PER_READ);\n const consumers = [...new Set(batch.flatMap((key) => expected.get(key)!.contracts))];\n let response: unknown;\n try {\n response = await reader.read(batch);\n } catch {\n issue(\n 'rpc-error',\n 'RPC read failed for a batch; retry the scan. Successful batches are retained.',\n consumers,\n );\n continue;\n }\n if (!object(response) || !ledger(response.latestLedger) || !Array.isArray(response.entries)) {\n issue(\n 'invalid-response',\n 'RPC response must contain a valid latestLedger and entries array.',\n consumers,\n );\n continue;\n }\n const observedAtLedger = response.latestLedger;\n const seen = new Set<LedgerKey>();\n const requested = new Set(batch);\n for (const row of response.entries as unknown[]) {\n let key: string;\n try {\n if (!object(row)) throw new Error('Invalid row');\n key = parseKey(row.key).toXDR('base64');\n } catch {\n issue(\n 'invalid-response',\n 'RPC returned a malformed ledger key.',\n consumers,\n undefined,\n observedAtLedger,\n );\n continue;\n }\n if (!requested.has(key)) {\n issue(\n 'invalid-response',\n 'RPC returned an unrequested entry.',\n consumers,\n undefined,\n observedAtLedger,\n );\n continue;\n }\n if (seen.has(key)) {\n // Do not keep either of two contradictory observations of one requested key.\n delete entries[key];\n decoded.delete(key);\n issue(\n 'invalid-response',\n 'RPC returned a duplicate entry; its observation was discarded.',\n expected.get(key)!.contracts,\n key,\n observedAtLedger,\n );\n continue;\n }\n seen.add(key);\n try {\n if (\n !object(row) ||\n typeof row.entryXdr !== 'string' ||\n (row.liveUntilLedgerSeq !== undefined && !ledger(row.liveUntilLedgerSeq))\n )\n throw new Error('Invalid row');\n const value = xdr.LedgerEntryData.fromXDR(row.entryXdr, 'base64');\n if (value.toXDR('base64') !== row.entryXdr || payloadKey(value) !== key)\n throw new Error('Mismatched payload');\n const kind = expected.get(key)?.kind;\n if (kind === undefined) throw new Error('Unexpected key');\n if (\n kind === 'instance' &&\n (value.type !== 'contractData' || value.contractData.val.type !== 'scvContractInstance')\n ) {\n throw new Error('Invalid instance payload');\n }\n const lifecycle =\n kind === 'temporary'\n ? { kind, endBehavior: 'deleted' as const }\n : {\n kind,\n endBehavior: 'archived' as const,\n };\n entries[key] = {\n ...lifecycle,\n contracts: [...expected.get(key)!.contracts],\n observedAtLedger,\n ttl: observeTTL({ liveUntilLedgerSeq: row.liveUntilLedgerSeq, observedAtLedger }),\n };\n decoded.set(key, value);\n } catch {\n issue(\n 'invalid-response',\n 'RPC entry payload or TTL is invalid or does not match its requested key.',\n expected.get(key)!.contracts,\n key,\n observedAtLedger,\n );\n }\n }\n for (const key of batch) {\n if (!seen.has(key))\n issue(\n 'entry-not-found',\n 'No entry returned. Absence is not proof of archival or deletion.',\n expected.get(key)!.contracts,\n key,\n observedAtLedger,\n );\n }\n }\n return decoded;\n }\n\n const observed = await read(instances);\n for (const [instance] of instances) {\n const instanceValue = observed.get(instance);\n if (!instanceValue) continue;\n if (\n instanceValue.type !== 'contractData' ||\n instanceValue.contractData.val.type !== 'scvContractInstance'\n )\n continue;\n const executable = instanceValue.contractData.val.instance.executable;\n const consumers = instances.get(instance)!.contracts;\n if (executable.type === 'contractExecutableWasm') {\n for (const id of consumers) expectEntry(data, codeKey(executable.wasmHash.value), 'code', id);\n } else {\n issue(\n 'unsupported-executable',\n 'Instance has a non-Wasm executable; this scanner does not discover its code.',\n consumers,\n instance,\n entries[instance]?.observedAtLedger,\n );\n }\n }\n await read(data);\n return {\n ...result,\n contracts: [...contracts.values()],\n coverage: {\n mode: 'known-keys',\n dataKeysSuppliedByContract: supplied,\n ...(Object.keys(declarations).length ? { noDataKeysDeclaredByContract: declarations } : {}),\n },\n };\n}\n", "import {\n Account,\n Networks,\n Operation,\n SorobanDataBuilder,\n StrKey,\n Transaction,\n TransactionBuilder,\n rpc,\n xdr,\n} from '@stellar/stellar-sdk';\nimport type { PlannedExtension } from './extend.js';\nimport { extensionKey } from './extend.js';\nimport { validateExtensionEnvelope } from './ed25519-signer.js';\n\nexport interface ExtensionRpc {\n getNetwork(): Promise<{ passphrase: string }>;\n getAccount(address: string): Promise<Account>;\n simulateTransaction(tx: Transaction): Promise<rpc.Api.SimulateTransactionResponse>;\n sendTransaction(tx: Transaction): Promise<{ status: string; hash: string }>;\n getTransaction(hash: string): Promise<{\n status: string;\n txHash?: string;\n ledger?: number;\n envelopeXdr?: xdr.TransactionEnvelope;\n }>;\n}\nexport interface PreparedExtension {\n readonly entry: PlannedExtension;\n readonly sourceAccount: string;\n readonly transactionXdr: string;\n readonly transactionHash: string;\n readonly feeStroops: string;\n readonly simulatedAtLedger: number;\n}\n\nexport async function prepareExtension(\n server: ExtensionRpc,\n entry: PlannedExtension,\n sourceAccount: string,\n): Promise<PreparedExtension> {\n if (!StrKey.isValidEd25519PublicKey(sourceAccount) || entry.skip)\n throw new Error('Invalid extension payer or no-op');\n if ((await server.getNetwork()).passphrase !== Networks.TESTNET)\n throw new Error('RPC is not Stellar Testnet');\n const account = await server.getAccount(sourceAccount);\n if (account.accountId() !== sourceAccount) throw new Error('RPC returned another payer');\n const tx = new TransactionBuilder(new Account(sourceAccount, account.sequenceNumber()), {\n fee: '100',\n networkPassphrase: Networks.TESTNET,\n })\n .addOperation(Operation.extendFootprintTtl({ extendTo: entry.extendToLedgers }))\n .setSorobanData(new SorobanDataBuilder().setReadOnly([extensionKey(entry.entryKey)]).build())\n .setTimeout(60)\n .build();\n const simulation = await server.simulateTransaction(tx);\n if (\n !rpc.Api.isSimulationSuccess(simulation) ||\n 'restorePreamble' in simulation ||\n typeof simulation.minResourceFee !== 'string' ||\n !/^\\d+$/.test(simulation.minResourceFee) ||\n !Number.isSafeInteger(simulation.latestLedger) ||\n simulation.latestLedger < entry.before.observedAtLedger ||\n simulation.latestLedger > entry.before.endsAtLedger ||\n simulation.transactionData.build().resourceFee !== BigInt(simulation.minResourceFee)\n )\n throw new Error('Extension simulation failed or returned invalid resources');\n const prepared = rpc.assembleTransaction(tx, simulation).build();\n const transactionXdr = prepared.toXDR();\n const transactionHash = Buffer.from(prepared.hash()).toString('hex');\n validateExtensionEnvelope(transactionXdr, {\n sourceAccount,\n entryKey: entry.entryKey,\n extendToLedgers: entry.extendToLedgers,\n expectedHash: transactionHash,\n maxFeeStroops: prepared.fee,\n });\n return {\n entry,\n sourceAccount,\n transactionXdr,\n transactionHash,\n feeStroops: prepared.fee,\n simulatedAtLedger: simulation.latestLedger,\n };\n}\n\nexport type ExtensionConfirmation =\n | { readonly status: 'unconfirmed' }\n | { readonly status: 'failed' }\n | { readonly status: 'confirmed'; readonly ledger: number };\n\nexport async function confirmExtension(\n server: Pick<ExtensionRpc, 'getTransaction'>,\n hash: string,\n options: {\n readonly attempts?: number;\n readonly sleep?: () => Promise<void>;\n } = {},\n): Promise<ExtensionConfirmation> {\n const attempts = options.attempts ?? 12;\n if (!Number.isSafeInteger(attempts) || attempts < 1 || attempts > 60)\n throw new Error('Invalid confirmation bound');\n for (let i = 0; i < attempts; i++) {\n const response = await server.getTransaction(hash);\n if (response.txHash !== hash) throw new Error('Transaction confirmation hash mismatch');\n if (response.status === 'SUCCESS' || response.status === 'FAILED') {\n // SDK 17 getTransaction() fills txHash from the request, not the response.\n // Bind the result to the returned envelope rather than accepting that echo.\n if (!response.envelopeXdr) throw new Error('Missing transaction confirmation envelope');\n const transaction = TransactionBuilder.fromXDR(\n response.envelopeXdr.toXDR('base64'),\n Networks.TESTNET,\n );\n if (Buffer.from(transaction.hash()).toString('hex') !== hash)\n throw new Error('Transaction confirmation envelope hash mismatch');\n }\n if (response.status === 'FAILED') return { status: 'failed' };\n if (response.status === 'SUCCESS') {\n if (!Number.isSafeInteger(response.ledger) || response.ledger! <= 0)\n throw new Error('Invalid inclusion ledger');\n return { status: 'confirmed', ledger: response.ledger! };\n }\n if (response.status !== 'NOT_FOUND') throw new Error('Unknown transaction status');\n if (i + 1 < attempts)\n await (options.sleep ?? (() => new Promise<void>((resolve) => setTimeout(resolve, 1000))))();\n }\n return { status: 'unconfirmed' };\n}\n\nexport async function submitExtension(\n server: ExtensionRpc,\n prepared: PreparedExtension,\n signedXdr: string,\n): Promise<{ status: string; hash: string }> {\n const signed = TransactionBuilder.fromXDR(signedXdr, Networks.TESTNET);\n if (\n !(signed instanceof Transaction) ||\n signed.source !== prepared.sourceAccount ||\n Buffer.from(signed.hash()).toString('hex') !== prepared.transactionHash ||\n signed.signatures.length !== 1\n )\n throw new Error('Signed envelope differs from the prepared transaction');\n if ((await server.getNetwork()).passphrase !== Networks.TESTNET)\n throw new Error('RPC is not Stellar Testnet');\n const response = await server.sendTransaction(signed);\n if (response.hash !== prepared.transactionHash) throw new Error('Submission hash mismatch');\n return response;\n}\n", "/** Small published evidence snapshot, included in the package, never loaded from test files. */\nexport const STORAGE_ADVICE_EVIDENCE = {\n rent: {\n id: 'guinea-pig-a-2026-09-09',\n recordedAt: '2026-09-09T10:58:52Z',\n source:\n 'https://github.com/Fatihmaull/evergreen/blob/main/packages/core/test/fixtures/extendTTL-fees-guinea-pig-a.json',\n persistent: {\n rentStroops: '103849',\n totalFeeStroops: '106308',\n dataBytes: 88,\n keyBytes: 76,\n ledgersExtended: 1312937,\n },\n temporary: {\n rentStroops: '53196',\n totalFeeStroops: '55655',\n dataBytes: 88,\n keyBytes: 76,\n ledgersExtended: 1312939,\n },\n qualification:\n 'Historical measured rent at equal encoded sizes over durations differing by two ledgers; not a quote or savings forecast for this entry.',\n },\n settings: {\n recordedOn: '2026-09-05',\n observedAtLedger: 4519665,\n minTemporaryTtl: 720,\n minPersistentTtl: 120960,\n source:\n 'https://github.com/Fatihmaull/evergreen/blob/main/docs/evidence/2026-09-05-ttl-boundary/state-archival-settings.json',\n },\n deletion: {\n recordedOn: '2026-09-06',\n subject: 'isolated temporary-entry experiment',\n lastLiveLedger: 4529810,\n firstAbsentLedger: 4529811,\n source:\n 'https://github.com/Fatihmaull/evergreen/blob/main/docs/evidence/2026-09-06-ttl-boundary/README.md',\n },\n} as const;\n", "import type { ScanResult, Stroops, TTLObservation } from '@evergreen-stellar/shared-types';\nimport { STORAGE_ADVICE_EVIDENCE } from './optimizer-evidence.js';\n\nexport interface StorageSettings {\n readonly minTemporaryTtl: number;\n readonly minPersistentTtl: number;\n readonly observedAtLedger: number;\n}\nexport interface StorageAdviceContext {\n readonly settings?: StorageSettings;\n readonly quote?: {\n readonly rentByEntry: Readonly<Record<string, Stroops>>;\n readonly pricedAtLedger: number;\n readonly additionalLedgers: number;\n };\n}\nexport interface StorageAdvice {\n readonly code: 'temporary-retention' | 'durability-review' | 'shared-code-dependency';\n readonly entryKey: string;\n readonly knownConsumers: readonly string[];\n readonly observedAtLedger: number;\n readonly ttl: TTLObservation;\n readonly action: string;\n readonly rationale: string;\n readonly benchmarkId?: 'guinea-pig-a-2026-09-09';\n readonly currentRent:\n { readonly status: 'unavailable' } | { readonly status: 'quoted'; readonly stroops: Stroops };\n}\nexport interface StorageAdviceReport {\n readonly scope: 'observed-keys-only';\n readonly context: StorageAdviceContext;\n readonly findings: readonly StorageAdvice[];\n readonly limitations: readonly string[];\n readonly evidence: typeof STORAGE_ADVICE_EVIDENCE;\n}\nfunction ledger(value: number): boolean {\n return Number.isSafeInteger(value) && value >= 0 && value <= 0xffff_ffff;\n}\nfunction lifetime(value: number): boolean {\n return ledger(value) && value > 0;\n}\n\n/** Advice about design choices, not another health grade or an instruction to transact. */\nexport function analyzeStorage(\n scan: ScanResult,\n context: StorageAdviceContext = {},\n): StorageAdviceReport {\n if (scan.network !== 'testnet') throw new Error('Storage advice requires Testnet observations');\n const limitations = [\n 'Only observed keys were analyzed; storage was not enumerated. No size, duplicate-content or global-consumer inference is supported.',\n 'Advice is conditional on application requirements; it does not authorize a transaction or change scan health exits.',\n ];\n const settings = context.settings;\n const validSettings =\n settings != null &&\n lifetime(settings.minTemporaryTtl) &&\n lifetime(settings.minPersistentTtl) &&\n ledger(settings.observedAtLedger);\n if (!validSettings)\n limitations.push(\n \"Current minimum lifetimes are unavailable. Historical settings below are a dated reference, not current configuration or this entry's expiry.\",\n );\n const quote = context.quote;\n const validQuote =\n quote != null &&\n typeof quote.rentByEntry === 'object' &&\n quote.rentByEntry !== null &&\n !Array.isArray(quote.rentByEntry) &&\n ledger(quote.pricedAtLedger) &&\n Number.isSafeInteger(quote.additionalLedgers) &&\n quote.additionalLedgers > 0;\n const rents: Record<string, Stroops> = {};\n if (validQuote) {\n for (const [key, entry] of Object.entries(scan.entries)) {\n const amount = quote.rentByEntry[key];\n if (\n typeof amount === 'string' &&\n /^(0|[1-9]\\d*)$/.test(amount) &&\n quote.pricedAtLedger >= entry.observedAtLedger\n )\n rents[key] = amount;\n }\n }\n const cleanContext: StorageAdviceContext = {\n ...(validSettings\n ? {\n settings: {\n minTemporaryTtl: settings.minTemporaryTtl,\n minPersistentTtl: settings.minPersistentTtl,\n observedAtLedger: settings.observedAtLedger,\n },\n }\n : {}),\n ...(validQuote\n ? {\n quote: {\n rentByEntry: rents,\n pricedAtLedger: quote.pricedAtLedger,\n additionalLedgers: quote.additionalLedgers,\n },\n }\n : {}),\n };\n const findings: StorageAdvice[] = [];\n // Coverage/sharing caveats describe successful reads; they are not unreadable entries.\n const readIssues = scan.issues.filter(\n (i) => i.kind !== 'coverage-limited' && i.kind !== 'sharing-undetermined',\n );\n let unreadable = readIssues.length > 0;\n for (const [entryKey, entry] of Object.entries(scan.entries)) {\n if (\n entry.ttl.status !== 'known' ||\n !ledger(entry.observedAtLedger) ||\n !ledger(entry.ttl.endsAtLedger) ||\n !Number.isSafeInteger(entry.ttl.remainingLedgers) ||\n entry.ttl.remainingLedgers !== entry.ttl.endsAtLedger - entry.observedAtLedger ||\n readIssues.some((i) => i.entryKey === entryKey)\n ) {\n unreadable = true;\n continue;\n }\n if (entry.kind === 'instance') continue;\n const base = {\n entryKey,\n knownConsumers: [...new Set(entry.contracts)],\n observedAtLedger: entry.observedAtLedger,\n ttl: { ...entry.ttl },\n currentRent:\n rents[entryKey] === undefined\n ? { status: 'unavailable' as const }\n : { status: 'quoted' as const, stroops: rents[entryKey] },\n };\n if (entry.kind === 'code') {\n findings.push({\n ...base,\n code: 'shared-code-dependency',\n action:\n 'Monitor this code key once with its known consumers; checking their instances alone does not establish protection.',\n rationale:\n 'Every contract built from the same Wasm uses this code entry. Other consumers may exist outside this scan; sharing is not duplicated storage or a measured saving.',\n });\n } else if (entry.kind === 'temporary') {\n findings.push({\n ...base,\n code: 'temporary-retention',\n benchmarkId: 'guinea-pig-a-2026-09-09',\n action:\n 'Compare the observed TTL with intended retention. If data must survive expiry, evaluate persistent storage in contract source; otherwise permit deliberate expiry.',\n rationale:\n \"Temporary state is deleted, not archived or restorable. The isolated 2026-09-06 experiment observed the final live ledger and absence at the next ledger; it does not measure this entry's deletion.\",\n });\n } else if (entry.kind === 'persistent') {\n findings.push({\n ...base,\n code: 'durability-review',\n benchmarkId: 'guinea-pig-a-2026-09-09',\n action:\n 'Only if this data is disposable or recomputable, evaluate temporary storage in contract source. Keep balances, required configuration and durable state persistent.',\n rationale:\n 'Historical A persistent rent was about 1.95 times the temporary rent at equal encoded sizes. This is a measured comparison, not guaranteed savings or evidence of equal contents.',\n });\n }\n }\n if (unreadable)\n limitations.push(\n 'Some requested entries are missing or unreadable; they receive no inferred retention or cost advice.',\n );\n if (findings.some((f) => f.currentRent.status === 'unavailable'))\n limitations.push(\n 'Current rent is unavailable for some findings. Use --cost for quotes where supported; historical benchmark amounts are not per-entry quotes.',\n );\n if (findings.length === 0)\n limitations.push(\n 'No supported recommendation for these observations; this does not mean the contract is fully optimized.',\n );\n return {\n scope: 'observed-keys-only',\n context: cleanContext,\n findings,\n limitations,\n evidence: STORAGE_ADVICE_EVIDENCE,\n };\n}\n", "import { temporaryConsent } from './temporary-policy.js';\nimport { Address } from '@stellar/stellar-sdk';\nimport type { BumpDecision, EvergreenConfig, ScanResult } from '@evergreen-stellar/shared-types';\nimport { extensionKey } from './extend.js';\nimport type { PlannedExtension } from './extend.js';\nimport { hasExpired, needsAction } from './ttl.js';\nimport { resolveHealthThresholds } from './health.js';\nimport { assertWriteAllowed, ProtectedEntryError } from './write-guard.js';\n\nexport interface EngineExecutionEntry {\n readonly payer: string;\n readonly entry: PlannedExtension;\n}\nexport interface EngineExecutionSelection {\n readonly entries: readonly EngineExecutionEntry[];\n readonly decisions: readonly BumpDecision[];\n}\n\n/** Exact-key adapter. It never adds the manual planner's implicit instance. */\nexport function planEngineExecution(\n scan: ScanResult,\n decisions: readonly BumpDecision[],\n config: EvergreenConfig,\n): EngineExecutionSelection {\n if (scan.network !== 'testnet') throw new Error('Execution requires Testnet observations');\n if (new Set(decisions.map((d) => d.entryKey)).size !== decisions.length)\n throw new Error('Duplicate execution decisions');\n const entries: EngineExecutionEntry[] = [];\n const selected: BumpDecision[] = [];\n for (const decision of decisions) {\n if (decision.action === 'skip') {\n selected.push(decision);\n continue;\n }\n const entry = scan.entries[decision.entryKey];\n if (!entry) throw new Error('Selected entry is missing or unreadable');\n const skip = (reason: string): void => {\n selected.push({\n action: 'skip',\n entryKey: decision.entryKey,\n contracts: entry.contracts,\n reason,\n });\n };\n if (entry.kind !== 'instance' && entry.kind !== 'persistent' && entry.kind !== 'temporary') {\n skip(`Execution scope excludes ${entry.kind} entries in D16-01.`);\n continue;\n }\n const key = extensionKey(decision.entryKey);\n if (key.type !== 'contractData') throw new Error('Selected key kind mismatch');\n const owner = Address.fromScAddress(key.contractData.contract).toString();\n const kind =\n key.contractData.key.type === 'scvLedgerKeyContractInstance'\n ? 'instance'\n : key.contractData.durability.name === 'temporary'\n ? 'temporary'\n : 'persistent';\n if (\n (kind === 'instance' && key.contractData.durability.name !== 'persistent') ||\n kind !== entry.kind ||\n entry.contracts.length !== 1 ||\n entry.contracts[0] !== owner ||\n decision.contracts.length !== 1 ||\n decision.contracts[0] !== owner\n ) {\n throw new Error('Selected key kind or consumer mismatch');\n }\n try {\n assertWriteAllowed({ contractId: owner, entryKeys: [decision.entryKey], scan });\n } catch (error) {\n if (!(error instanceof ProtectedEntryError)) throw error;\n skip(`REFUSED BY WRITE GUARD \u2014 ${error.message.split('\\n')[0]}`);\n continue;\n }\n if (kind === 'temporary') {\n const consent = temporaryConsent(config, decision.entryKey, owner);\n if (!consent.allowed) {\n skip(consent.reason);\n continue;\n }\n }\n const registrations = config.contracts.filter((c) => c.id === owner);\n if (\n !registrations.length ||\n !Object.hasOwn(config.payers, decision.payer) ||\n registrations.some((c) => c.payer !== decision.payer)\n )\n throw new Error('Selected payer does not match config');\n if (\n kind === 'persistent' &&\n !registrations.some((c) => c.dataKeys?.some((k) => k.trim() === decision.entryKey))\n ) {\n throw new Error('Persistent execution key was not declared');\n }\n if (\n entry.ttl.status !== 'known' ||\n scan.issues.some(\n (i) =>\n i.kind !== 'coverage-limited' &&\n i.kind !== 'sharing-undetermined' &&\n (i.entryKey === decision.entryKey || (!i.entryKey && i.contracts.includes(owner))),\n )\n ) {\n throw new Error('Selected entry is unreadable');\n }\n const { remainingLedgers, endsAtLedger } = entry.ttl;\n if (\n !Number.isSafeInteger(entry.observedAtLedger) ||\n entry.observedAtLedger < 0 ||\n !Number.isSafeInteger(endsAtLedger) ||\n endsAtLedger > 0xffff_ffff ||\n remainingLedgers !== endsAtLedger - entry.observedAtLedger ||\n hasExpired(remainingLedgers)\n ) {\n throw new Error('Selected entry is not valid live state');\n }\n const action = Math.max(\n ...registrations.map(\n (c) => resolveHealthThresholds(config.defaults, c.thresholds).criticalBelowLedgers,\n ),\n );\n const targets = registrations.map(\n (c) => c.thresholds?.extendToLedgers ?? config.defaults.extendToLedgers,\n );\n if (\n targets.some((t) => t !== targets[0]) ||\n !needsAction(remainingLedgers, action) ||\n !Number.isSafeInteger(decision.extendToLedgers) ||\n decision.extendToLedgers <= 0 ||\n decision.extendToLedgers > 0xffff_ffff ||\n decision.extendToLedgers > targets[0]! ||\n needsAction(decision.extendToLedgers, remainingLedgers) ||\n needsAction(decision.extendToLedgers, action)\n ) {\n throw new Error('Selected target does not match the action policy');\n }\n entries.push({\n payer: decision.payer,\n entry: {\n entryKey: decision.entryKey,\n kind: entry.kind,\n contracts: entry.contracts,\n before: { observedAtLedger: entry.observedAtLedger, endsAtLedger },\n extendToLedgers: decision.extendToLedgers,\n wasCapped: decision.extendToLedgers < targets[0]!,\n skip: false,\n },\n });\n selected.push(decision);\n }\n return { entries, decisions: selected };\n}\n", "import {\n isValidContractId,\n isValidPayerAccount,\n ProtectedEntryError,\n} from '@evergreen-stellar/core';\nimport type {\n ExtensionExecutionResult,\n ExtensionPlan,\n PreparedExtension,\n} from '@evergreen-stellar/core';\nimport type { CliOutput } from './command.js';\n\nexport interface ExtendRequest {\n readonly contractId: string;\n readonly additionalLedgers: number;\n readonly sourceAccount: string;\n readonly dataKeys: readonly string[];\n readonly includeCode: boolean;\n readonly submit: boolean;\n readonly secretEnv?: string;\n readonly maxFeeStroops?: string;\n}\nexport interface ExtendReport {\n readonly plan: ExtensionPlan;\n readonly result: ExtensionExecutionResult;\n readonly previews: readonly PreparedExtension[];\n}\nexport interface ExtendCliDependencies {\n readonly sourceAccount?: string;\n readKeysFile(path: string): Promise<string>;\n run(request: ExtendRequest, preview: (text: string) => void): Promise<ExtendReport>;\n /** bin.ts prints this to stderr BEFORE any signature; JSON stdout stays valid. */\n preview?(text: string): void;\n}\n\nexport const EXTEND_HELP = `usage: evergreen extend <contract-id> --ledgers N\n [--source-account G...] [--keys-file path] [--include-code] [--json]\n [--submit --secret-env NAME --max-fee-stroops N]\n--dry-run simulate only and say so. This is already the default; the flag\n exists so a script can state its own safety rather than rely on\n an absence. Mutually exclusive with --submit.\n\nTestnet only. Default: simulate, never sign or submit. Supply a public payer\nwith --source-account or EVERGREEN_SOURCE_ACCOUNT; there is no fallback payer.\n--ledgers N adds N ledgers to each selected entry's current remaining TTL.\nThe operation target is capped at max_entry_ttl - 1; capping is reported.\nSelects instance by default. A keys file adds explicit data keys:\n{ \"dataKeys\": [\"base64 XDR LedgerKey\", ...] }. Storage is not enumerated.\n--include-code explicitly includes Wasm shared with potentially unseen consumers.\n--submit requires an exported secret variable NAME and an aggregate fee cap in\ninteger stroops. Never put the secret itself in arguments. No .env auto-loading.\nNo automatic restore or funding. No replacement send after uncertain results.\nExit 0: complete simulation, no-op, or verified live result; 2: error or partial/\nunconfirmed result. Simulation success does not mean TTL changed.`;\n\nexport function extensionPreview(p: PreparedExtension): string {\n return (\n `Selected ${p.entry.kind} ${p.entry.entryKey}\\nKnown consumers: ${p.entry.contracts.join(', ')}\\n` +\n `Payer: ${p.sourceAccount}\\nPrepared hash (not yet sent): ${p.transactionHash}\\nBefore: ledger ${p.entry.before.observedAtLedger}, live until ${p.entry.before.endsAtLedger}\\n` +\n `Target remaining: ${p.entry.extendToLedgers}${p.entry.wasCapped ? ' (CAPPED)' : ''}; prepared fee cap: ${p.feeStroops} stroops\\n` +\n 'Selected scope only. Code may have consumers outside this scan.'\n );\n}\n\nexport async function runExtendCli(\n args: readonly string[],\n deps: ExtendCliDependencies,\n): Promise<CliOutput> {\n const fail = (message: string): CliOutput => ({ stdout: '', stderr: message, exitCode: 2 });\n if (args.length === 2 && args[1] === '--help')\n return { stdout: EXTEND_HELP, stderr: '', exitCode: 0 };\n const contractId = args[1];\n if (args[0] !== 'extend' || !contractId || !isValidContractId(contractId))\n return fail('Expected a valid contract ID.\\n' + EXTEND_HELP);\n const values = new Map<string, string>();\n const flags = new Set<string>();\n const valueOptions = new Set([\n '--ledgers',\n '--source-account',\n '--keys-file',\n '--secret-env',\n '--max-fee-stroops',\n ]);\n for (let i = 2; i < args.length; i++) {\n const arg = args[i]!;\n if (values.has(arg) || flags.has(arg)) return fail('Repeated extend option.');\n if (valueOptions.has(arg)) {\n const value = args[++i];\n if (!value || value.startsWith('-')) return fail('Missing extend option value.');\n values.set(arg, value);\n } else if (['--submit', '--dry-run', '--json', '--include-code'].includes(arg)) flags.add(arg);\n else return fail('Unknown or conflicting extend option.\\n' + EXTEND_HELP);\n }\n const rawLedgers = values.get('--ledgers') ?? '';\n const additionalLedgers = Number(rawLedgers);\n const sourceAccount = values.get('--source-account') ?? deps.sourceAccount;\n // `--dry-run` is the DEFAULT made speakable (`W2-D11-04`). It adds no new\n // behaviour \u2014 it names the behaviour you already get \u2014 which is the point:\n // someone scripting a safe run should be able to SAY so, and someone reading\n // that script should see the safety rather than infer it from an absence.\n //\n // Requesting both is refused rather than resolved. Any precedence rule here\n // is a coin-flip on a live transaction: if `--submit` wins, a script that\n // added `--dry-run` for safety submits anyway; if `--dry-run` wins, an\n // operator who typed `--submit` believes they sent something and did not.\n if (flags.has('--submit') && flags.has('--dry-run'))\n return fail(\n '--submit and --dry-run are mutually exclusive.\\n' +\n ' Dry-run is already the default; drop --dry-run to submit, or drop --submit to simulate.',\n );\n const submit = flags.has('--submit');\n const secretEnv = values.get('--secret-env');\n const maxFeeStroops = values.get('--max-fee-stroops');\n if (!/^[1-9]\\d*$/.test(rawLedgers) || !Number.isSafeInteger(additionalLedgers))\n return fail('--ledgers needs a positive safe integer.');\n if (!sourceAccount || !isValidPayerAccount(sourceAccount))\n return fail('A valid public payer is required (--source-account or EVERGREEN_SOURCE_ACCOUNT).');\n if (\n (maxFeeStroops !== undefined && !/^[1-9]\\d*$/.test(maxFeeStroops)) ||\n (submit && (!secretEnv || !maxFeeStroops)) ||\n (!submit && secretEnv !== undefined) ||\n (secretEnv !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(secretEnv))\n )\n return fail(\n '--submit requires --secret-env NAME and --max-fee-stroops N; secret selection is live-only.',\n );\n let dataKeys: string[] = [];\n const keysPath = values.get('--keys-file');\n if (keysPath !== undefined) {\n try {\n const parsed: unknown = JSON.parse(await deps.readKeysFile(keysPath));\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n Array.isArray(parsed) ||\n !('dataKeys' in parsed) ||\n !Array.isArray(parsed.dataKeys) ||\n parsed.dataKeys.some((k) => typeof k !== 'string')\n )\n return fail('Invalid keys file: expected dataKeys string array.');\n dataKeys = parsed.dataKeys;\n } catch {\n return fail('Unable to read a valid keys file.');\n }\n }\n try {\n const report = await deps.run(\n {\n contractId,\n additionalLedgers,\n sourceAccount,\n dataKeys,\n includeCode: flags.has('--include-code'),\n submit,\n ...(secretEnv === undefined ? {} : { secretEnv }),\n ...(maxFeeStroops === undefined ? {} : { maxFeeStroops }),\n },\n (text) => deps.preview?.(text),\n );\n const stdout = flags.has('--json')\n ? JSON.stringify(report, null, 2)\n : [\n `Mode: ${report.result.mode}. ${report.result.ok ? 'Complete' : 'Incomplete'}.`,\n ...report.plan.warnings,\n ...report.previews.map(extensionPreview),\n ...report.result.records.map(\n (r) =>\n `${r.entryKey}: ${r.outcome}${'transactionHash' in r && r.transactionHash ? ` (${r.transactionHash})` : ''}`,\n ),\n ...report.result.skipped.map((k) => `${k}: no-op (target already satisfied)`),\n ...report.result.unattempted.map((k) => `${k}: not attempted`),\n 'A submitted/unconfirmed hash must be reconciled before retrying; no automatic replacement was sent.',\n ].join('\\n');\n return {\n stdout,\n stderr: report.result.ok\n ? ''\n : 'Extension incomplete. Inspect per-entry outcomes and reconcile any submitted hash before retrying.',\n exitCode: report.result.ok ? 0 : 2,\n };\n } catch (error) {\n // A protected-subject refusal is NOT a connectivity problem, and reporting\n // it as one sends the operator to check their RPC endpoint while the real\n // message \u2014 that this write would spend an unrepeatable proof \u2014 is thrown\n // away. Same defect class as the wrong-network message fixed in W2-D10-03:\n // a safety event wearing a generic failure.\n if (error instanceof ProtectedEntryError) return fail(error.message);\n return fail(\n 'Extension preparation failed. Check Testnet RPC, selected live keys, public payer and network configuration. No raw provider details are printed.',\n );\n }\n}\n", "import type { StorageAdviceReport } from '@evergreen-stellar/core';\n\n/** Present provenance with the advice: benchmark amounts never look like this key's quote. */\nexport function formatStorageAdvice(report: StorageAdviceReport): string[] {\n const lines = ['Storage advice \u2014 observed keys only'];\n const settings = report.context.settings;\n if (settings) {\n lines.push(\n `Network minimum lifetimes at ledger ${settings.observedAtLedger}: temporary ${settings.minTemporaryTtl}, persistent ${settings.minPersistentTtl} ledgers.`,\n );\n } else {\n const old = report.evidence.settings;\n lines.push(\n `Historical minimum lifetimes (${old.recordedOn}, ledger ${old.observedAtLedger}; not current configuration): temporary ${old.minTemporaryTtl}, persistent ${old.minPersistentTtl} ledgers.`,\n `Source: ${old.source}`,\n );\n }\n lines.push(\n 'Minimum lifetime includes the current ledger; it is not the expiry of an already-existing entry.',\n );\n for (const finding of report.findings) {\n lines.push(\n '',\n `${finding.code}: ${finding.entryKey}`,\n `Known consumers: ${finding.knownConsumers.join(', ')}`,\n `Action: ${finding.action}`,\n `Why: ${finding.rationale}`,\n );\n if (finding.ttl.status === 'known')\n lines.push(\n `Observed at ledger ${finding.observedAtLedger}: ${finding.ttl.remainingLedgers} remaining; last live ledger ${finding.ttl.endsAtLedger}.`,\n );\n if (finding.currentRent.status === 'quoted') {\n const quote = report.context.quote!;\n lines.push(\n `Current rent quote: ${finding.currentRent.stroops} stroops; pricing context ledger ${quote.pricedAtLedger}, requested increment ${quote.additionalLedgers}. Estimate, not a price guarantee; excludes other transaction fees.`,\n );\n } else lines.push('Current rent: unavailable (not zero).');\n }\n if (report.findings.some((f) => f.benchmarkId !== undefined)) {\n const benchmark = report.evidence.rent;\n lines.push(\n '',\n `Historical A benchmark (${benchmark.recordedAt}): persistent rent ${benchmark.persistent.rentStroops} versus temporary rent ${benchmark.temporary.rentStroops} stroops; about 1.95x.`,\n benchmark.qualification,\n `Source: ${benchmark.source}`,\n );\n }\n if (report.findings.some((f) => f.code === 'temporary-retention')) {\n const deletion = report.evidence.deletion;\n lines.push(\n `Observed deletion reference: ${deletion.subject} (${deletion.recordedOn}); present at ${deletion.lastLiveLedger}, absent at ${deletion.firstAbsentLedger}.`,\n `Source: ${deletion.source}`,\n );\n }\n lines.push('', ...report.limitations.map((note) => `Limit: ${note}`));\n return lines;\n}\n", "import { formatCount } from '@evergreen-stellar/core';\nimport { stroopsToXlm } from '@evergreen-stellar/core';\nimport type { Stroops } from '@evergreen-stellar/shared-types';\n\n/**\n * Cost presentation (`W2-D9`).\n *\n * Two rules, both learned rather than assumed.\n *\n * **Lead with the total.** The fee fixture separates `rentFeeCharged` from the\n * non-refundable part, and that split is analytically valuable \u2014 it is what\n * produced the durability-not-size finding. It is not what the user is\n * deciding with. Someone funding a bot account wants to know what LEAVES THE\n * ACCOUNT. Rent is broken out underneath for whoever cares why.\n *\n * **Never print a precise-looking figure.** Simulated rent moved ~18% against a\n * real fee recorded one day earlier, because pricing varies with network state.\n * `0.0327 XLM` implies four significant figures from a method that cannot\n * support them \u2014 the same error as printing a bare instant for a projection\n * whose cadence has a band. So: two significant figures, an explicit \"about\",\n * and the provenance travelling with it.\n */\n\nexport interface CostLine {\n readonly totalStroops: Stroops;\n readonly rentStroops: Stroops;\n readonly otherStroops: Stroops;\n readonly entryCount: number;\n readonly additionalLedgers: number;\n readonly cappedEntryCount: number;\n readonly maxEntryTtl: number;\n readonly pricedAtLedger: number;\n /**\n * Rent per entry. The shared `code` entry routinely dominates the bill \u2014\n * it holds the Wasm \u2014 so a total alone hides where the money goes, and\n * \"which entry costs what\" is what a funding decision actually turns on.\n */\n readonly rentByEntry: Readonly<Record<string, Stroops>>;\n}\n\n/**\n * Round to two significant figures and say \"about\".\n *\n * A number the method cannot support should not be printed as though it can.\n */\nexport function approximateXlm(stroops: Stroops): string {\n const exact = Number(stroopsToXlm(stroops));\n if (exact === 0) return '0 XLM';\n const magnitude = Math.floor(Math.log10(Math.abs(exact)));\n const factor = 10 ** (magnitude - 1);\n const rounded = Math.round(exact / factor) * factor;\n // Enough decimals to show two significant figures, never more.\n const decimals = Math.max(0, 1 - magnitude);\n return `about ${rounded.toFixed(decimals)} XLM`;\n}\n\nexport function formatCost(cost: CostLine): string[] {\n const lines: string[] = [];\n const entries = `${cost.entryCount} entr${cost.entryCount === 1 ? 'y' : 'ies'}`;\n lines.push(`Cost to extend ${entries} by ${formatCount(cost.additionalLedgers)} more ledgers`);\n // Total first: it is the number the funding decision is made with.\n lines.push(\n ` total ${approximateXlm(cost.totalStroops)} (${formatCount(Number(cost.totalStroops))} stroops) \u2014 what leaves the account`,\n );\n lines.push(\n ` rent ${approximateXlm(cost.rentStroops)} (${formatCount(Number(cost.rentStroops))} stroops)`,\n );\n lines.push(\n ` fees ${approximateXlm(cost.otherStroops)} (${formatCount(Number(cost.otherStroops))} stroops) \u2014 non-refundable resource + base fee`,\n );\n const rentEntries = Object.entries(cost.rentByEntry);\n if (rentEntries.length > 1) {\n const [topKey, topRent] = rentEntries.reduce((a, b) => (BigInt(b[1]) > BigInt(a[1]) ? b : a));\n const share = Number((BigInt(topRent) * 100n) / (BigInt(cost.rentStroops) || 1n));\n if (share >= 60) {\n lines.push('');\n lines.push(\n ` ${share}% of that rent is one entry (${topKey.slice(0, 10)}\u2026). Code entries hold the`,\n );\n lines.push(\n ' Wasm and are usually the expensive one \u2014 and the one shared between contracts.',\n );\n }\n }\n lines.push('');\n lines.push(\n ` Priced by simulating against the network at ledger ${formatCount(cost.pricedAtLedger)}.`,\n );\n // The imprecision is stated, not implied by rounding alone.\n lines.push(' Rent pricing varies with network state \u2014 a quote taken on another day has');\n lines.push(' differed by ~18%. This is an estimate to budget against, not a quoted price.');\n if (cost.cappedEntryCount > 0) {\n // Silent capping would hand back less than was asked for while reporting\n // success \u2014 the same shortfall shape as passing a delta where a target belongs.\n lines.push('');\n lines.push(\n ` \u26A0 ${cost.cappedEntryCount} entr${cost.cappedEntryCount === 1 ? 'y was' : 'ies were'} CAPPED at the operation maximum of ${formatCount(cost.maxEntryTtl - 1)} ledgers`,\n );\n lines.push(' (~180 days). Those entries get less than requested; the price above reflects');\n lines.push(' the capped extension, not the request.');\n }\n lines.push('');\n // NAME THE QUANTITY. Both of these are true and they move in OPPOSITE\n // directions, so a sentence that says only \"under the target\" reads as a\n // contradiction of the receipt. Measured on the live proof 2026-09-12:\n // `--ledgers 1000` produced +1,002 on absolute expiry.\n //\n // remaining TTL, measured later -> a few ledgers UNDER the target\n // absolute expiry, at inclusion -> a few ledgers OVER the request\n //\n // Same shape as the alertThresholdOn/expiresOn collision one week earlier:\n // one phrase covering two quantities. A user comparing the promise to the\n // result would conclude one of them is a bug, and be right to.\n lines.push(' Targets are computed from TTL read now, and the ledger advances before');\n lines.push(' submission. Two consequences, in opposite directions:');\n lines.push(' \u00B7 REMAINING TTL, measured after inclusion, lands a few ledgers UNDER target');\n lines.push(' \u00B7 ABSOLUTE EXPIRY moves a few ledgers PAST the request (+N plus the gap)');\n return lines;\n}\n", "import { formatCount } from '@evergreen-stellar/core';\nimport type { ScanResult } from '@evergreen-stellar/shared-types';\nimport {\n assessEntryWithThresholds,\n resolveHealthThresholds,\n coverageIssues,\n estimateEndsAt,\n isLive,\n needsAction,\n worstHealth,\n} from '@evergreen-stellar/core';\nimport type { EntryAssessment, EntryHealth } from '@evergreen-stellar/core';\n\n/**\n * Colour is opt-in and off by default. A CLI whose output is piped into a log,\n * a CI annotation or a grant reviewer's terminal transcript should not emit\n * escape codes nobody asked for; `bin.ts` enables it only for an interactive\n * TTY with NO_COLOR unset.\n *\n * The health WORD is always printed. Colour is redundant emphasis on top of it,\n * never the only carrier of the state \u2014 a reader who is colour-blind, piping to\n * a file, or reading a screenshot must get the same information.\n */\nconst ANSI: Record<EntryHealth, string> = {\n healthy: '\\u001B[32m',\n warning: '\\u001B[33m',\n critical: '\\u001B[31m',\n unknown: '\\u001B[35m',\n};\nconst RESET = '\\u001B[0m';\n\nfunction paint(health: EntryHealth, text: string, color: boolean): string {\n return color ? `${ANSI[health]}${text}${RESET}` : text;\n}\n\nconst LABEL: Record<EntryHealth, string> = {\n healthy: 'HEALTHY',\n warning: 'WARNING',\n critical: 'CRITICAL',\n unknown: 'UNKNOWN',\n};\n\nexport interface FormatOptions {\n /** Emit ANSI colour. Default false; `bin.ts` decides from the environment. */\n readonly color?: boolean;\n /** Ledgers below which an entry needs action. Must match the exit-code gate. */\n readonly thresholdLedgers?: number;\n}\n\n/**\n * Format a scan for humans. The CLI is thin: it parses, calls core, formats,\n * and sets an exit code. All logic lives in core.\n */\n\nexport const EXIT_OK = 0;\nexport const EXIT_BELOW_THRESHOLD = 1;\nexport const EXIT_ERROR = 2;\nexport const EXIT_INCOMPLETE = 3;\n\n/**\n * The action threshold. Owned by `evergreen.config.example.json`\n * (`defaults.bumpWhenRemainingLedgersBelow`) and **now actually pinned** to it by\n * `scripts/check-policy-constants.mjs`, alongside core's `DEFAULT_CRITICAL_LEDGERS`.\n *\n * This comment previously claimed the pin already existed. It did not: moving this\n * literal left the check green, which is the documented-intent-versus-enforced-link\n * failure that check's own header warns about. Measured and closed 2026-09-17.\n */\nexport const DEFAULT_THRESHOLD_LEDGERS = 17_280;\n\n/**\n * The `--json` health block.\n *\n * Additive to `ScanResult`, never a mutation of it: `ScanResult` is\n * ADR-005-accepted and consumed by the engine and dashboard, and existing\n * readers of `entries`/`issues` must keep working untouched.\n *\n * This is where blast radius lives. ADR-006 \u00A7 *Considered and declined*\n * settles that it must NOT reach the exit code: exit codes signal category,\n * not magnitude, and a new code silently breaks every consumer matching the\n * old set. Magnitude belongs in output, and this is the machine-readable half.\n */\nexport interface ScanHealthReport {\n readonly thresholdLedgers: number;\n /** The warning tier. Informational only \u2014 the exit code uses the action tier. */\n readonly warnBelowLedgers: number;\n /** Worst state across all entries. Absent when nothing was observed. */\n readonly worst?: EntryHealth;\n /** Entries KNOWN to serve more than one contract. A floor. */\n readonly sharedEntryCount: number;\n /**\n * Entries whose sharing could not be determined. **Non-zero means\n * `sharedEntryCount` is a lower bound, not a count** \u2014 do not read a zero\n * shared count as \"nothing is shared\" while this is above zero.\n */\n readonly undeterminedSharingCount: number;\n readonly byEntry: Readonly<Record<string, EntryAssessment>>;\n}\n\n/**\n * Both tiers, from the single action threshold the CLI is given.\n *\n * `scan` graded every entry against the action threshold alone until 2026-09-14,\n * so it printed `(threshold 17,280 ledgers)` and could never say WARNING \u2014 it had\n * the vocabulary and no path to it. The two-tier decision reached the engine and\n * stopped there, which meant the two tools disagreed about the same entry:\n * guinea-pig B at 120,909 remaining read `WARNING` in an engine run and `HEALTHY`\n * in a scan, minutes apart. An operator got no warning horizon at all \u2014 an entry\n * stayed HEALTHY until it was one day from expiry, which is the failure the\n * second tier was introduced to prevent.\n *\n * `resolveHealthThresholds` is reused rather than restated so a widened action\n * threshold still widens the warning with it.\n */\nfunction tiers(thresholdLedgers: number) {\n return resolveHealthThresholds({ bumpWhenRemainingLedgersBelow: thresholdLedgers });\n}\n\n/** Grade every entry once, for whichever renderer wants it. */\nexport function healthReport(result: ScanResult, thresholdLedgers: number): ScanHealthReport {\n const thresholds = tiers(thresholdLedgers);\n const byEntry: Record<string, EntryAssessment> = {};\n for (const [key, entry] of Object.entries(result.entries)) {\n byEntry[key] = assessEntryWithThresholds(entry, thresholds);\n }\n const assessments = Object.values(byEntry);\n const worst = worstHealth(assessments);\n return {\n thresholdLedgers,\n warnBelowLedgers: thresholds.warnBelowLedgers,\n ...(worst === undefined ? {} : { worst }),\n sharedEntryCount: assessments.filter((a) => a.sharingStatus === 'shared').length,\n undeterminedSharingCount: assessments.filter((a) => a.sharingStatus === 'undetermined').length,\n byEntry,\n };\n}\n\nexport function formatHuman(result: ScanResult, now: Date, options: FormatOptions = {}): string {\n const color = options.color === true;\n const thresholdLedgers = options.thresholdLedgers ?? DEFAULT_THRESHOLD_LEDGERS;\n const lines: string[] = [];\n const entries = Object.entries(result.entries);\n const assessments: EntryAssessment[] = [];\n\n // Every caveat the human sees comes from `coverageIssues`, which is also what\n // the JSON emits. One source of truth, so the two channels cannot disagree.\n const caveats = coverageIssues(result);\n\n // The ROSTER, not an inference from one. `W2-D10-01c` asks that a scan say\n // which contracts it actually scanned, in both channels, so that an argument\n // dropped by a parser is VISIBLE rather than deduced from something missing\n // further down. The JSON has always carried `contracts`; the human channel\n // only listed them as a side effect of per-contract data-key counts, which\n // reads as a coverage detail rather than \"here is what I looked at\".\n if (result.contracts.length > 0) {\n lines.push(\n `Scanned ${result.contracts.length} contract(s): ${result.contracts.map((c) => c.id).join(', ')}`,\n );\n }\n if (result.coverage) {\n lines.push('Coverage: known keys only \u2014 contract storage has NOT been fully enumerated.');\n for (const [contract, count] of Object.entries(result.coverage.dataKeysSuppliedByContract)) {\n lines.push(` ${contract}: ${count} explicit data key(s)`);\n if (result.coverage.noDataKeysDeclaredByContract?.[contract] === true) {\n lines.push(' No additional data keys declared by caller; not independently verified.');\n }\n }\n for (const caveat of caveats) {\n if (caveat.kind !== 'coverage-limited') continue;\n lines.push(` ${caveat.message}`);\n }\n lines.push('');\n } else {\n lines.push('Coverage: unspecified by producer; health assessment is incomplete.', '');\n }\n\n if (entries.length === 0 && result.issues.length === 0) {\n return [...lines, 'No ledger entries found.'].join('\\n');\n }\n\n for (const [key, entry] of entries) {\n const live = isLive(entry.ttl);\n const assessment = assessEntryWithThresholds(entry, tiers(thresholdLedgers));\n assessments.push(assessment);\n const shortKey = `${key.slice(0, 10)}\u2026`;\n lines.push(\n `${paint(assessment.health, LABEL[assessment.health], color)} ${entry.kind} ${shortKey}`,\n );\n lines.push(` contracts: ${entry.contracts.join(', ')}`);\n // Misleading BY OMISSION otherwise, and misleading in the\n // confidently-green-before-total-outage direction: a per-contract view that\n // never mentions sharing shows N healthy contracts whose one common entry\n // is about to take all of them down together.\n if (assessment.sharingStatus === 'shared') {\n const others = assessment.observedContractCount - 1;\n lines.push(\n ` \u26A0 shared: this ${entry.kind} entry is shared with ${others} other contract${others === 1 ? '' : 's'} \u2014 they fail together`,\n );\n } else if (assessment.sharingStatus === 'undetermined') {\n // Rendered from the SAME sharingStatus the JSON reports, so the two\n // channels cannot say different things about the same entry.\n lines.push(\n ' \u26A0 sharing: code entries are shared by every contract built from the same Wasm.',\n );\n lines.push(\n ` This scan saw ${assessment.observedContractCount}. Whether others depend on this entry cannot be`,\n );\n lines.push(' determined from a single-contract scan \u2014 pass them together.');\n }\n\n if (entry.ttl.status === 'unavailable') {\n // Say \"no TTL\", never \"0 ledgers\" \u2014 an entry type that carries no TTL is\n // not an entry that is about to expire.\n lines.push(' ttl: no TTL metadata returned; health is unknown');\n } else {\n const state = live ? 'live' : `EXPIRED (${entry.endBehavior})`;\n lines.push(` remaining: ${formatCount(entry.ttl.remainingLedgers)} ledgers \u2014 ${state}`);\n lines.push(` ends at: ledger ${formatCount(entry.ttl.endsAtLedger)}`);\n const at = estimateEndsAt(entry.ttl, now);\n // \"expires\", not \"approx\" or \"crosses\". `check-decay-drift.py` projects\n // the ALERT THRESHOLD and this projects EXPIRY; they sit exactly 24h\n // apart because THRESHOLD_LEDGERS is exactly one day, which is what made\n // the 2026-09-12 collision so convincing. Both were right, and a vague\n // label is what let one word cover two events.\n if (at) lines.push(` expires ~: ${at.toISOString()} (estimate \u2014 ledgers are the truth)`);\n }\n lines.push(` observed: ledger ${formatCount(entry.observedAtLedger)}`);\n lines.push(` health: ${LABEL[assessment.health]} \u2014 ${assessment.reason}`);\n lines.push('');\n }\n\n for (const issue of result.issues) {\n lines.push(`! ${issue.kind}: ${issue.message}`);\n lines.push(` contracts: ${issue.contracts.join(', ')}`);\n // An absent entry has two very different causes with two different fixes,\n // and the scan genuinely cannot tell them apart \u2014 RPC returns nothing\n // either way. Naming both beats implying the wrong one, and beats leaving\n // a reader to guess at the moment they are trying to act.\n if (issue.kind === 'entry-not-found') {\n lines.push(' This means one of two things, and a scan cannot distinguish them:');\n lines.push(' \u00B7 the entry was ARCHIVED \u2014 restore it with RestoreFootprintOp, or');\n lines.push(' \u00B7 it never existed \u2014 check the contract ID and that it is deployed here.');\n }\n lines.push('');\n }\n\n const worst = worstHealth(assessments);\n if (worst !== undefined) {\n const shared = assessments.filter((a) => a.sharingStatus === 'shared').length;\n lines.push(\n `Worst entry health: ${paint(worst, LABEL[worst], color)}` +\n ` (warn below ${formatCount(tiers(thresholdLedgers).warnBelowLedgers)}` +\n ` \u00B7 act below ${formatCount(thresholdLedgers)} ledgers)` +\n (shared > 0 ? ` \u00B7 ${shared} shared entr${shared === 1 ? 'y' : 'ies'}` : ''),\n );\n }\n\n // A partial scan must never read as a clean bill of health.\n if (result.issues.length > 0) {\n lines.push(`Scan is PARTIAL \u2014 ${result.issues.length} issue(s). Absence is not health.`);\n }\n\n return lines.join('\\n').trimEnd();\n}\n\n/**\n * Health gate for the planned `evergreen-check` Action, never a spending decision.\n * Precedence: error (2), incomplete (3), observed low TTL (1), healthy scope (0).\n * Mixed results keep their observations/issues in JSON even when one exit wins.\n */\nexport interface ExitCodeOptions {\n /**\n * Demand that every contract declare its data-key scope, and report `3` when\n * one has not. **Off by default, and that default is the whole point of the\n * ADR-006 amendment** (2026-09-10).\n *\n * Whether a contract has data keys beyond its instance is knowable only from\n * its source. RPC cannot enumerate storage, so a caller scanning a contract\n * they did not write *cannot* declare scope truthfully \u2014 and for them the\n * original default made `3` permanent, with the only escape being a flag\n * asserting something they cannot check. A signal that fires on every default\n * invocation has stopped being a signal.\n *\n * So the demand moved to the caller who can actually satisfy it: the contract's\n * author, in CI. `evergreen-check` turns this on (`W4-D25-01`); a human at a\n * terminal scanning someone else's contract does not get it.\n */\n readonly requireDeclaredScope?: boolean;\n}\n\n/** True when the scan itself came back degraded \u2014 as opposed to merely un-declared. */\nfunction scanIsDegraded(result: ScanResult): boolean {\n return (\n Object.keys(result.entries).length === 0 ||\n result.issues.some(\n (i) => i.kind === 'entry-not-found' || i.kind === 'unsupported-executable',\n ) ||\n Object.values(result.entries).some((e) => e.ttl.status === 'unavailable')\n );\n}\n\n/** True when a contract's data-key scope was never stated, or was stated inconsistently. */\nfunction scopeIsUndeclared(result: ScanResult): boolean {\n if (!result.coverage || result.contracts.length === 0) return true;\n return result.contracts.some((c) => {\n const count = result.coverage?.dataKeysSuppliedByContract[c.id];\n const empty = result.coverage?.noDataKeysDeclaredByContract?.[c.id] === true;\n if (count === undefined || !Number.isInteger(count) || count < 0) return true;\n // Zero supplied keys means something only if the caller said it meant something,\n // and a non-empty list alongside an emptiness claim is a contradiction.\n return count === 0 ? !empty : empty;\n });\n}\n\nexport function exitCodeFor(\n result: ScanResult,\n thresholdLedgers: number,\n options: ExitCodeOptions = {},\n): number {\n if (result.issues.some((i) => i.kind === 'rpc-error' || i.kind === 'invalid-response'))\n return EXIT_ERROR;\n\n // `3` means the read came back degraded: an entry missing, a TTL unavailable,\n // an executable we cannot follow, nothing observed at all. Rare, and therefore\n // still informative.\n if (scanIsDegraded(result)) return EXIT_INCOMPLETE;\n\n // Undeclared scope is `3` only when the caller asked to be held to it.\n if (options.requireDeclaredScope === true && scopeIsUndeclared(result)) return EXIT_INCOMPLETE;\n\n // CALLS the shared predicate; never restates it. This line previously read\n // `remainingLedgers < thresholdLedgers`, a longhand copy of the policy. When\n // the threshold became a floor (`<=`) on 2026-09-10, the copy did not move\n // with it \u2014 so at EXACTLY the threshold the engine alarmed while the Action\n // reported a clean CI pass. A gate that disagrees with the engine it gates\n // is worse than no gate. See CONVENTIONS \u00A7 one home for a policy.\n for (const entry of Object.values(result.entries)) {\n if (entry.ttl.status === 'unavailable') continue;\n if (needsAction(entry.ttl.remainingLedgers, thresholdLedgers)) return EXIT_BELOW_THRESHOLD;\n }\n return EXIT_OK;\n}\n", "import {\n NotTestnetError,\n coverageIssues,\n isValidContractId,\n scanContracts,\n analyzeStorage,\n} from '@evergreen-stellar/core';\nimport type { StorageSettings, StorageAdviceReport } from '@evergreen-stellar/core';\nimport { formatStorageAdvice } from './optimizer.js';\nimport { formatCost, type CostLine } from './cost.js';\nimport { EXTEND_HELP, runExtendCli } from './extend.js';\nimport type { ExtendCliDependencies } from './extend.js';\nimport type { LedgerEntryReader } from '@evergreen-stellar/core';\nimport {\n DEFAULT_THRESHOLD_LEDGERS,\n EXIT_ERROR,\n exitCodeFor,\n formatHuman,\n healthReport,\n} from './scan.js';\n\n/** Matches evergreen.config.example.json's defaults.extendToLedgers (~30 days). */\nconst DEFAULT_EXTEND_LEDGERS = 518_400;\n\nconst USAGE =\n 'usage: evergreen scan <contract-id> [<contract-id> ...] [--keys-file <path> | --no-data-keys] [--require-declared-scope] [--threshold N] [--json] [--cost [--ledgers N]] [--optimize]';\nconst HELP = `${USAGE}\n\nReads instance/Wasm and supplied persistent/temporary keys on Stellar Testnet.\nKeys file: { \"dataKeys\": [\"base64 XDR LedgerKey\", ...] }\n\nPASS SEVERAL CONTRACTS TOGETHER to see real shared-code blast radius. Contracts\nbuilt from the same Wasm share ONE ContractCode ledger entry, and a scan of one\ncontract cannot tell whether others depend on it \u2014 the chain does not index\nreverse dependencies from a single query, so that entry reports \"sharing\nundetermined\". Naming them together resolves it:\n\n evergreen scan <A> code entry: 1 consumer, sharing UNDETERMINED\n evergreen scan <A> <B> <C> code entry: 3 consumers, SHARED, they fail together\n\nEvery scan prints which contracts it actually scanned, so a mistyped or dropped\nargument is visible rather than inferred. --keys-file takes exactly one contract,\nbecause data keys belong to a specific contract and the file does not say which.\n\nExit: 0 everything scanned is healthy; 1 observed low TTL; 2 error;\n 3 the scan came back incomplete (entry missing, TTL unavailable,\n executable not followable, or nothing observed).\nPrecedence: 2 > 3 > 1 > 0. Exit status never authorizes a transaction.\n\nScanning reads the keys it is given; it cannot enumerate a contract's storage,\nso a clean exit means \"everything I was asked to check is healthy\" and never\n\"this contract is fully healthy\". Coverage is printed with every scan.\n\n--threshold N act-now threshold in LEDGERS, default 17,280 (~1 day).\n What evergreen-check sets in CI: a repository that wants\n a week of warning fails its build at 120,960, not at ours.\n Both health tiers move with it \u2014 WARNING widens as the\n action threshold rises, so raising it never silently\n narrows the earlier warning. Exit 1 means an entry is at\n or below this value; the boundary is inclusive, because\n remaining exactly N is already the margin you set out to\n keep. Changes what is REPORTED and never what is written.\n\n--no-data-keys assert this contract has no data keys beyond its instance.\n Only its author can know that; it is a caller declaration\n and is never independently verified.\n--require-declared-scope\n also exit 3 when scope was not declared. Intended for CI on\n a contract you own; evergreen-check sets it by default.\n--json machine-readable output. The human view is a summary; JSON\n is the complete record, including every issue.\n--optimize append conditional storage advice with evidence and scope\n limits. Reads network minimum lifetimes; no payer needed.\n Add --cost for current rent quotes. No storage is changed.\n--cost [--ledgers N] estimate what extending every entry by N more ledgers\n would cost, priced by simulating against the network.\n Default N is 518,400 (~30 days). Nothing is submitted.\n\n \"--ledgers N\" means \"give me N MORE ledgers\". The protocol\n wants an absolute target, so the CLI computes it for you\n and caps it at max_entry_ttl - 1, saying so when it does.\n Costs are estimates: rent pricing varies with network\n state and has differed ~18% between days.\n\nHealth states, printed per entry and as a worst-of summary:\n HEALTHY above threshold.\n WARNING low, recoverable, and affects only this contract.\n CRITICAL expired, OR temporary (deleted at expiry, unrecoverable), OR low and\n SHARED \u2014 a code entry shared by N contracts at 3 days is N contracts\n at 3 days, not one.\n UNKNOWN TTL could not be read. Not healthy; unread.\n\nColour is added only for an interactive terminal and honours NO_COLOR. The state\nword always prints, so piped output and screenshots lose nothing.`;\n\nexport interface CliDependencies {\n readonly extend?: ExtendCliDependencies;\n connect(): Promise<LedgerEntryReader>;\n readStorageSettings?(): Promise<StorageSettings | undefined>;\n /** Optional: supplied only when --cost is requested, so a plain scan stays one round trip. */\n priceExtend?(args: {\n readonly scan: import('@evergreen-stellar/shared-types').ScanResult;\n readonly additionalLedgers: number;\n }): Promise<CostLine>;\n readKeysFile(path: string): Promise<string>;\n now(): Date;\n /** True only for an interactive TTY with NO_COLOR unset. Decided in bin.ts. */\n color?: boolean;\n}\n\nexport interface CliOutput {\n readonly stdout: string;\n readonly stderr: string;\n readonly exitCode: number;\n}\n\n/** Validate arguments/file shape before connecting; diagnostics never echo file contents or credentials. */\nexport async function runCli(\n args: readonly string[],\n dependencies: CliDependencies,\n): Promise<CliOutput> {\n if (args[0] === 'extend') {\n if (args[1] === '--help' && args.length === 2)\n return { stdout: EXTEND_HELP, stderr: '', exitCode: 0 };\n if (!dependencies.extend)\n return {\n stdout: '',\n stderr: 'Extension dependencies are unavailable.',\n exitCode: EXIT_ERROR,\n };\n return runExtendCli(args, dependencies.extend);\n }\n const fail = (message: string): CliOutput => ({\n stdout: '',\n stderr: message,\n exitCode: EXIT_ERROR,\n });\n if (\n (args.length === 1 && args[0] === '--help') ||\n (args.length === 2 && args[0] === 'scan' && args[1] === '--help')\n ) {\n return {\n stdout: HELP + '\\n\\nManual extension: evergreen extend --help',\n stderr: '',\n exitCode: 0,\n };\n }\n if (args[0] !== 'scan') return fail(USAGE);\n // N contract IDs, because the tool's own advice requires it (`W2-D10-01c`).\n //\n // A single-contract scan structurally CANNOT establish that a shared code\n // entry is unshared, so it reports `sharingStatus: 'undetermined'` and tells\n // the reader to \"pass them together to see the real blast radius\". That\n // sentence was unreachable from the command line: `scanContracts` has taken\n // an array since it was written, and only this parser was singular. Naming a\n // limitation and withholding its remedy is half a fix.\n const contractIds: string[] = [];\n let argIndex = 1;\n for (; argIndex < args.length && !args[argIndex]!.startsWith('-'); argIndex++) {\n contractIds.push(args[argIndex]!);\n }\n if (contractIds.length === 0) return fail(USAGE);\n // Validate shape BEFORE connecting. A typo should cost a one-line message,\n // not a network round trip that surfaces as a scan report full of coverage\n // boilerplate about a contract that cannot exist.\n for (const id of contractIds) {\n if (!isValidContractId(id)) {\n return fail(\n `Not a Stellar contract ID: ${id}\\n` +\n 'Contract IDs start with C and are 56 characters (StrKey-encoded).\\n' +\n 'Check for a truncated paste or an account address (G\u2026) used by mistake.',\n );\n }\n }\n // A repeated ID is a mistake worth naming rather than silently deduplicating:\n // the caller believes they asked about more contracts than they did, and the\n // blast-radius count they read back would be a floor below what they expect.\n const duplicate = contractIds.find((id, i) => contractIds.indexOf(id) !== i);\n if (duplicate !== undefined) return fail(`Repeated contract ID: ${duplicate}`);\n let asJson = false;\n let withCost = false;\n let withOptimize = false;\n let additionalLedgers = DEFAULT_EXTEND_LEDGERS;\n let noDataKeys = false;\n let requireDeclaredScope = false;\n let keysPath: string | undefined;\n let thresholdLedgers = DEFAULT_THRESHOLD_LEDGERS;\n for (let i = argIndex; i < args.length; i++) {\n if (args[i] === '--json' && !asJson) asJson = true;\n else if (args[i] === '--cost' && !withCost) withCost = true;\n else if (args[i] === '--optimize' && !withOptimize) withOptimize = true;\n else if (args[i] === '--ledgers') {\n const raw = args[++i];\n const parsed = Number(raw);\n if (!raw || !/^\\d+$/.test(raw) || !Number.isInteger(parsed) || parsed <= 0) {\n return fail(`--ledgers needs a positive whole number of ledgers.\\n${USAGE}`);\n }\n additionalLedgers = parsed;\n } else if (args[i] === '--threshold') {\n const raw = args[++i];\n const parsed = Number(raw);\n if (!raw || !/^\\d+$/.test(raw) || !Number.isInteger(parsed) || parsed <= 0) {\n return fail(`--threshold needs a positive whole number of ledgers.\\n${USAGE}`);\n }\n thresholdLedgers = parsed;\n } else if (args[i] === '--no-data-keys' && !noDataKeys) noDataKeys = true;\n else if (args[i] === '--require-declared-scope' && !requireDeclaredScope)\n requireDeclaredScope = true;\n else if (args[i] === '--keys-file' && keysPath === undefined) {\n keysPath = args[++i];\n if (!keysPath || keysPath.startsWith('-')) return fail(`--keys-file needs a path.\\n${USAGE}`);\n } else return fail(`Unknown or repeated argument.\\n${USAGE}`);\n }\n if (noDataKeys && keysPath !== undefined)\n return fail('--keys-file and --no-data-keys are mutually exclusive.');\n // Refused, not resolved. A keys file is `{ \"dataKeys\": [...] }` with no\n // contract attached, and persistent/temporary keys are derived from the\n // contract that owns them \u2014 so spreading one list across N contracts would\n // attribute entries to contracts that do not own them, and the scan would\n // report that misattribution as fact.\n if (keysPath !== undefined && contractIds.length > 1)\n return fail(\n '--keys-file applies to exactly one contract.\\n' +\n ' Data keys are owned by a specific contract and the file does not say which,\\n' +\n ' so spreading one list across several would misattribute entries.\\n' +\n ' Scan them together without --keys-file to see shared-code blast radius,\\n' +\n ' or scan one at a time when you need explicit data keys.',\n );\n\n let dataKeys: string[] = [];\n if (keysPath !== undefined) {\n try {\n const parsed: unknown = JSON.parse(await dependencies.readKeysFile(keysPath));\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n Array.isArray(parsed) ||\n !('dataKeys' in parsed) ||\n !Array.isArray(parsed.dataKeys) ||\n !parsed.dataKeys.every((key: unknown) => typeof key === 'string') ||\n Object.keys(parsed).some((key) => key !== 'dataKeys')\n ) {\n return fail('Keys file must be a JSON object containing only a dataKeys array of strings.');\n }\n dataKeys = parsed.dataKeys;\n } catch {\n return fail('Could not read keys file as JSON. Check the file path and JSON syntax.');\n }\n }\n let reader: LedgerEntryReader;\n try {\n reader = await dependencies.connect();\n } catch (error) {\n // A wrong-network refusal and an unreachable endpoint are different\n // problems with different fixes, and collapsing them into one message hid\n // the more important of the two: the testnet guard firing means you are\n // pointed at another network, most likely MAINNET, which is a safety event\n // rather than a connectivity one.\n if (error instanceof NotTestnetError) {\n return fail(\n `${error.message}\\n` +\n 'Evergreen only runs against Stellar Testnet. Point SOROBAN_RPC_URL at a\\n' +\n 'testnet endpoint \u2014 the default is https://soroban-testnet.stellar.org.',\n );\n }\n return fail(\n 'Could not reach the Stellar RPC endpoint. Check SOROBAN_RPC_URL, the URL\\n' +\n 'syntax, and your network connection. Nothing was read and nothing was changed.',\n );\n }\n const scanned = await scanContracts(\n reader,\n contractIds.map((id) => ({ contract: { id }, dataKeys, noDataKeys })),\n );\n // `issues.length === 0` is the question a consumer will actually ask, so it\n // has to be answerable. Caveats are merged in for that reason \u2014 a scan that\n // told a human it was incomplete must not hand a machine an empty array.\n const result = { ...scanned, issues: [...scanned.issues, ...coverageIssues(scanned)] };\n\n let settings: StorageSettings | undefined;\n if (withOptimize) {\n try {\n settings = await dependencies.readStorageSettings?.();\n } catch {\n /* Dated historical context is explicitly labelled in advice. */\n }\n }\n const advice = (priced?: CostLine): StorageAdviceReport | undefined =>\n withOptimize\n ? analyzeStorage(result, {\n ...(settings === undefined ? {} : { settings }),\n ...(priced === undefined\n ? {}\n : {\n quote: {\n rentByEntry: priced.rentByEntry,\n pricedAtLedger: priced.pricedAtLedger,\n additionalLedgers: priced.additionalLedgers,\n },\n }),\n })\n : undefined;\n\n let cost: CostLine | undefined;\n if (withCost) {\n if (dependencies.priceExtend === undefined && !withOptimize) {\n return fail('--cost is unavailable: no pricing backend was configured.');\n }\n try {\n if (dependencies.priceExtend === undefined) throw new Error('No pricing backend');\n cost = await dependencies.priceExtend({ scan: result, additionalLedgers });\n } catch {\n const optimization = advice();\n // A failed quote must not take the scan down with it \u2014 the TTL answer is\n // still correct and still worth printing.\n return {\n stdout: asJson\n ? JSON.stringify(\n {\n ...result,\n health: healthReport(result, thresholdLedgers),\n ...(optimization === undefined ? {} : { optimization }),\n },\n null,\n 2,\n )\n : `${formatHuman(result, dependencies.now(), {\n color: dependencies.color === true,\n thresholdLedgers,\n })}\\n\\n! Could not price an extend: the network declined to simulate it.\\n The TTL results above are unaffected.${optimization === undefined ? '' : `\\n\\n${formatStorageAdvice(optimization).join('\\n')}`}`,\n stderr: '',\n exitCode: exitCodeFor(result, thresholdLedgers, { requireDeclaredScope }),\n };\n }\n }\n\n const optimization = advice(cost);\n return {\n stdout: asJson\n ? // Additive envelope: every existing key of ScanResult is untouched, so\n // a consumer reading `entries` or `issues` is unaffected by `health`.\n JSON.stringify(\n {\n ...result,\n health: healthReport(result, thresholdLedgers),\n ...(cost === undefined ? {} : { cost }),\n ...(optimization === undefined ? {} : { optimization }),\n },\n null,\n 2,\n )\n : formatHuman(result, dependencies.now(), {\n color: dependencies.color === true,\n thresholdLedgers,\n }) +\n (cost === undefined ? '' : `\\n\\n${formatCost(cost).join('\\n')}`) +\n (optimization === undefined ? '' : `\\n\\n${formatStorageAdvice(optimization).join('\\n')}`),\n stderr: '',\n // Same constant the display grades against, so the printed health and the\n // exit code can never describe different thresholds.\n exitCode: exitCodeFor(result, thresholdLedgers, { requireDeclaredScope }),\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["rpc", "Networks", "xdr", "Networks", "Address", "xdr", "xdr", "rpc", "xdr", "Address", "Networks", "Keypair", "TransactionBuilder", "rpc", "xdr", "Address", "xdr", "xdr", "Address", "Account", "Networks", "Operation", "SorobanDataBuilder", "StrKey", "Transaction", "TransactionBuilder", "rpc", "xdr", "StrKey", "Networks", "TransactionBuilder", "Account", "Operation", "SorobanDataBuilder", "rpc", "Transaction", "ledger", "Address", "optimization", "rpc", "Networks"]
7
+ }