@secondlayer/shared 12.1.0 → 12.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/src/archive/copy-loader.d.ts +1 -0
  2. package/dist/src/archive/copy-loader.js.map +2 -2
  3. package/dist/src/archive/instance-diagnosis.d.ts +1 -0
  4. package/dist/src/archive/instance-diagnosis.js.map +2 -2
  5. package/dist/src/archive/range-digest.d.ts +4 -2
  6. package/dist/src/archive/reference.d.ts +1 -0
  7. package/dist/src/archive/reference.js.map +2 -2
  8. package/dist/src/archive/semantic-digest-builder.d.ts +4 -2
  9. package/dist/src/archive/status.d.ts +1 -0
  10. package/dist/src/archive/status.js.map +2 -2
  11. package/dist/src/coverage/index.d.ts +4 -2
  12. package/dist/src/db/index.d.ts +4 -2
  13. package/dist/src/db/queries/chain-reorgs.d.ts +4 -2
  14. package/dist/src/db/queries/contracts.d.ts +4 -2
  15. package/dist/src/db/queries/instance.d.ts +4 -2
  16. package/dist/src/db/queries/integrity.d.ts +4 -2
  17. package/dist/src/db/queries/subgraph-gaps.d.ts +4 -2
  18. package/dist/src/db/queries/subgraph-operations.d.ts +4 -2
  19. package/dist/src/db/queries/subgraphs.d.ts +4 -2
  20. package/dist/src/db/queries/subgraphs.js.map +2 -2
  21. package/dist/src/db/queries/sync-scope.d.ts +4 -2
  22. package/dist/src/db/queries/webhooks.d.ts +4 -2
  23. package/dist/src/db/schema.d.ts +4 -2
  24. package/dist/src/index-http.d.ts +1 -0
  25. package/dist/src/index.d.ts +21 -2
  26. package/dist/src/index.js.map +4 -4
  27. package/dist/src/node/archive-client.d.ts +1 -0
  28. package/dist/src/node/archive-client.js.map +2 -2
  29. package/dist/src/node/local-client.d.ts +4 -2
  30. package/dist/src/schemas/index.d.ts +15 -0
  31. package/dist/src/schemas/index.js.map +3 -3
  32. package/dist/src/schemas/subgraphs.d.ts +6 -0
  33. package/dist/src/schemas/subgraphs.js.map +2 -2
  34. package/dist/src/schemas/webhooks.d.ts +18 -5
  35. package/dist/src/schemas/webhooks.js.map +2 -2
  36. package/dist/src/streams-rows/index.d.ts +8 -4
  37. package/dist/src/subgraphs/spec.d.ts +3 -0
  38. package/dist/src/subgraphs/spec.js.map +2 -2
  39. package/migrations/0161_observer_journal_retried.ts +50 -0
  40. package/migrations/0164_api_keys_docs_product.ts +52 -0
  41. package/package.json +2 -2
@@ -13,6 +13,7 @@ import { Writable } from "node:stream";
13
13
  * rows, and lets the CLI and the indexer share one loader rather than growing
14
14
  * two subtly different CSV encoders.
15
15
  */
16
+ /** The three archive datasets: blocks, transactions, and events. */
16
17
  type ArchiveDataset = "blocks" | "transactions" | "events";
17
18
  /** Row shapes exactly as the archive's Parquet columns are named. */
18
19
  type ArchiveRow = Record<string, unknown>;
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/archive/copy-loader.ts"],
4
4
  "sourcesContent": [
5
- "import { once } from \"node:events\";\nimport type { Writable } from \"node:stream\";\n\n/**\n * Streaming `COPY ... FROM STDIN` loader for canonical archive rows.\n *\n * Restoring history row-by-row is not viable at chain scale: batched INSERTs\n * measured ~2k rows/s on this schema, which is days for a full genesis restore.\n * COPY moves the same data at ~64k rows/s because it is one statement per\n * partition with no per-row round-trip.\n *\n * This module deliberately knows nothing about Parquet or about where rows come\n * from — it accepts the archive's row shape as an async iterable. That keeps the\n * heavy file-format dependency out of every consumer that only needs to write\n * rows, and lets the CLI and the indexer share one loader rather than growing\n * two subtly different CSV encoders.\n */\n\nexport type ArchiveDataset = \"blocks\" | \"transactions\" | \"events\";\n\n/** Row shapes exactly as the archive's Parquet columns are named. */\nexport type ArchiveRow = Record<string, unknown>;\n\n/**\n * CSV encoding for `WITH (FORMAT csv, NULL '\\N')`: every present value is\n * quoted with embedded quotes doubled, so a value that happens to equal the\n * NULL marker cannot be misread as SQL NULL. Only a genuinely absent value is\n * written unquoted as `\\N`.\n */\nexport function csvField(value: string | number | null | undefined): string {\n\tif (value === null || value === undefined) return \"\\\\N\";\n\tconst text = typeof value === \"number\" ? String(value) : value;\n\treturn `\"${text.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction asNumber(value: unknown): number {\n\treturn typeof value === \"bigint\" ? Number(value) : Number(value as number);\n}\n\nfunction asText(value: unknown): string {\n\treturn Buffer.isBuffer(value) ? value.toString(\"utf8\") : String(value);\n}\n\nfunction asOptionalText(value: unknown): string | null {\n\treturn value === null || value === undefined ? null : asText(value);\n}\n\nexport function toCsvLine(dataset: ArchiveDataset, row: ArchiveRow): string {\n\tif (dataset === \"blocks\") {\n\t\treturn [\n\t\t\tcsvField(asNumber(row.height)),\n\t\t\tcsvField(asText(row.hash)),\n\t\t\tcsvField(asText(row.parent_hash)),\n\t\t\tcsvField(asNumber(row.burn_block_height)),\n\t\t\tcsvField(asOptionalText(row.burn_block_hash)),\n\t\t\tcsvField(asOptionalText(row.index_block_hash)),\n\t\t\tcsvField(asNumber(row.timestamp)),\n\t\t].join(\",\");\n\t}\n\tif (dataset === \"transactions\") {\n\t\treturn [\n\t\t\tcsvField(asText(row.tx_id)),\n\t\t\tcsvField(asNumber(row.block_height)),\n\t\t\tcsvField(asNumber(row.tx_index)),\n\t\t\tcsvField(asText(row.type)),\n\t\t\tcsvField(asText(row.sender)),\n\t\t\tcsvField(asText(row.status)),\n\t\t\tcsvField(asOptionalText(row.contract_id)),\n\t\t\tcsvField(asOptionalText(row.function_name)),\n\t\t\tcsvField(asOptionalText(row.function_args_json)),\n\t\t\tcsvField(asOptionalText(row.raw_result)),\n\t\t\tcsvField(asText(row.raw_tx)),\n\t\t].join(\",\");\n\t}\n\treturn [\n\t\tcsvField(asText(row.tx_id)),\n\t\tcsvField(asNumber(row.block_height)),\n\t\tcsvField(asNumber(row.event_index)),\n\t\tcsvField(asText(row.event_type)),\n\t\tcsvField(asText(row.data_json)),\n\t].join(\",\");\n}\n\n/**\n * `canonical` is omitted from the blocks column list: every archived block is\n * canonical by definition, and the schema defaults it to true, so carrying it\n * through every row would be dead weight.\n */\nexport function copyStatement(dataset: ArchiveDataset): string {\n\tif (dataset === \"blocks\") {\n\t\treturn \"COPY blocks (height, hash, parent_hash, burn_block_height, burn_block_hash, index_block_hash, timestamp) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n\t}\n\tif (dataset === \"transactions\") {\n\t\treturn \"COPY transactions (tx_id, block_height, tx_index, type, sender, status, contract_id, function_name, function_args, raw_result, raw_tx) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n\t}\n\treturn \"COPY events (tx_id, block_height, event_index, type, data) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n}\n\n/**\n * Stream rows onto an open COPY writable. The caller owns opening it (drivers\n * differ in how a COPY stream is obtained) and this owns the wire format,\n * backpressure, and failure semantics.\n *\n * On error the stream is destroyed rather than ended, so Postgres aborts the\n * COPY instead of committing a partial one — an interrupted load must leave\n * nothing behind, not half a partition.\n */\nexport async function writeRowsToCopyStream(params: {\n\twritable: Writable;\n\tdataset: ArchiveDataset;\n\trows: AsyncIterable<ArchiveRow>;\n\tonProgress?: (rowsWritten: number) => void;\n\tprogressEvery?: number;\n}): Promise<number> {\n\tconst { writable, dataset, rows } = params;\n\tconst progressEvery = params.progressEvery ?? 500_000;\n\tlet count = 0;\n\ttry {\n\t\tfor await (const row of rows) {\n\t\t\tconst line = `${toCsvLine(dataset, row)}\\n`;\n\t\t\tif (!writable.write(line)) {\n\t\t\t\tawait once(writable, \"drain\");\n\t\t\t}\n\t\t\tcount++;\n\t\t\tif (params.onProgress && count % progressEvery === 0) {\n\t\t\t\tparams.onProgress(count);\n\t\t\t}\n\t\t}\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\twritable.end((error?: Error | null) =>\n\t\t\t\terror ? reject(error) : resolve(),\n\t\t\t);\n\t\t});\n\t} catch (error) {\n\t\twritable.destroy(error instanceof Error ? error : new Error(String(error)));\n\t\tthrow error;\n\t}\n\treturn count;\n}\n"
5
+ "import { once } from \"node:events\";\nimport type { Writable } from \"node:stream\";\n\n/**\n * Streaming `COPY ... FROM STDIN` loader for canonical archive rows.\n *\n * Restoring history row-by-row is not viable at chain scale: batched INSERTs\n * measured ~2k rows/s on this schema, which is days for a full genesis restore.\n * COPY moves the same data at ~64k rows/s because it is one statement per\n * partition with no per-row round-trip.\n *\n * This module deliberately knows nothing about Parquet or about where rows come\n * from — it accepts the archive's row shape as an async iterable. That keeps the\n * heavy file-format dependency out of every consumer that only needs to write\n * rows, and lets the CLI and the indexer share one loader rather than growing\n * two subtly different CSV encoders.\n */\n\n/** The three archive datasets: blocks, transactions, and events. */\nexport type ArchiveDataset = \"blocks\" | \"transactions\" | \"events\";\n\n/** Row shapes exactly as the archive's Parquet columns are named. */\nexport type ArchiveRow = Record<string, unknown>;\n\n/**\n * CSV encoding for `WITH (FORMAT csv, NULL '\\N')`: every present value is\n * quoted with embedded quotes doubled, so a value that happens to equal the\n * NULL marker cannot be misread as SQL NULL. Only a genuinely absent value is\n * written unquoted as `\\N`.\n */\nexport function csvField(value: string | number | null | undefined): string {\n\tif (value === null || value === undefined) return \"\\\\N\";\n\tconst text = typeof value === \"number\" ? String(value) : value;\n\treturn `\"${text.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction asNumber(value: unknown): number {\n\treturn typeof value === \"bigint\" ? Number(value) : Number(value as number);\n}\n\nfunction asText(value: unknown): string {\n\treturn Buffer.isBuffer(value) ? value.toString(\"utf8\") : String(value);\n}\n\nfunction asOptionalText(value: unknown): string | null {\n\treturn value === null || value === undefined ? null : asText(value);\n}\n\nexport function toCsvLine(dataset: ArchiveDataset, row: ArchiveRow): string {\n\tif (dataset === \"blocks\") {\n\t\treturn [\n\t\t\tcsvField(asNumber(row.height)),\n\t\t\tcsvField(asText(row.hash)),\n\t\t\tcsvField(asText(row.parent_hash)),\n\t\t\tcsvField(asNumber(row.burn_block_height)),\n\t\t\tcsvField(asOptionalText(row.burn_block_hash)),\n\t\t\tcsvField(asOptionalText(row.index_block_hash)),\n\t\t\tcsvField(asNumber(row.timestamp)),\n\t\t].join(\",\");\n\t}\n\tif (dataset === \"transactions\") {\n\t\treturn [\n\t\t\tcsvField(asText(row.tx_id)),\n\t\t\tcsvField(asNumber(row.block_height)),\n\t\t\tcsvField(asNumber(row.tx_index)),\n\t\t\tcsvField(asText(row.type)),\n\t\t\tcsvField(asText(row.sender)),\n\t\t\tcsvField(asText(row.status)),\n\t\t\tcsvField(asOptionalText(row.contract_id)),\n\t\t\tcsvField(asOptionalText(row.function_name)),\n\t\t\tcsvField(asOptionalText(row.function_args_json)),\n\t\t\tcsvField(asOptionalText(row.raw_result)),\n\t\t\tcsvField(asText(row.raw_tx)),\n\t\t].join(\",\");\n\t}\n\treturn [\n\t\tcsvField(asText(row.tx_id)),\n\t\tcsvField(asNumber(row.block_height)),\n\t\tcsvField(asNumber(row.event_index)),\n\t\tcsvField(asText(row.event_type)),\n\t\tcsvField(asText(row.data_json)),\n\t].join(\",\");\n}\n\n/**\n * `canonical` is omitted from the blocks column list: every archived block is\n * canonical by definition, and the schema defaults it to true, so carrying it\n * through every row would be dead weight.\n */\nexport function copyStatement(dataset: ArchiveDataset): string {\n\tif (dataset === \"blocks\") {\n\t\treturn \"COPY blocks (height, hash, parent_hash, burn_block_height, burn_block_hash, index_block_hash, timestamp) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n\t}\n\tif (dataset === \"transactions\") {\n\t\treturn \"COPY transactions (tx_id, block_height, tx_index, type, sender, status, contract_id, function_name, function_args, raw_result, raw_tx) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n\t}\n\treturn \"COPY events (tx_id, block_height, event_index, type, data) FROM STDIN WITH (FORMAT csv, NULL '\\\\N')\";\n}\n\n/**\n * Stream rows onto an open COPY writable. The caller owns opening it (drivers\n * differ in how a COPY stream is obtained) and this owns the wire format,\n * backpressure, and failure semantics.\n *\n * On error the stream is destroyed rather than ended, so Postgres aborts the\n * COPY instead of committing a partial one — an interrupted load must leave\n * nothing behind, not half a partition.\n */\nexport async function writeRowsToCopyStream(params: {\n\twritable: Writable;\n\tdataset: ArchiveDataset;\n\trows: AsyncIterable<ArchiveRow>;\n\tonProgress?: (rowsWritten: number) => void;\n\tprogressEvery?: number;\n}): Promise<number> {\n\tconst { writable, dataset, rows } = params;\n\tconst progressEvery = params.progressEvery ?? 500_000;\n\tlet count = 0;\n\ttry {\n\t\tfor await (const row of rows) {\n\t\t\tconst line = `${toCsvLine(dataset, row)}\\n`;\n\t\t\tif (!writable.write(line)) {\n\t\t\t\tawait once(writable, \"drain\");\n\t\t\t}\n\t\t\tcount++;\n\t\t\tif (params.onProgress && count % progressEvery === 0) {\n\t\t\t\tparams.onProgress(count);\n\t\t\t}\n\t\t}\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\twritable.end((error?: Error | null) =>\n\t\t\t\terror ? reject(error) : resolve(),\n\t\t\t);\n\t\t});\n\t} catch (error) {\n\t\twritable.destroy(error instanceof Error ? error : new Error(String(error)));\n\t\tthrow error;\n\t}\n\treturn count;\n}\n"
6
6
  ],
7
- "mappings": "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",
7
+ "mappings": "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",
8
8
  "debugId": "4643C93B767098ED64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -53,6 +53,7 @@ interface InstanceIssue {
53
53
  nextSteps: string[];
54
54
  }
55
55
  type InstanceState = "healthy" | "empty-index" | "degraded";
56
+ /** An instance's state, the API verdict, and the issues found. */
56
57
  interface InstanceDiagnosis {
57
58
  state: InstanceState;
58
59
  /** The API's own top-level verdict, passed through unmodified. */
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/archive/instance-diagnosis.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * One reading of `/status` (or `/public/status`), shared by the CLI\n * (`status`, `doctor`, `context`) and the SDK (`sl.instance.diagnose`).\n *\n * These surfaces used to each interpret the same payload on their own and\n * disagreed in the most common first-run state: on a freshly self-hosted\n * instance with no chain data, `status` printed DEGRADED while `doctor` printed\n * \"All checks passed\", and neither named the cause or a next command. An\n * operator (or an agent) reading both had no way to tell whether it was safe to\n * proceed. The verdict now lives here so the surfaces cannot drift apart, and\n * every issue carries the exact command that resolves it.\n */\n\nexport interface PublicStatusDecoder {\n\tdecoder: string;\n\tstatus: string;\n}\n\nexport interface PublicStatusService {\n\tname: string;\n\tstatus: string;\n}\n\n/** The subset of `/status` a diagnosis reads. Everything is optional:\n * an older instance may not send a field, and a missing field must never be\n * read as a failure. */\nexport interface PublicStatus {\n\tstatus?: string;\n\tchainTip?: number | null;\n\tchainIntegrity?: {\n\t\tok?: boolean;\n\t\tmaxHeight?: number;\n\t\treason?: string | null;\n\t};\n\tstreams?: {\n\t\tstatus?: string;\n\t\ttip?: { lag_seconds?: number; block_height?: number } | null;\n\t};\n\tindex?: { status?: string; decoders?: PublicStatusDecoder[] };\n\tservices?: PublicStatusService[];\n\ttimestamp?: string;\n\t[key: string]: unknown;\n}\n\nexport interface InstanceIssue {\n\t/** What is wrong, in one line. */\n\ttitle: string;\n\t/** Why — assembled only from what the payload actually reports. */\n\tdetail?: string;\n\t/** What to run next, most useful first. */\n\tnextSteps: string[];\n}\n\nexport type InstanceState = \"healthy\" | \"empty-index\" | \"degraded\";\n\nexport interface InstanceDiagnosis {\n\tstate: InstanceState;\n\t/** The API's own top-level verdict, passed through unmodified. */\n\toverall: string;\n\tissues: InstanceIssue[];\n}\n\nexport const BOOTSTRAP_STEP =\n\t\"Restore verified history: secondlayer bootstrap --against <manifest>\";\nexport const OBSERVER_STEP =\n\t\"Confirm your node streams into this instance — secondlayer observer --mode indexer --endpoint secondlayer:3700 prints the stanza its config needs\";\nexport const SELF_HOST_DOCS_STEP =\n\t\"Docs: https://www.secondlayer.tools/docs/self-host\";\n\n/** `true` when the instance is reachable but has never indexed a block. */\nexport function hasNoChainData(status: PublicStatus): boolean {\n\tconst integrity = status.chainIntegrity;\n\t// `check_failed` is the API's placeholder when the integrity query itself\n\t// errored — its maxHeight of 0 means \"unknown\", not \"empty\".\n\tif (!integrity || integrity.reason === \"check_failed\") return false;\n\tif (integrity.maxHeight !== 0) return false;\n\treturn (status.chainTip ?? null) === null;\n}\n\nfunction emptyIndexIssue(status: PublicStatus): InstanceIssue {\n\tconst decoders = status.index?.decoders ?? [];\n\tconst stalled = decoders.filter((d) => d.status !== \"ok\").length;\n\tconst decoderClause =\n\t\tstalled > 0\n\t\t\t? ` and ${stalled} decoder${stalled === 1 ? \"\" : \"s\"} report unavailable because there is nothing to decode`\n\t\t\t: \"\";\n\treturn {\n\t\ttitle: \"No blocks indexed yet — this instance holds 0 blocks.\",\n\t\tdetail: `Streams has no tip${decoderClause}, which is what makes the overall status DEGRADED. This is the expected state for a fresh instance: nothing is broken, but no read returns data until history lands.`,\n\t\tnextSteps: [BOOTSTRAP_STEP, OBSERVER_STEP, SELF_HOST_DOCS_STEP],\n\t};\n}\n\nconst SERVICE_STEPS = [\n\t\"See the raw per-service report: secondlayer status --json\",\n\t\"Check the containers from docker/oss: docker compose ps, then docker compose logs --tail=50 secondlayer\",\n];\n\n/**\n * Turn a `/status` payload into a verdict plus zero or more issues.\n * Only facts present in the payload become issues — when the API says degraded\n * and names no failing surface, the issue says exactly that rather than\n * guessing a cause.\n */\nexport function diagnoseInstanceStatus(\n\tstatus: PublicStatus,\n): InstanceDiagnosis {\n\tconst overall = String(status.status ?? \"unknown\");\n\n\tif (hasNoChainData(status)) {\n\t\treturn { state: \"empty-index\", overall, issues: [emptyIndexIssue(status)] };\n\t}\n\n\tconst issues: InstanceIssue[] = [];\n\tfor (const service of status.services ?? []) {\n\t\tif (service.status === \"ok\") continue;\n\t\tissues.push({\n\t\t\ttitle: `Service ${service.name} reports ${service.status}.`,\n\t\t\tnextSteps: SERVICE_STEPS,\n\t\t});\n\t}\n\n\tconst integrity = status.chainIntegrity;\n\tif (integrity && integrity.ok === false) {\n\t\tissues.push({\n\t\t\ttitle: \"Chain-data integrity check failed.\",\n\t\t\tdetail:\n\t\t\t\tintegrity.reason ??\n\t\t\t\t\"The tip the instance serves is not backed by the history it implies.\",\n\t\t\tnextSteps: [\n\t\t\t\t\"Verify against the archive: secondlayer verify all --against <manifest>\",\n\t\t\t\t\"Repair what diverged: secondlayer repair --against <manifest> --apply\",\n\t\t\t],\n\t\t});\n\t}\n\n\tif (issues.length === 0 && overall !== \"healthy\") {\n\t\t// Do not invent a cause: report the contradiction and where to look.\n\t\tissues.push({\n\t\t\ttitle: `The API reports status=${overall} but names no failing service.`,\n\t\t\tdetail:\n\t\t\t\t\"Every service in the report is ok, so the degradation is in a surface this command does not summarize.\",\n\t\t\tnextSteps: SERVICE_STEPS,\n\t\t});\n\t}\n\n\treturn {\n\t\tstate: issues.length > 0 ? \"degraded\" : \"healthy\",\n\t\toverall,\n\t\tissues,\n\t};\n}\n\n// ── context ────────────────────────────────────────────────────────────\n\nexport interface ContextProbe {\n\t/** The endpoint the snapshot was read from. */\n\tapiUrl: string;\n\t/** `false` only for the metered archive deployment, which has accounts. */\n\tselfHosted: boolean;\n\t/** `true` when INSTANCE_TOKEN / SECONDLAYER_API_KEY resolved to a value. */\n\thasCredential: boolean;\n\t/** `/public/status` when it answered, `null` when the probe failed. */\n\tstatus: PublicStatus | null;\n\t/** Why the probe failed, when it did. */\n\tstatusError?: string;\n}\n\nexport interface ContextExplanation {\n\t/** One line naming the likeliest cause of the nulls below. */\n\tsummary: string;\n\t/** Per-null-field reason. Fields that carry a value are absent. */\n\tnulls: Record<string, string>;\n}\n\nconst CONTEXT_FIELDS = [\n\t\"account\",\n\t\"streamsTip\",\n\t\"indexTip\",\n\t\"subgraphs\",\n\t\"webhooks\",\n\t\"activeOperations\",\n] as const;\n\n/**\n * Explain every null in a `context` snapshot.\n *\n * The SDK degrades each read to `null` on failure, so an all-null snapshot is\n * indistinguishable from \"not bootstrapped\", \"unauthorized\", and \"broken\" —\n * which is exactly what a caller needs to tell apart. Each reason here is\n * derived from a live probe of the same instance, never assumed.\n */\nexport function explainContextNulls(\n\tsnapshot: Record<string, unknown>,\n\tprobe: ContextProbe,\n): ContextExplanation {\n\tconst nullFields = CONTEXT_FIELDS.filter(\n\t\t(field) => (snapshot[field] ?? null) === null,\n\t);\n\n\tif (nullFields.length === 0) {\n\t\treturn { summary: \"Every field resolved.\", nulls: {} };\n\t}\n\n\tif (!probe.status) {\n\t\tconst reason = `No instance answered at ${probe.apiUrl} (${probe.statusError ?? \"unreachable\"}), so this read never reached an API.`;\n\t\treturn {\n\t\t\tsummary: `${reason} Start it from docker/oss with: docker compose up -d — or point SECONDLAYER_API_URL at the instance you meant.`,\n\t\t\tnulls: Object.fromEntries(nullFields.map((field) => [field, reason])),\n\t\t};\n\t}\n\n\tconst emptyIndex = hasNoChainData(probe.status);\n\tconst blocks = probe.status.chainIntegrity?.maxHeight ?? null;\n\tconst accountReason = probe.selfHosted\n\t\t? \"Expected: a self-hosted instance has no account system. Accounts exist only on the metered archive deployment at api.secondlayer.tools, so null here is normal and not a failure.\"\n\t\t: \"Not authenticated — run `secondlayer login`, or export INSTANCE_TOKEN.\";\n\n\t// An empty index explains the chain tips and nothing else: the subgraph and\n\t// webhook lists are control-plane reads that answer on a bare instance.\n\tconst listReason = unresolvedReadReason(probe, blocks);\n\tconst chainReason = emptyIndex\n\t\t? `No blocks indexed yet — this instance holds 0 blocks, so there is no tip to report. ${BOOTSTRAP_STEP}`\n\t\t: listReason;\n\n\tconst nulls: Record<string, string> = {};\n\tfor (const field of nullFields) {\n\t\tif (field === \"account\") nulls[field] = accountReason;\n\t\telse if (field === \"streamsTip\" || field === \"indexTip\")\n\t\t\tnulls[field] = chainReason;\n\t\telse if (field === \"activeOperations\")\n\t\t\tnulls[field] =\n\t\t\t\t(snapshot.subgraphs ?? null) === null\n\t\t\t\t\t? \"Not probed: in-flight operations are read per subgraph, and the subgraph list did not resolve.\"\n\t\t\t\t\t: listReason;\n\t\telse nulls[field] = listReason;\n\t}\n\n\tconst summary = emptyIndex\n\t\t? `Instance at ${probe.apiUrl} is reachable but holds 0 blocks — the chain fields are null because nothing has been indexed yet. ${BOOTSTRAP_STEP}`\n\t\t: nullFields.length === 1 && nullFields[0] === \"account\"\n\t\t\t? `Instance at ${probe.apiUrl} is reachable and indexed to block ${blocks ?? \"unknown\"}; only \\`account\\` is null, which is normal when self-hosting.`\n\t\t\t: `Instance at ${probe.apiUrl} is reachable, but ${nullFields.length} field${nullFields.length === 1 ? \"\" : \"s\"} did not resolve — see \\`nulls\\` below for each one.`;\n\n\treturn { summary, nulls };\n}\n\nfunction unresolvedReadReason(\n\tprobe: ContextProbe,\n\tblocks: number | null,\n): string {\n\tconst where = `The instance at ${probe.apiUrl} is reachable${blocks !== null ? ` and indexed to block ${blocks}` : \"\"}, but this read returned nothing.`;\n\treturn probe.hasCredential\n\t\t? `${where} Check the surface directly: secondlayer status --json`\n\t\t: `${where} No credential resolved: export INSTANCE_TOKEN — an instance published past loopback requires it on every /v1 read.`;\n}\n"
5
+ "/**\n * One reading of `/status` (or `/public/status`), shared by the CLI\n * (`status`, `doctor`, `context`) and the SDK (`sl.instance.diagnose`).\n *\n * These surfaces used to each interpret the same payload on their own and\n * disagreed in the most common first-run state: on a freshly self-hosted\n * instance with no chain data, `status` printed DEGRADED while `doctor` printed\n * \"All checks passed\", and neither named the cause or a next command. An\n * operator (or an agent) reading both had no way to tell whether it was safe to\n * proceed. The verdict now lives here so the surfaces cannot drift apart, and\n * every issue carries the exact command that resolves it.\n */\n\nexport interface PublicStatusDecoder {\n\tdecoder: string;\n\tstatus: string;\n}\n\nexport interface PublicStatusService {\n\tname: string;\n\tstatus: string;\n}\n\n/** The subset of `/status` a diagnosis reads. Everything is optional:\n * an older instance may not send a field, and a missing field must never be\n * read as a failure. */\nexport interface PublicStatus {\n\tstatus?: string;\n\tchainTip?: number | null;\n\tchainIntegrity?: {\n\t\tok?: boolean;\n\t\tmaxHeight?: number;\n\t\treason?: string | null;\n\t};\n\tstreams?: {\n\t\tstatus?: string;\n\t\ttip?: { lag_seconds?: number; block_height?: number } | null;\n\t};\n\tindex?: { status?: string; decoders?: PublicStatusDecoder[] };\n\tservices?: PublicStatusService[];\n\ttimestamp?: string;\n\t[key: string]: unknown;\n}\n\nexport interface InstanceIssue {\n\t/** What is wrong, in one line. */\n\ttitle: string;\n\t/** Why — assembled only from what the payload actually reports. */\n\tdetail?: string;\n\t/** What to run next, most useful first. */\n\tnextSteps: string[];\n}\n\nexport type InstanceState = \"healthy\" | \"empty-index\" | \"degraded\";\n\n/** An instance's state, the API verdict, and the issues found. */\nexport interface InstanceDiagnosis {\n\tstate: InstanceState;\n\t/** The API's own top-level verdict, passed through unmodified. */\n\toverall: string;\n\tissues: InstanceIssue[];\n}\n\nexport const BOOTSTRAP_STEP =\n\t\"Restore verified history: secondlayer bootstrap --against <manifest>\";\nexport const OBSERVER_STEP =\n\t\"Confirm your node streams into this instance — secondlayer observer --mode indexer --endpoint secondlayer:3700 prints the stanza its config needs\";\nexport const SELF_HOST_DOCS_STEP =\n\t\"Docs: https://www.secondlayer.tools/docs/self-host\";\n\n/** `true` when the instance is reachable but has never indexed a block. */\nexport function hasNoChainData(status: PublicStatus): boolean {\n\tconst integrity = status.chainIntegrity;\n\t// `check_failed` is the API's placeholder when the integrity query itself\n\t// errored — its maxHeight of 0 means \"unknown\", not \"empty\".\n\tif (!integrity || integrity.reason === \"check_failed\") return false;\n\tif (integrity.maxHeight !== 0) return false;\n\treturn (status.chainTip ?? null) === null;\n}\n\nfunction emptyIndexIssue(status: PublicStatus): InstanceIssue {\n\tconst decoders = status.index?.decoders ?? [];\n\tconst stalled = decoders.filter((d) => d.status !== \"ok\").length;\n\tconst decoderClause =\n\t\tstalled > 0\n\t\t\t? ` and ${stalled} decoder${stalled === 1 ? \"\" : \"s\"} report unavailable because there is nothing to decode`\n\t\t\t: \"\";\n\treturn {\n\t\ttitle: \"No blocks indexed yet — this instance holds 0 blocks.\",\n\t\tdetail: `Streams has no tip${decoderClause}, which is what makes the overall status DEGRADED. This is the expected state for a fresh instance: nothing is broken, but no read returns data until history lands.`,\n\t\tnextSteps: [BOOTSTRAP_STEP, OBSERVER_STEP, SELF_HOST_DOCS_STEP],\n\t};\n}\n\nconst SERVICE_STEPS = [\n\t\"See the raw per-service report: secondlayer status --json\",\n\t\"Check the containers from docker/oss: docker compose ps, then docker compose logs --tail=50 secondlayer\",\n];\n\n/**\n * Turn a `/status` payload into a verdict plus zero or more issues.\n * Only facts present in the payload become issues — when the API says degraded\n * and names no failing surface, the issue says exactly that rather than\n * guessing a cause.\n */\nexport function diagnoseInstanceStatus(\n\tstatus: PublicStatus,\n): InstanceDiagnosis {\n\tconst overall = String(status.status ?? \"unknown\");\n\n\tif (hasNoChainData(status)) {\n\t\treturn { state: \"empty-index\", overall, issues: [emptyIndexIssue(status)] };\n\t}\n\n\tconst issues: InstanceIssue[] = [];\n\tfor (const service of status.services ?? []) {\n\t\tif (service.status === \"ok\") continue;\n\t\tissues.push({\n\t\t\ttitle: `Service ${service.name} reports ${service.status}.`,\n\t\t\tnextSteps: SERVICE_STEPS,\n\t\t});\n\t}\n\n\tconst integrity = status.chainIntegrity;\n\tif (integrity && integrity.ok === false) {\n\t\tissues.push({\n\t\t\ttitle: \"Chain-data integrity check failed.\",\n\t\t\tdetail:\n\t\t\t\tintegrity.reason ??\n\t\t\t\t\"The tip the instance serves is not backed by the history it implies.\",\n\t\t\tnextSteps: [\n\t\t\t\t\"Verify against the archive: secondlayer verify all --against <manifest>\",\n\t\t\t\t\"Repair what diverged: secondlayer repair --against <manifest> --apply\",\n\t\t\t],\n\t\t});\n\t}\n\n\tif (issues.length === 0 && overall !== \"healthy\") {\n\t\t// Do not invent a cause: report the contradiction and where to look.\n\t\tissues.push({\n\t\t\ttitle: `The API reports status=${overall} but names no failing service.`,\n\t\t\tdetail:\n\t\t\t\t\"Every service in the report is ok, so the degradation is in a surface this command does not summarize.\",\n\t\t\tnextSteps: SERVICE_STEPS,\n\t\t});\n\t}\n\n\treturn {\n\t\tstate: issues.length > 0 ? \"degraded\" : \"healthy\",\n\t\toverall,\n\t\tissues,\n\t};\n}\n\n// ── context ────────────────────────────────────────────────────────────\n\nexport interface ContextProbe {\n\t/** The endpoint the snapshot was read from. */\n\tapiUrl: string;\n\t/** `false` only for the metered archive deployment, which has accounts. */\n\tselfHosted: boolean;\n\t/** `true` when INSTANCE_TOKEN / SECONDLAYER_API_KEY resolved to a value. */\n\thasCredential: boolean;\n\t/** `/public/status` when it answered, `null` when the probe failed. */\n\tstatus: PublicStatus | null;\n\t/** Why the probe failed, when it did. */\n\tstatusError?: string;\n}\n\nexport interface ContextExplanation {\n\t/** One line naming the likeliest cause of the nulls below. */\n\tsummary: string;\n\t/** Per-null-field reason. Fields that carry a value are absent. */\n\tnulls: Record<string, string>;\n}\n\nconst CONTEXT_FIELDS = [\n\t\"account\",\n\t\"streamsTip\",\n\t\"indexTip\",\n\t\"subgraphs\",\n\t\"webhooks\",\n\t\"activeOperations\",\n] as const;\n\n/**\n * Explain every null in a `context` snapshot.\n *\n * The SDK degrades each read to `null` on failure, so an all-null snapshot is\n * indistinguishable from \"not bootstrapped\", \"unauthorized\", and \"broken\" —\n * which is exactly what a caller needs to tell apart. Each reason here is\n * derived from a live probe of the same instance, never assumed.\n */\nexport function explainContextNulls(\n\tsnapshot: Record<string, unknown>,\n\tprobe: ContextProbe,\n): ContextExplanation {\n\tconst nullFields = CONTEXT_FIELDS.filter(\n\t\t(field) => (snapshot[field] ?? null) === null,\n\t);\n\n\tif (nullFields.length === 0) {\n\t\treturn { summary: \"Every field resolved.\", nulls: {} };\n\t}\n\n\tif (!probe.status) {\n\t\tconst reason = `No instance answered at ${probe.apiUrl} (${probe.statusError ?? \"unreachable\"}), so this read never reached an API.`;\n\t\treturn {\n\t\t\tsummary: `${reason} Start it from docker/oss with: docker compose up -d — or point SECONDLAYER_API_URL at the instance you meant.`,\n\t\t\tnulls: Object.fromEntries(nullFields.map((field) => [field, reason])),\n\t\t};\n\t}\n\n\tconst emptyIndex = hasNoChainData(probe.status);\n\tconst blocks = probe.status.chainIntegrity?.maxHeight ?? null;\n\tconst accountReason = probe.selfHosted\n\t\t? \"Expected: a self-hosted instance has no account system. Accounts exist only on the metered archive deployment at api.secondlayer.tools, so null here is normal and not a failure.\"\n\t\t: \"Not authenticated — run `secondlayer login`, or export INSTANCE_TOKEN.\";\n\n\t// An empty index explains the chain tips and nothing else: the subgraph and\n\t// webhook lists are control-plane reads that answer on a bare instance.\n\tconst listReason = unresolvedReadReason(probe, blocks);\n\tconst chainReason = emptyIndex\n\t\t? `No blocks indexed yet — this instance holds 0 blocks, so there is no tip to report. ${BOOTSTRAP_STEP}`\n\t\t: listReason;\n\n\tconst nulls: Record<string, string> = {};\n\tfor (const field of nullFields) {\n\t\tif (field === \"account\") nulls[field] = accountReason;\n\t\telse if (field === \"streamsTip\" || field === \"indexTip\")\n\t\t\tnulls[field] = chainReason;\n\t\telse if (field === \"activeOperations\")\n\t\t\tnulls[field] =\n\t\t\t\t(snapshot.subgraphs ?? null) === null\n\t\t\t\t\t? \"Not probed: in-flight operations are read per subgraph, and the subgraph list did not resolve.\"\n\t\t\t\t\t: listReason;\n\t\telse nulls[field] = listReason;\n\t}\n\n\tconst summary = emptyIndex\n\t\t? `Instance at ${probe.apiUrl} is reachable but holds 0 blocks — the chain fields are null because nothing has been indexed yet. ${BOOTSTRAP_STEP}`\n\t\t: nullFields.length === 1 && nullFields[0] === \"account\"\n\t\t\t? `Instance at ${probe.apiUrl} is reachable and indexed to block ${blocks ?? \"unknown\"}; only \\`account\\` is null, which is normal when self-hosting.`\n\t\t\t: `Instance at ${probe.apiUrl} is reachable, but ${nullFields.length} field${nullFields.length === 1 ? \"\" : \"s\"} did not resolve — see \\`nulls\\` below for each one.`;\n\n\treturn { summary, nulls };\n}\n\nfunction unresolvedReadReason(\n\tprobe: ContextProbe,\n\tblocks: number | null,\n): string {\n\tconst where = `The instance at ${probe.apiUrl} is reachable${blocks !== null ? ` and indexed to block ${blocks}` : \"\"}, but this read returned nothing.`;\n\treturn probe.hasCredential\n\t\t? `${where} Check the surface directly: secondlayer status --json`\n\t\t: `${where} No credential resolved: export INSTANCE_TOKEN — an instance published past loopback requires it on every /v1 read.`;\n}\n"
6
6
  ],
7
- "mappings": "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",
7
+ "mappings": "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",
8
8
  "debugId": "774E7BBD29A35EA564756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -284,12 +284,14 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
291
291
  last_used_at: Date | null;
292
292
  revoked_at: Date | null;
293
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
294
+ expires_at: Date | null;
293
295
  created_at: Generated<Date>;
294
296
  }
295
297
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -707,7 +709,7 @@ interface PendingForkBlocksTable {
707
709
  payload: ColumnType<unknown, unknown, unknown>;
708
710
  received_at: Generated<Date>;
709
711
  }
710
- type ObserverJournalStatus = "received" | "processed" | "failed";
712
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
711
713
  /**
712
714
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
713
715
  * processing columns are updated only after the current derived-state write
@@ -101,6 +101,7 @@ interface PartitionSemanticDigest {
101
101
  * where the root is two levels above the manifest
102
102
  * (`<root>/snapshots/<digest>.json`).
103
103
  */
104
+ /** One archive file: dataset, block range, path, rows, bytes. */
104
105
  type ArchivePartition = {
105
106
  dataset: string
106
107
  from_block: number
@@ -2,12 +2,12 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/archive/reference.ts", "../src/crypto/ed25519.ts", "../src/streams-bulk-manifest.ts", "../src/archive/root-key.ts"],
4
4
  "sourcesContent": [
5
- "import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { verifyStreamsBulkManifestSignature } from \"../streams-bulk-manifest.ts\";\nimport type { RangeDigest } from \"./range-digest.ts\";\nimport { ARCHIVE_ROOT_PUBLIC_KEY_PEM } from \"./root-key.ts\";\nimport type { PartitionSemanticDigest } from \"./semantic-digest.ts\";\n\n/**\n * Loading and trusting an archive reference — shared by the CLI and the SDK\n * so the two cannot disagree about what \"the archive says\".\n *\n * A reference is either an https URL or a local path to a snapshot manifest.\n * The SDK only passes `http(s)://` sources; local paths stay CLI. Partition\n * objects live beside the snapshot at `<root>/<dataset>/<name>.parquet`,\n * where the root is two levels above the manifest\n * (`<root>/snapshots/<digest>.json`).\n */\n\nexport type ArchivePartition = {\n\tdataset: string;\n\tfrom_block: number;\n\tto_block: number;\n\tpath: string;\n\trow_count: number;\n\tbyte_size: number;\n\tsha256: string;\n};\n\nexport type ArchiveManifest = {\n\tnetwork?: string;\n\tcoverage?: { from_block: number; to_block: number };\n\tpartition_size_blocks?: number;\n\trange_digests?: RangeDigest[];\n\tpartition_semantic_digests?: PartitionSemanticDigest[];\n\tpartitions?: ArchivePartition[];\n\tsignature?: string;\n\tkey_id?: string;\n\t[key: string]: unknown;\n};\n\nexport type LoadedReference = {\n\tmanifest: ArchiveManifest;\n\torigin: string;\n\t/** Base for resolving partition paths; a URL prefix or a directory. */\n\troot: string;\n\tisRemote: boolean;\n};\n\n/**\n * The archive root, `latest.json`, is a signed POINTER at a snapshot manifest,\n * not a manifest itself. It is also the only URL a user can be expected to\n * know, so every command accepts it and follows it — anything else makes the\n * obvious command fail with an error about digests, which reads as \"this tool\n * is broken\" rather than \"you passed the wrong file\".\n */\ntype LatestPointer = {\n\tsnapshot_path?: string;\n\tsnapshot_digest?: string;\n\tsignature?: string;\n\tkey_id?: string;\n};\n\nfunction isLatestPointer(value: ArchiveManifest): boolean {\n\tconst pointer = value as LatestPointer;\n\treturn (\n\t\ttypeof pointer.snapshot_path === \"string\" &&\n\t\t(value.partitions === undefined || value.partitions.length === 0)\n\t);\n}\n\n/** The publisher names snapshots by their content digest and nothing else\n * (`packages/indexer/src/archive/promote-snapshot.ts`). Anything looser is\n * a pointer someone rewrote, and it must not steer a fetch. */\nconst SNAPSHOT_PATH_PATTERN = /^snapshots\\/[0-9a-f]{64}\\.json$/;\n\nasync function fetchJson(\n\turl: string,\n\tfetchImpl: (\n\t\tinput: string | URL | Request,\n\t\tinit?: RequestInit,\n\t) => Promise<Response>,\n): Promise<unknown> {\n\tconst response = await fetchImpl(url, {\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`could not fetch manifest (${response.status} ${response.statusText})`,\n\t\t);\n\t}\n\treturn response.json();\n}\n\nexport type LoadReferenceOptions = {\n\t/** Key used to verify a signed `latest.json` pointer. The pointer names\n\t * which snapshot is current, so without this an attacker holding only\n\t * bucket write access could roll every reader back to an older, still\n\t * validly signed, snapshot. */\n\tpublicKeyPem?: string;\n\t/** Test/SDK seam; production CLI leaves this unset and uses global fetch. */\n\tfetchImpl?: (\n\t\tinput: string | URL | Request,\n\t\tinit?: RequestInit,\n\t) => Promise<Response>;\n};\n\nexport async function loadReference(\n\tsource: string,\n\toptions: LoadReferenceOptions = {},\n): Promise<LoadedReference> {\n\tconst isRemote = /^https?:\\/\\//.test(source);\n\tconst fetchImpl = options.fetchImpl ?? globalThis.fetch;\n\tlet manifest: ArchiveManifest;\n\tlet root: string;\n\n\tif (isRemote) {\n\t\tmanifest = (await fetchJson(source, fetchImpl)) as ArchiveManifest;\n\t\tconst url = new URL(source);\n\t\t// Both `<root>/latest.json` and `<root>/snapshots/<digest>.json` resolve\n\t\t// their partition paths against `<root>`.\n\t\turl.pathname = url.pathname\n\t\t\t.replace(/\\/snapshots\\/[^/]+$/, \"\")\n\t\t\t.replace(/\\/[^/]+\\.json$/, \"\");\n\t\troot = url.toString();\n\t} else {\n\t\tconst path = resolve(source);\n\t\tmanifest = JSON.parse(await readFile(path, \"utf8\")) as ArchiveManifest;\n\t\troot = isLatestPointer(manifest) ? dirname(path) : dirname(dirname(path));\n\t}\n\n\tif (!isLatestPointer(manifest)) {\n\t\treturn { manifest, origin: source, root, isRemote };\n\t}\n\n\t// Follow the pointer, then prove it led where it claimed. A tampered\n\t// pointer could otherwise redirect to a different — and still validly\n\t// signed — snapshot, which is a downgrade attack rather than a forgery.\n\tconst pointer = manifest as ArchiveManifest & LatestPointer;\n\tconst snapshotPath = pointer.snapshot_path as string;\n\tif (!SNAPSHOT_PATH_PATTERN.test(snapshotPath)) {\n\t\tthrow new Error(\n\t\t\t`pointer names an invalid snapshot path: ${snapshotPath} (expected snapshots/<sha256>.json)`,\n\t\t);\n\t}\n\tif (\n\t\ttypeof pointer.snapshot_digest !== \"string\" ||\n\t\t!/^[0-9a-f]{64}$/.test(pointer.snapshot_digest)\n\t) {\n\t\tthrow new Error(\n\t\t\t\"pointer carries no snapshot_digest, so it cannot prove which snapshot is current\",\n\t\t);\n\t}\n\tif (pointer.signature !== undefined) {\n\t\tif (!options.publicKeyPem) {\n\t\t\tthrow new Error(\n\t\t\t\t\"pointer is signed but no public key is available to verify it\",\n\t\t\t);\n\t\t}\n\t\tlet verified = false;\n\t\ttry {\n\t\t\tverified = verifyStreamsBulkManifestSignature(\n\t\t\t\tpointer as Record<string, unknown>,\n\t\t\t\toptions.publicKeyPem,\n\t\t\t);\n\t\t} catch {\n\t\t\tverified = false;\n\t\t}\n\t\tif (!verified) {\n\t\t\tthrow new Error(\n\t\t\t\t\"pointer signature did not verify against the archive key\",\n\t\t\t);\n\t\t}\n\t}\n\n\tconst resolved = isRemote\n\t\t? ((await fetchJson(\n\t\t\t\t`${root.replace(/\\/$/, \"\")}/${snapshotPath}`,\n\t\t\t\tfetchImpl,\n\t\t\t)) as ArchiveManifest)\n\t\t: (JSON.parse(\n\t\t\t\tawait readFile(join(root, snapshotPath), \"utf8\"),\n\t\t\t) as ArchiveManifest);\n\n\tconst digest = createHash(\"sha256\")\n\t\t.update(\n\t\t\tJSON.stringify(\n\t\t\t\t(({ signature: _s, key_id: _k, ...rest }) => rest)(\n\t\t\t\t\tresolved as Record<string, unknown>,\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t\t.digest(\"hex\");\n\tif (digest !== pointer.snapshot_digest) {\n\t\tthrow new Error(\n\t\t\t`pointer/snapshot mismatch: latest.json names ${pointer.snapshot_digest}, resolved manifest hashes to ${digest}`,\n\t\t);\n\t}\n\n\treturn { manifest: resolved, origin: source, root, isRemote };\n}\n\nexport type SignatureResult = { verified: boolean; reason?: string };\n\n/**\n * An unsigned or unverifiable reference is worthless as a source of truth:\n * comparing against it yields a confident answer with nothing behind it, and\n * repairing from it would write unverified data into a live database.\n */\nexport function checkSignature(\n\tmanifest: ArchiveManifest,\n\tpublicKeyPem: string | undefined,\n\tinsecure: boolean,\n): SignatureResult {\n\tif (insecure) return { verified: false, reason: \"signature check skipped\" };\n\tif (!manifest.signature) {\n\t\treturn { verified: false, reason: \"manifest carries no signature\" };\n\t}\n\tif (!publicKeyPem) {\n\t\treturn {\n\t\t\tverified: false,\n\t\t\treason: \"no public key available to verify with\",\n\t\t};\n\t}\n\ttry {\n\t\tconst verified = verifyStreamsBulkManifestSignature(\n\t\t\tmanifest as Record<string, unknown>,\n\t\t\tpublicKeyPem,\n\t\t);\n\t\treturn {\n\t\t\tverified,\n\t\t\treason: verified ? undefined : \"signature did not verify\",\n\t\t};\n\t} catch (err) {\n\t\treturn {\n\t\t\tverified: false,\n\t\t\treason: err instanceof Error ? err.message : \"signature check failed\",\n\t\t};\n\t}\n}\n\nconst HOSTED_SIGNING_KEY_URL =\n\t\"https://api.secondlayer.tools/public/streams/signing-key\";\n\nasync function fetchHostedKey(url: string): Promise<string | undefined> {\n\t// A key fetched over plaintext is whoever sits on the wire, not a key.\n\tif (!url.startsWith(\"https://\")) return undefined;\n\ttry {\n\t\tconst response = await fetch(url, { signal: AbortSignal.timeout(10_000) });\n\t\tif (!response.ok) return undefined;\n\t\tconst body = (await response.json()) as { public_key_pem?: string };\n\t\treturn typeof body.public_key_pem === \"string\"\n\t\t\t? body.public_key_pem\n\t\t\t: undefined;\n\t} catch {\n\t\t// Offline is a legitimate state; the compiled root key still applies.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Which key a manifest is checked against, in trust order: an explicit\n * `--public-key`, then `ARCHIVE_SIGNING_PUBLIC_KEY` / `STREAMS_SIGNING_PUBLIC_KEY`\n * from the environment, then the hosted key endpoint over https (never in OSS\n * mode, which must work with no path to api.secondlayer.tools), and finally the\n * key compiled into this release. The compiled key means a fresh self-hosted\n * instance can bootstrap offline; the tradeoff is that rotating it is a CLI\n * release. Plaintext `http://` key sources are never consulted.\n */\nexport async function resolveArchivePublicKey(input: {\n\texplicitPem?: string;\n\tenvPem?: string;\n\tallowHostedApi?: boolean;\n\t/** Test seam for the hosted endpoint; production callers leave it unset. */\n\thostedKeyUrl?: string;\n}): Promise<string> {\n\tif (input.explicitPem) return input.explicitPem;\n\tif (input.envPem) return input.envPem;\n\tif (input.allowHostedApi) {\n\t\tconst hosted = await fetchHostedKey(\n\t\t\tinput.hostedKeyUrl ?? HOSTED_SIGNING_KEY_URL,\n\t\t);\n\t\tif (hosted) return hosted;\n\t}\n\treturn ARCHIVE_ROOT_PUBLIC_KEY_PEM;\n}\n",
5
+ "import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { verifyStreamsBulkManifestSignature } from \"../streams-bulk-manifest.ts\";\nimport type { RangeDigest } from \"./range-digest.ts\";\nimport { ARCHIVE_ROOT_PUBLIC_KEY_PEM } from \"./root-key.ts\";\nimport type { PartitionSemanticDigest } from \"./semantic-digest.ts\";\n\n/**\n * Loading and trusting an archive reference — shared by the CLI and the SDK\n * so the two cannot disagree about what \"the archive says\".\n *\n * A reference is either an https URL or a local path to a snapshot manifest.\n * The SDK only passes `http(s)://` sources; local paths stay CLI. Partition\n * objects live beside the snapshot at `<root>/<dataset>/<name>.parquet`,\n * where the root is two levels above the manifest\n * (`<root>/snapshots/<digest>.json`).\n */\n\n/** One archive file: dataset, block range, path, rows, bytes. */\nexport type ArchivePartition = {\n\tdataset: string;\n\tfrom_block: number;\n\tto_block: number;\n\tpath: string;\n\trow_count: number;\n\tbyte_size: number;\n\tsha256: string;\n};\n\nexport type ArchiveManifest = {\n\tnetwork?: string;\n\tcoverage?: { from_block: number; to_block: number };\n\tpartition_size_blocks?: number;\n\trange_digests?: RangeDigest[];\n\tpartition_semantic_digests?: PartitionSemanticDigest[];\n\tpartitions?: ArchivePartition[];\n\tsignature?: string;\n\tkey_id?: string;\n\t[key: string]: unknown;\n};\n\nexport type LoadedReference = {\n\tmanifest: ArchiveManifest;\n\torigin: string;\n\t/** Base for resolving partition paths; a URL prefix or a directory. */\n\troot: string;\n\tisRemote: boolean;\n};\n\n/**\n * The archive root, `latest.json`, is a signed POINTER at a snapshot manifest,\n * not a manifest itself. It is also the only URL a user can be expected to\n * know, so every command accepts it and follows it — anything else makes the\n * obvious command fail with an error about digests, which reads as \"this tool\n * is broken\" rather than \"you passed the wrong file\".\n */\ntype LatestPointer = {\n\tsnapshot_path?: string;\n\tsnapshot_digest?: string;\n\tsignature?: string;\n\tkey_id?: string;\n};\n\nfunction isLatestPointer(value: ArchiveManifest): boolean {\n\tconst pointer = value as LatestPointer;\n\treturn (\n\t\ttypeof pointer.snapshot_path === \"string\" &&\n\t\t(value.partitions === undefined || value.partitions.length === 0)\n\t);\n}\n\n/** The publisher names snapshots by their content digest and nothing else\n * (`packages/indexer/src/archive/promote-snapshot.ts`). Anything looser is\n * a pointer someone rewrote, and it must not steer a fetch. */\nconst SNAPSHOT_PATH_PATTERN = /^snapshots\\/[0-9a-f]{64}\\.json$/;\n\nasync function fetchJson(\n\turl: string,\n\tfetchImpl: (\n\t\tinput: string | URL | Request,\n\t\tinit?: RequestInit,\n\t) => Promise<Response>,\n): Promise<unknown> {\n\tconst response = await fetchImpl(url, {\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\tif (!response.ok) {\n\t\tthrow new Error(\n\t\t\t`could not fetch manifest (${response.status} ${response.statusText})`,\n\t\t);\n\t}\n\treturn response.json();\n}\n\nexport type LoadReferenceOptions = {\n\t/** Key used to verify a signed `latest.json` pointer. The pointer names\n\t * which snapshot is current, so without this an attacker holding only\n\t * bucket write access could roll every reader back to an older, still\n\t * validly signed, snapshot. */\n\tpublicKeyPem?: string;\n\t/** Test/SDK seam; production CLI leaves this unset and uses global fetch. */\n\tfetchImpl?: (\n\t\tinput: string | URL | Request,\n\t\tinit?: RequestInit,\n\t) => Promise<Response>;\n};\n\nexport async function loadReference(\n\tsource: string,\n\toptions: LoadReferenceOptions = {},\n): Promise<LoadedReference> {\n\tconst isRemote = /^https?:\\/\\//.test(source);\n\tconst fetchImpl = options.fetchImpl ?? globalThis.fetch;\n\tlet manifest: ArchiveManifest;\n\tlet root: string;\n\n\tif (isRemote) {\n\t\tmanifest = (await fetchJson(source, fetchImpl)) as ArchiveManifest;\n\t\tconst url = new URL(source);\n\t\t// Both `<root>/latest.json` and `<root>/snapshots/<digest>.json` resolve\n\t\t// their partition paths against `<root>`.\n\t\turl.pathname = url.pathname\n\t\t\t.replace(/\\/snapshots\\/[^/]+$/, \"\")\n\t\t\t.replace(/\\/[^/]+\\.json$/, \"\");\n\t\troot = url.toString();\n\t} else {\n\t\tconst path = resolve(source);\n\t\tmanifest = JSON.parse(await readFile(path, \"utf8\")) as ArchiveManifest;\n\t\troot = isLatestPointer(manifest) ? dirname(path) : dirname(dirname(path));\n\t}\n\n\tif (!isLatestPointer(manifest)) {\n\t\treturn { manifest, origin: source, root, isRemote };\n\t}\n\n\t// Follow the pointer, then prove it led where it claimed. A tampered\n\t// pointer could otherwise redirect to a different — and still validly\n\t// signed — snapshot, which is a downgrade attack rather than a forgery.\n\tconst pointer = manifest as ArchiveManifest & LatestPointer;\n\tconst snapshotPath = pointer.snapshot_path as string;\n\tif (!SNAPSHOT_PATH_PATTERN.test(snapshotPath)) {\n\t\tthrow new Error(\n\t\t\t`pointer names an invalid snapshot path: ${snapshotPath} (expected snapshots/<sha256>.json)`,\n\t\t);\n\t}\n\tif (\n\t\ttypeof pointer.snapshot_digest !== \"string\" ||\n\t\t!/^[0-9a-f]{64}$/.test(pointer.snapshot_digest)\n\t) {\n\t\tthrow new Error(\n\t\t\t\"pointer carries no snapshot_digest, so it cannot prove which snapshot is current\",\n\t\t);\n\t}\n\tif (pointer.signature !== undefined) {\n\t\tif (!options.publicKeyPem) {\n\t\t\tthrow new Error(\n\t\t\t\t\"pointer is signed but no public key is available to verify it\",\n\t\t\t);\n\t\t}\n\t\tlet verified = false;\n\t\ttry {\n\t\t\tverified = verifyStreamsBulkManifestSignature(\n\t\t\t\tpointer as Record<string, unknown>,\n\t\t\t\toptions.publicKeyPem,\n\t\t\t);\n\t\t} catch {\n\t\t\tverified = false;\n\t\t}\n\t\tif (!verified) {\n\t\t\tthrow new Error(\n\t\t\t\t\"pointer signature did not verify against the archive key\",\n\t\t\t);\n\t\t}\n\t}\n\n\tconst resolved = isRemote\n\t\t? ((await fetchJson(\n\t\t\t\t`${root.replace(/\\/$/, \"\")}/${snapshotPath}`,\n\t\t\t\tfetchImpl,\n\t\t\t)) as ArchiveManifest)\n\t\t: (JSON.parse(\n\t\t\t\tawait readFile(join(root, snapshotPath), \"utf8\"),\n\t\t\t) as ArchiveManifest);\n\n\tconst digest = createHash(\"sha256\")\n\t\t.update(\n\t\t\tJSON.stringify(\n\t\t\t\t(({ signature: _s, key_id: _k, ...rest }) => rest)(\n\t\t\t\t\tresolved as Record<string, unknown>,\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t\t.digest(\"hex\");\n\tif (digest !== pointer.snapshot_digest) {\n\t\tthrow new Error(\n\t\t\t`pointer/snapshot mismatch: latest.json names ${pointer.snapshot_digest}, resolved manifest hashes to ${digest}`,\n\t\t);\n\t}\n\n\treturn { manifest: resolved, origin: source, root, isRemote };\n}\n\nexport type SignatureResult = { verified: boolean; reason?: string };\n\n/**\n * An unsigned or unverifiable reference is worthless as a source of truth:\n * comparing against it yields a confident answer with nothing behind it, and\n * repairing from it would write unverified data into a live database.\n */\nexport function checkSignature(\n\tmanifest: ArchiveManifest,\n\tpublicKeyPem: string | undefined,\n\tinsecure: boolean,\n): SignatureResult {\n\tif (insecure) return { verified: false, reason: \"signature check skipped\" };\n\tif (!manifest.signature) {\n\t\treturn { verified: false, reason: \"manifest carries no signature\" };\n\t}\n\tif (!publicKeyPem) {\n\t\treturn {\n\t\t\tverified: false,\n\t\t\treason: \"no public key available to verify with\",\n\t\t};\n\t}\n\ttry {\n\t\tconst verified = verifyStreamsBulkManifestSignature(\n\t\t\tmanifest as Record<string, unknown>,\n\t\t\tpublicKeyPem,\n\t\t);\n\t\treturn {\n\t\t\tverified,\n\t\t\treason: verified ? undefined : \"signature did not verify\",\n\t\t};\n\t} catch (err) {\n\t\treturn {\n\t\t\tverified: false,\n\t\t\treason: err instanceof Error ? err.message : \"signature check failed\",\n\t\t};\n\t}\n}\n\nconst HOSTED_SIGNING_KEY_URL =\n\t\"https://api.secondlayer.tools/public/streams/signing-key\";\n\nasync function fetchHostedKey(url: string): Promise<string | undefined> {\n\t// A key fetched over plaintext is whoever sits on the wire, not a key.\n\tif (!url.startsWith(\"https://\")) return undefined;\n\ttry {\n\t\tconst response = await fetch(url, { signal: AbortSignal.timeout(10_000) });\n\t\tif (!response.ok) return undefined;\n\t\tconst body = (await response.json()) as { public_key_pem?: string };\n\t\treturn typeof body.public_key_pem === \"string\"\n\t\t\t? body.public_key_pem\n\t\t\t: undefined;\n\t} catch {\n\t\t// Offline is a legitimate state; the compiled root key still applies.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Which key a manifest is checked against, in trust order: an explicit\n * `--public-key`, then `ARCHIVE_SIGNING_PUBLIC_KEY` / `STREAMS_SIGNING_PUBLIC_KEY`\n * from the environment, then the hosted key endpoint over https (never in OSS\n * mode, which must work with no path to api.secondlayer.tools), and finally the\n * key compiled into this release. The compiled key means a fresh self-hosted\n * instance can bootstrap offline; the tradeoff is that rotating it is a CLI\n * release. Plaintext `http://` key sources are never consulted.\n */\nexport async function resolveArchivePublicKey(input: {\n\texplicitPem?: string;\n\tenvPem?: string;\n\tallowHostedApi?: boolean;\n\t/** Test seam for the hosted endpoint; production callers leave it unset. */\n\thostedKeyUrl?: string;\n}): Promise<string> {\n\tif (input.explicitPem) return input.explicitPem;\n\tif (input.envPem) return input.envPem;\n\tif (input.allowHostedApi) {\n\t\tconst hosted = await fetchHostedKey(\n\t\t\tinput.hostedKeyUrl ?? HOSTED_SIGNING_KEY_URL,\n\t\t);\n\t\tif (hosted) return hosted;\n\t}\n\treturn ARCHIVE_ROOT_PUBLIC_KEY_PEM;\n}\n",
6
6
  "import {\n\ttype KeyObject,\n\tcreateHash,\n\tcreatePrivateKey,\n\tcreatePublicKey,\n\tgenerateKeyPairSync,\n\tsign as nodeSign,\n\tverify as nodeVerify,\n} from \"node:crypto\";\n\n/**\n * Asymmetric ed25519 signing for Streams response proofs.\n *\n * Asymmetric (not HMAC) so the proof is real: only the server holds the private\n * key, and any consumer verifies with the published public key — no shared\n * secret to leak. ed25519 uses node's `sign`/`verify` with a `null` algorithm.\n * Keys are PEM (PKCS8 private / SPKI public) for env transport; load once and\n * reuse the KeyObject on hot paths.\n */\n\nexport function generateEd25519KeyPair(): {\n\tprivateKeyPem: string;\n\tpublicKeyPem: string;\n} {\n\tconst { privateKey, publicKey } = generateKeyPairSync(\"ed25519\");\n\treturn {\n\t\tprivateKeyPem: privateKey\n\t\t\t.export({ format: \"pem\", type: \"pkcs8\" })\n\t\t\t.toString(),\n\t\tpublicKeyPem: publicKey.export({ format: \"pem\", type: \"spki\" }).toString(),\n\t};\n}\n\nexport function loadEd25519PrivateKey(pem: string): KeyObject {\n\treturn createPrivateKey(pem);\n}\n\nexport function loadEd25519PublicKey(pem: string): KeyObject {\n\treturn createPublicKey(pem);\n}\n\nexport function publicKeyPemFromPrivate(privateKeyPem: string): string {\n\treturn createPublicKey(createPrivateKey(privateKeyPem))\n\t\t.export({ format: \"pem\", type: \"spki\" })\n\t\t.toString();\n}\n\n/** Stable short id for a public key (rotation hint via X-Signature-KeyId). */\nexport function ed25519KeyId(publicKeyPem: string): string {\n\tconst der = createPublicKey(publicKeyPem).export({\n\t\tformat: \"der\",\n\t\ttype: \"spki\",\n\t});\n\treturn createHash(\"sha256\").update(der).digest(\"base64url\").slice(0, 16);\n}\n\nexport function signEd25519(payload: string, privateKey: KeyObject): string {\n\treturn nodeSign(null, Buffer.from(payload, \"utf8\"), privateKey).toString(\n\t\t\"base64\",\n\t);\n}\n\nexport function verifyEd25519(\n\tpayload: string,\n\tsignatureBase64: string,\n\tpublicKey: KeyObject,\n): boolean {\n\ttry {\n\t\treturn nodeVerify(\n\t\t\tnull,\n\t\t\tBuffer.from(payload, \"utf8\"),\n\t\t\tpublicKey,\n\t\t\tBuffer.from(signatureBase64, \"base64\"),\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n",
7
7
  "import {\n\ted25519KeyId,\n\tloadEd25519PrivateKey,\n\tloadEd25519PublicKey,\n\tpublicKeyPemFromPrivate,\n\tsignEd25519,\n\tverifyEd25519,\n} from \"./crypto/ed25519.ts\";\n\n/**\n * ed25519 signing for the Streams cold-bulk parquet manifest.\n *\n * The live Streams lane is ed25519-signed; the bulk manifest was plain JSON with\n * only per-file sha256, so a tampered manifest+file pair verified cleanly. This\n * signs the manifest itself with the same platform key, so a consumer can trust\n * the file hashes only after the manifest signature checks out — making the two\n * availability lanes symmetric.\n *\n * The signed bytes are the manifest's canonical JSON with the signature envelope\n * fields removed, so signer and verifier agree without a separate canonical\n * form: `signature`/`key_id` are appended last, so stripping them and\n * re-serializing reproduces the exact pre-sign bytes.\n */\ntype SignatureEnvelope = { signature?: string; key_id?: string };\n\n/** The exact bytes a manifest signature covers: the manifest JSON minus the\n * signature envelope fields. */\nexport function canonicalStreamsBulkManifestPayload(\n\tmanifest: Record<string, unknown> & SignatureEnvelope,\n): string {\n\tconst { signature: _signature, key_id: _keyId, ...rest } = manifest;\n\treturn JSON.stringify(rest);\n}\n\nfunction normalizePem(pem: string): string {\n\t// Env transport often escapes newlines; restore real PEM line breaks.\n\treturn pem.includes(\"\\\\n\") ? pem.replace(/\\\\n/g, \"\\n\") : pem;\n}\n\n/**\n * Attach an ed25519 `signature` + `key_id` over the manifest's canonical bytes.\n * Returns a new manifest; re-signing one that already carries a signature signs\n * over its un-enveloped form (idempotent shape).\n */\nexport function signStreamsBulkManifest<\n\tT extends Record<string, unknown> & SignatureEnvelope,\n>(\n\tmanifest: T,\n\tprivateKeyPem: string,\n): T & { signature: string; key_id: string } {\n\tconst pem = normalizePem(privateKeyPem);\n\tconst privateKey = loadEd25519PrivateKey(pem);\n\tconst keyId = ed25519KeyId(publicKeyPemFromPrivate(pem));\n\tconst { signature: _signature, key_id: _keyId, ...base } = manifest;\n\tconst payload = JSON.stringify(base);\n\treturn {\n\t\t...(base as T),\n\t\tsignature: signEd25519(payload, privateKey),\n\t\tkey_id: keyId,\n\t};\n}\n\n/** Verify a manifest's ed25519 signature against the published public key. */\nexport function verifyStreamsBulkManifestSignature(\n\tmanifest: Record<string, unknown> & SignatureEnvelope,\n\tpublicKeyPem: string,\n): boolean {\n\tif (!manifest.signature) return false;\n\tconst payload = canonicalStreamsBulkManifestPayload(manifest);\n\treturn verifyEd25519(\n\t\tpayload,\n\t\tmanifest.signature,\n\t\tloadEd25519PublicKey(publicKeyPem),\n\t);\n}\n",
8
8
  "/**\n * The archive signing key, compiled into every release.\n *\n * `secondlayer bootstrap`, `verify`, and `repair` refuse a manifest they\n * cannot verify, so a self-hosted instance with no network path to the hosted\n * API still needs a key it can trust. Shipping it here means the trust root\n * travels with the CLI rather than with whichever host answers\n * `/public/streams/signing-key`.\n *\n * Tradeoff: rotating this key is a CLI release until a root-signed key\n * ceremony lands. Operators who want a different root pin one with\n * `ARCHIVE_SIGNING_PUBLIC_KEY` or `--public-key`, which win over this value.\n *\n * Value: `GET https://api.secondlayer.tools/public/streams/signing-key`,\n * key_id `fHQWzs9ML2WIYakf`, ed25519, fetched 2026-08-27.\n */\nexport const ARCHIVE_ROOT_PUBLIC_KEY_PEM: string = [\n\t\"-----BEGIN PUBLIC KEY-----\",\n\t\"MCowBQYDK2VwAyEAq7wXm235JkFbElDmTVdNtmTKqRLx4PzjBgZhyREUFis=\",\n\t\"-----END PUBLIC KEY-----\",\n\t\"\",\n].join(\"\\n\");\n\nexport const ARCHIVE_ROOT_KEY_ID: string = \"fHQWzs9ML2WIYakf\";\n"
9
9
  ],
10
- "mappings": "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",
10
+ "mappings": "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",
11
11
  "debugId": "001D083D88CAE76364756E2164756E21",
12
12
  "names": []
13
13
  }
@@ -284,12 +284,14 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
291
291
  last_used_at: Date | null;
292
292
  revoked_at: Date | null;
293
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
294
+ expires_at: Date | null;
293
295
  created_at: Generated<Date>;
294
296
  }
295
297
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -707,7 +709,7 @@ interface PendingForkBlocksTable {
707
709
  payload: ColumnType<unknown, unknown, unknown>;
708
710
  received_at: Generated<Date>;
709
711
  }
710
- type ObserverJournalStatus = "received" | "processed" | "failed";
712
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
711
713
  /**
712
714
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
713
715
  * processing columns are updated only after the current derived-state write
@@ -31,6 +31,7 @@
31
31
  */
32
32
  declare const ARCHIVE_STATUS_SCHEMA_VERSION = 1;
33
33
  type ArchiveState = "fresh" | "lagging" | "stale" | "gap" | "failed-audit" | "source-unavailable";
34
+ /** The archive's state, coverage, and a plain-language reason. */
34
35
  type ArchiveStatus = {
35
36
  schema_version: typeof ARCHIVE_STATUS_SCHEMA_VERSION
36
37
  network: string
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/archive/status.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * `status.json` — the archive's operational truth, published short-cache from\n * verified state only.\n *\n * Everything else in the tree is immutable and signed, which makes it durable\n * but silent: a consumer holding a valid manifest cannot tell whether it is\n * current, three days behind, or the last thing published before an incident.\n * This object answers that, and it is the only object allowed to say something\n * unflattering.\n *\n * The distinction this file exists to protect:\n *\n * lagging → the archive trails the chain tip. This is CORRECT and permanent.\n * Two expected causes: twice-weekly publish (Wednesday and Sunday)\n * and the finality boundary. Cadence is the bulk of the gap;\n * finality is hours, not days. An archive that reported `stale`\n * for its own expected lag would be crying wolf forever, and\n * consumers would learn to ignore it.\n * stale → publishing stopped. The lag exceeds what cadence and the\n * finality rule can explain, so something is broken.\n *\n * `state` is derived from measurements, never asserted by the publisher, and\n * every unhealthy state is reachable — an archive that can only report health\n * is decoration.\n */\n\n/**\n * v1 is additive. `source.decoder_head` joined the object without a bump:\n * it does not change the meaning of any existing field, and readers already\n * ignore unknown keys. Bump only if a field is renamed, removed, or\n * re-interpreted.\n */\nexport const ARCHIVE_STATUS_SCHEMA_VERSION = 1;\n\nexport type ArchiveState =\n\t| \"fresh\"\n\t| \"lagging\"\n\t| \"stale\"\n\t| \"gap\"\n\t| \"failed-audit\"\n\t| \"source-unavailable\";\n\nexport type ArchiveStatus = {\n\tschema_version: typeof ARCHIVE_STATUS_SCHEMA_VERSION;\n\tnetwork: string;\n\tstate: ArchiveState;\n\t/** Human-facing one-liner explaining the state, including WHY. */\n\tdetail: string;\n\tgenerated_at: string;\n\tarchive: {\n\t\tsnapshot_digest: string | null;\n\t\tcoverage_to_block: number | null;\n\t\tpromoted_at: string | null;\n\t\tsigning_key_id: string | null;\n\t};\n\tsource: {\n\t\t/** Chain tip as the publisher last observed it. Null when unreachable. */\n\t\ttip_height: number | null;\n\t\t/** Highest height eligible to publish under the finality rule. */\n\t\tfinalized_height: number | null;\n\t\t/**\n\t\t * Highest height the Index decoder (`decode.ft_transfer.v1`) has\n\t\t * committed. Null when the checkpoint cannot be read. Distinct from\n\t\t * `archive.coverage_to_block`, which is the last signed publish.\n\t\t */\n\t\tdecoder_head: number | null;\n\t};\n\tlag: {\n\t\t/** Behind the FINALIZED height — the number that indicates a problem. */\n\t\tblocks_behind_finalized: number | null;\n\t\t/** Behind the raw chain tip — expected to be non-zero forever. */\n\t\tblocks_behind_tip: number | null;\n\t\tseconds_since_promotion: number | null;\n\t};\n\taudit: {\n\t\tcomplete: boolean;\n\t\tchecked_at: string;\n\t} | null;\n\tsignature?: string;\n\tkey_id?: string;\n};\n\nexport type StatusInputs = {\n\tnetwork: string;\n\tsnapshotDigest: string | null;\n\tcoverageToBlock: number | null;\n\tpromotedAt: string | null;\n\tsigningKeyId: string | null;\n\t/** Null when the source could not be reached. */\n\tsourceTipHeight: number | null;\n\tfinalizedHeight: number | null;\n\t/**\n\t * Index decoder checkpoint height. Null when unread or unreachable.\n\t * Reported, never used to derive `state`.\n\t */\n\tdecoderHead?: number | null;\n\taudit: { complete: boolean; checkedAt: string } | null;\n\tnow: Date;\n\t/**\n\t * How far behind the FINALIZED height the archive may fall before it is\n\t * stale rather than merely lagging. Publishing one partition covers 50k\n\t * blocks, so a healthy archive sits well inside this.\n\t */\n\tmaxBlocksBehindFinalized?: number;\n\t/** How long since the last promotion before staleness, regardless of height. */\n\tmaxSecondsSincePromotion?: number;\n};\n\nconst DEFAULT_MAX_BLOCKS_BEHIND_FINALIZED = 60_000;\n/**\n * Must exceed the publish cadence, or the archive reports `stale` on every\n * healthy cycle and the signal means nothing.\n *\n * The publisher runs Wed + Sun, so the longest gap between promotions in a\n * healthy week is 4 days. Five days leaves a day of grace for a slow export or\n * timer jitter, and still fires two days before the next scheduled attempt\n * whenever a cycle is actually missed.\n *\n * It also lands just inside the height rule: at the observed ~7–9k blocks/day\n * the archive is ~45k blocks behind finalized at the five-day mark, so age\n * trips first and reports the more diagnostic message (\"publishing stopped\")\n * rather than the symptom.\n */\nconst DEFAULT_MAX_SECONDS_SINCE_PROMOTION = 5 * 24 * 3_600;\n\n/** systemd `OnCalendar=Wed,Sun *-*-* 08:00:00`. Host is UTC. Job start, not promotion landing. */\nconst PUBLISH_HOUR_UTC = 8;\nconst PUBLISH_WEEKDAYS_UTC = new Set([0, 3]);\n\n/**\n * Next twice-weekly publish fire strictly after `now`. Matches\n * `secondlayer-archive-publish.timer`. Export duration means the promotion\n * lands hours later; this is the scheduled start.\n */\nexport function nextScheduledArchivePublish(now: Date): Date {\n\tfor (let i = 0; i <= 7; i++) {\n\t\tconst candidate = new Date(\n\t\t\tDate.UTC(\n\t\t\t\tnow.getUTCFullYear(),\n\t\t\t\tnow.getUTCMonth(),\n\t\t\t\tnow.getUTCDate() + i,\n\t\t\t\tPUBLISH_HOUR_UTC,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tif (PUBLISH_WEEKDAYS_UTC.has(candidate.getUTCDay()) && candidate > now) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\tthrow new Error(\"nextScheduledArchivePublish: no fire in 7 days\");\n}\n\nfunction publishWeekdayName(date: Date): \"Sunday\" | \"Wednesday\" {\n\treturn date.getUTCDay() === 0 ? \"Sunday\" : \"Wednesday\";\n}\n\n/**\n * Durations here run to days, and \"beyond the 120h objective\" makes a reader do\n * arithmetic before they can tell whether that is alarming.\n */\nfunction formatAge(seconds: number): string {\n\tconst hours = seconds / 3_600;\n\tif (hours < 48) return `${Math.round(hours)}h`;\n\tconst days = hours / 24;\n\t// One decimal only when it changes the reading: 4d and 4.5d are different\n\t// facts during an incident, 5.0d is noise.\n\tconst rounded = Math.round(days * 10) / 10;\n\treturn `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)}d`;\n}\n\n/**\n * Derive status from measurements. Pure, so every state below is reachable in\n * a test rather than only in production at 3am.\n *\n * Order matters: a failing audit outranks freshness, because an archive that\n * is current and wrong is worse than one that is behind and correct.\n */\nexport function deriveArchiveStatus(inputs: StatusInputs): ArchiveStatus {\n\tconst maxBehind =\n\t\tinputs.maxBlocksBehindFinalized ?? DEFAULT_MAX_BLOCKS_BEHIND_FINALIZED;\n\tconst maxAge =\n\t\tinputs.maxSecondsSincePromotion ?? DEFAULT_MAX_SECONDS_SINCE_PROMOTION;\n\n\tconst secondsSincePromotion = inputs.promotedAt\n\t\t? Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.round(\n\t\t\t\t\t(inputs.now.getTime() - Date.parse(inputs.promotedAt)) / 1000,\n\t\t\t\t),\n\t\t\t)\n\t\t: null;\n\n\tconst behindFinalized =\n\t\tinputs.finalizedHeight !== null && inputs.coverageToBlock !== null\n\t\t\t? Math.max(0, inputs.finalizedHeight - inputs.coverageToBlock)\n\t\t\t: null;\n\tconst behindTip =\n\t\tinputs.sourceTipHeight !== null && inputs.coverageToBlock !== null\n\t\t\t? Math.max(0, inputs.sourceTipHeight - inputs.coverageToBlock)\n\t\t\t: null;\n\n\tconst base = {\n\t\tschema_version:\n\t\t\tARCHIVE_STATUS_SCHEMA_VERSION as typeof ARCHIVE_STATUS_SCHEMA_VERSION,\n\t\tnetwork: inputs.network,\n\t\tgenerated_at: inputs.now.toISOString(),\n\t\tarchive: {\n\t\t\tsnapshot_digest: inputs.snapshotDigest,\n\t\t\tcoverage_to_block: inputs.coverageToBlock,\n\t\t\tpromoted_at: inputs.promotedAt,\n\t\t\tsigning_key_id: inputs.signingKeyId,\n\t\t},\n\t\tsource: {\n\t\t\ttip_height: inputs.sourceTipHeight,\n\t\t\tfinalized_height: inputs.finalizedHeight,\n\t\t\tdecoder_head: inputs.decoderHead ?? null,\n\t\t},\n\t\tlag: {\n\t\t\tblocks_behind_finalized: behindFinalized,\n\t\t\tblocks_behind_tip: behindTip,\n\t\t\tseconds_since_promotion: secondsSincePromotion,\n\t\t},\n\t\taudit: inputs.audit\n\t\t\t? { complete: inputs.audit.complete, checked_at: inputs.audit.checkedAt }\n\t\t\t: null,\n\t};\n\n\t// A failed audit is the loudest signal: current-and-wrong beats behind-and-right.\n\tif (inputs.audit && !inputs.audit.complete) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"failed-audit\",\n\t\t\tdetail: `the canonical audit did not pass at ${inputs.audit.checkedAt}; this archive is not safe to restore from`,\n\t\t};\n\t}\n\n\tif (!inputs.snapshotDigest || inputs.coverageToBlock === null) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"gap\",\n\t\t\tdetail: \"no snapshot is currently promoted\",\n\t\t};\n\t}\n\n\t// Source unreachable: report the gap in KNOWLEDGE rather than guessing. The\n\t// archive may be perfectly healthy; we cannot currently say so.\n\tif (inputs.sourceTipHeight === null || inputs.finalizedHeight === null) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"source-unavailable\",\n\t\t\tdetail:\n\t\t\t\t\"the chain source could not be reached, so freshness cannot be determined\",\n\t\t};\n\t}\n\n\tif (secondsSincePromotion !== null && secondsSincePromotion > maxAge) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"stale\",\n\t\t\tdetail: `last promotion was ${formatAge(secondsSincePromotion)} ago, beyond the ${formatAge(maxAge)} freshness objective`,\n\t\t};\n\t}\n\n\tif (behindFinalized !== null && behindFinalized > maxBehind) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"stale\",\n\t\t\tdetail: `${behindFinalized} blocks behind the finalized height, beyond the ${maxBehind} threshold`,\n\t\t};\n\t}\n\n\t// Behind the tip. CORRECT and permanent: twice-weekly publish plus the\n\t// finality boundary. Cadence is the bulk of the gap. Do not call this stale.\n\tif (behindTip !== null && behindTip > 0) {\n\t\tconst nextDay = publishWeekdayName(nextScheduledArchivePublish(inputs.now));\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"lagging\",\n\t\t\tdetail: `${behindTip} blocks behind the chain tip, which is expected: publishing is twice weekly (Wednesday and Sunday, next ${nextDay}) and stops at the finality boundary`,\n\t\t};\n\t}\n\n\treturn {\n\t\t...base,\n\t\tstate: \"fresh\",\n\t\tdetail: \"published through the finalized height\",\n\t};\n}\n\n/** States a consumer should refuse to restore from. */\nexport function isRestorable(state: ArchiveState): boolean {\n\treturn state === \"fresh\" || state === \"lagging\";\n}\n"
5
+ "/**\n * `status.json` — the archive's operational truth, published short-cache from\n * verified state only.\n *\n * Everything else in the tree is immutable and signed, which makes it durable\n * but silent: a consumer holding a valid manifest cannot tell whether it is\n * current, three days behind, or the last thing published before an incident.\n * This object answers that, and it is the only object allowed to say something\n * unflattering.\n *\n * The distinction this file exists to protect:\n *\n * lagging → the archive trails the chain tip. This is CORRECT and permanent.\n * Two expected causes: twice-weekly publish (Wednesday and Sunday)\n * and the finality boundary. Cadence is the bulk of the gap;\n * finality is hours, not days. An archive that reported `stale`\n * for its own expected lag would be crying wolf forever, and\n * consumers would learn to ignore it.\n * stale → publishing stopped. The lag exceeds what cadence and the\n * finality rule can explain, so something is broken.\n *\n * `state` is derived from measurements, never asserted by the publisher, and\n * every unhealthy state is reachable — an archive that can only report health\n * is decoration.\n */\n\n/**\n * v1 is additive. `source.decoder_head` joined the object without a bump:\n * it does not change the meaning of any existing field, and readers already\n * ignore unknown keys. Bump only if a field is renamed, removed, or\n * re-interpreted.\n */\nexport const ARCHIVE_STATUS_SCHEMA_VERSION = 1;\n\nexport type ArchiveState =\n\t| \"fresh\"\n\t| \"lagging\"\n\t| \"stale\"\n\t| \"gap\"\n\t| \"failed-audit\"\n\t| \"source-unavailable\";\n\n/** The archive's state, coverage, and a plain-language reason. */\nexport type ArchiveStatus = {\n\tschema_version: typeof ARCHIVE_STATUS_SCHEMA_VERSION;\n\tnetwork: string;\n\tstate: ArchiveState;\n\t/** Human-facing one-liner explaining the state, including WHY. */\n\tdetail: string;\n\tgenerated_at: string;\n\tarchive: {\n\t\tsnapshot_digest: string | null;\n\t\tcoverage_to_block: number | null;\n\t\tpromoted_at: string | null;\n\t\tsigning_key_id: string | null;\n\t};\n\tsource: {\n\t\t/** Chain tip as the publisher last observed it. Null when unreachable. */\n\t\ttip_height: number | null;\n\t\t/** Highest height eligible to publish under the finality rule. */\n\t\tfinalized_height: number | null;\n\t\t/**\n\t\t * Highest height the Index decoder (`decode.ft_transfer.v1`) has\n\t\t * committed. Null when the checkpoint cannot be read. Distinct from\n\t\t * `archive.coverage_to_block`, which is the last signed publish.\n\t\t */\n\t\tdecoder_head: number | null;\n\t};\n\tlag: {\n\t\t/** Behind the FINALIZED height — the number that indicates a problem. */\n\t\tblocks_behind_finalized: number | null;\n\t\t/** Behind the raw chain tip — expected to be non-zero forever. */\n\t\tblocks_behind_tip: number | null;\n\t\tseconds_since_promotion: number | null;\n\t};\n\taudit: {\n\t\tcomplete: boolean;\n\t\tchecked_at: string;\n\t} | null;\n\tsignature?: string;\n\tkey_id?: string;\n};\n\nexport type StatusInputs = {\n\tnetwork: string;\n\tsnapshotDigest: string | null;\n\tcoverageToBlock: number | null;\n\tpromotedAt: string | null;\n\tsigningKeyId: string | null;\n\t/** Null when the source could not be reached. */\n\tsourceTipHeight: number | null;\n\tfinalizedHeight: number | null;\n\t/**\n\t * Index decoder checkpoint height. Null when unread or unreachable.\n\t * Reported, never used to derive `state`.\n\t */\n\tdecoderHead?: number | null;\n\taudit: { complete: boolean; checkedAt: string } | null;\n\tnow: Date;\n\t/**\n\t * How far behind the FINALIZED height the archive may fall before it is\n\t * stale rather than merely lagging. Publishing one partition covers 50k\n\t * blocks, so a healthy archive sits well inside this.\n\t */\n\tmaxBlocksBehindFinalized?: number;\n\t/** How long since the last promotion before staleness, regardless of height. */\n\tmaxSecondsSincePromotion?: number;\n};\n\nconst DEFAULT_MAX_BLOCKS_BEHIND_FINALIZED = 60_000;\n/**\n * Must exceed the publish cadence, or the archive reports `stale` on every\n * healthy cycle and the signal means nothing.\n *\n * The publisher runs Wed + Sun, so the longest gap between promotions in a\n * healthy week is 4 days. Five days leaves a day of grace for a slow export or\n * timer jitter, and still fires two days before the next scheduled attempt\n * whenever a cycle is actually missed.\n *\n * It also lands just inside the height rule: at the observed ~7–9k blocks/day\n * the archive is ~45k blocks behind finalized at the five-day mark, so age\n * trips first and reports the more diagnostic message (\"publishing stopped\")\n * rather than the symptom.\n */\nconst DEFAULT_MAX_SECONDS_SINCE_PROMOTION = 5 * 24 * 3_600;\n\n/** systemd `OnCalendar=Wed,Sun *-*-* 08:00:00`. Host is UTC. Job start, not promotion landing. */\nconst PUBLISH_HOUR_UTC = 8;\nconst PUBLISH_WEEKDAYS_UTC = new Set([0, 3]);\n\n/**\n * Next twice-weekly publish fire strictly after `now`. Matches\n * `secondlayer-archive-publish.timer`. Export duration means the promotion\n * lands hours later; this is the scheduled start.\n */\nexport function nextScheduledArchivePublish(now: Date): Date {\n\tfor (let i = 0; i <= 7; i++) {\n\t\tconst candidate = new Date(\n\t\t\tDate.UTC(\n\t\t\t\tnow.getUTCFullYear(),\n\t\t\t\tnow.getUTCMonth(),\n\t\t\t\tnow.getUTCDate() + i,\n\t\t\t\tPUBLISH_HOUR_UTC,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tif (PUBLISH_WEEKDAYS_UTC.has(candidate.getUTCDay()) && candidate > now) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\tthrow new Error(\"nextScheduledArchivePublish: no fire in 7 days\");\n}\n\nfunction publishWeekdayName(date: Date): \"Sunday\" | \"Wednesday\" {\n\treturn date.getUTCDay() === 0 ? \"Sunday\" : \"Wednesday\";\n}\n\n/**\n * Durations here run to days, and \"beyond the 120h objective\" makes a reader do\n * arithmetic before they can tell whether that is alarming.\n */\nfunction formatAge(seconds: number): string {\n\tconst hours = seconds / 3_600;\n\tif (hours < 48) return `${Math.round(hours)}h`;\n\tconst days = hours / 24;\n\t// One decimal only when it changes the reading: 4d and 4.5d are different\n\t// facts during an incident, 5.0d is noise.\n\tconst rounded = Math.round(days * 10) / 10;\n\treturn `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)}d`;\n}\n\n/**\n * Derive status from measurements. Pure, so every state below is reachable in\n * a test rather than only in production at 3am.\n *\n * Order matters: a failing audit outranks freshness, because an archive that\n * is current and wrong is worse than one that is behind and correct.\n */\nexport function deriveArchiveStatus(inputs: StatusInputs): ArchiveStatus {\n\tconst maxBehind =\n\t\tinputs.maxBlocksBehindFinalized ?? DEFAULT_MAX_BLOCKS_BEHIND_FINALIZED;\n\tconst maxAge =\n\t\tinputs.maxSecondsSincePromotion ?? DEFAULT_MAX_SECONDS_SINCE_PROMOTION;\n\n\tconst secondsSincePromotion = inputs.promotedAt\n\t\t? Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.round(\n\t\t\t\t\t(inputs.now.getTime() - Date.parse(inputs.promotedAt)) / 1000,\n\t\t\t\t),\n\t\t\t)\n\t\t: null;\n\n\tconst behindFinalized =\n\t\tinputs.finalizedHeight !== null && inputs.coverageToBlock !== null\n\t\t\t? Math.max(0, inputs.finalizedHeight - inputs.coverageToBlock)\n\t\t\t: null;\n\tconst behindTip =\n\t\tinputs.sourceTipHeight !== null && inputs.coverageToBlock !== null\n\t\t\t? Math.max(0, inputs.sourceTipHeight - inputs.coverageToBlock)\n\t\t\t: null;\n\n\tconst base = {\n\t\tschema_version:\n\t\t\tARCHIVE_STATUS_SCHEMA_VERSION as typeof ARCHIVE_STATUS_SCHEMA_VERSION,\n\t\tnetwork: inputs.network,\n\t\tgenerated_at: inputs.now.toISOString(),\n\t\tarchive: {\n\t\t\tsnapshot_digest: inputs.snapshotDigest,\n\t\t\tcoverage_to_block: inputs.coverageToBlock,\n\t\t\tpromoted_at: inputs.promotedAt,\n\t\t\tsigning_key_id: inputs.signingKeyId,\n\t\t},\n\t\tsource: {\n\t\t\ttip_height: inputs.sourceTipHeight,\n\t\t\tfinalized_height: inputs.finalizedHeight,\n\t\t\tdecoder_head: inputs.decoderHead ?? null,\n\t\t},\n\t\tlag: {\n\t\t\tblocks_behind_finalized: behindFinalized,\n\t\t\tblocks_behind_tip: behindTip,\n\t\t\tseconds_since_promotion: secondsSincePromotion,\n\t\t},\n\t\taudit: inputs.audit\n\t\t\t? { complete: inputs.audit.complete, checked_at: inputs.audit.checkedAt }\n\t\t\t: null,\n\t};\n\n\t// A failed audit is the loudest signal: current-and-wrong beats behind-and-right.\n\tif (inputs.audit && !inputs.audit.complete) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"failed-audit\",\n\t\t\tdetail: `the canonical audit did not pass at ${inputs.audit.checkedAt}; this archive is not safe to restore from`,\n\t\t};\n\t}\n\n\tif (!inputs.snapshotDigest || inputs.coverageToBlock === null) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"gap\",\n\t\t\tdetail: \"no snapshot is currently promoted\",\n\t\t};\n\t}\n\n\t// Source unreachable: report the gap in KNOWLEDGE rather than guessing. The\n\t// archive may be perfectly healthy; we cannot currently say so.\n\tif (inputs.sourceTipHeight === null || inputs.finalizedHeight === null) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"source-unavailable\",\n\t\t\tdetail:\n\t\t\t\t\"the chain source could not be reached, so freshness cannot be determined\",\n\t\t};\n\t}\n\n\tif (secondsSincePromotion !== null && secondsSincePromotion > maxAge) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"stale\",\n\t\t\tdetail: `last promotion was ${formatAge(secondsSincePromotion)} ago, beyond the ${formatAge(maxAge)} freshness objective`,\n\t\t};\n\t}\n\n\tif (behindFinalized !== null && behindFinalized > maxBehind) {\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"stale\",\n\t\t\tdetail: `${behindFinalized} blocks behind the finalized height, beyond the ${maxBehind} threshold`,\n\t\t};\n\t}\n\n\t// Behind the tip. CORRECT and permanent: twice-weekly publish plus the\n\t// finality boundary. Cadence is the bulk of the gap. Do not call this stale.\n\tif (behindTip !== null && behindTip > 0) {\n\t\tconst nextDay = publishWeekdayName(nextScheduledArchivePublish(inputs.now));\n\t\treturn {\n\t\t\t...base,\n\t\t\tstate: \"lagging\",\n\t\t\tdetail: `${behindTip} blocks behind the chain tip, which is expected: publishing is twice weekly (Wednesday and Sunday, next ${nextDay}) and stops at the finality boundary`,\n\t\t};\n\t}\n\n\treturn {\n\t\t...base,\n\t\tstate: \"fresh\",\n\t\tdetail: \"published through the finalized height\",\n\t};\n}\n\n/** States a consumer should refuse to restore from. */\nexport function isRestorable(state: ArchiveState): boolean {\n\treturn state === \"fresh\" || state === \"lagging\";\n}\n"
6
6
  ],
7
- "mappings": "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",
7
+ "mappings": "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",
8
8
  "debugId": "779DB5C9B354565F64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -601,12 +601,14 @@ interface ApiKeysTable {
601
601
  rate_limit: Generated<number>;
602
602
  ip_address: string;
603
603
  account_id: string;
604
- product: Generated<"account" | "streams" | "index" | "sentinel">;
604
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
605
605
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
606
606
  areas: unknown | null;
607
607
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
608
608
  last_used_at: Date | null;
609
609
  revoked_at: Date | null;
610
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
611
+ expires_at: Date | null;
610
612
  created_at: Generated<Date>;
611
613
  }
612
614
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -1024,7 +1026,7 @@ interface PendingForkBlocksTable {
1024
1026
  payload: ColumnType<unknown, unknown, unknown>;
1025
1027
  received_at: Generated<Date>;
1026
1028
  }
1027
- type ObserverJournalStatus = "received" | "processed" | "failed";
1029
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
1028
1030
  /**
1029
1031
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
1030
1032
  * processing columns are updated only after the current derived-state write
@@ -285,12 +285,14 @@ interface ApiKeysTable {
285
285
  rate_limit: Generated<number>;
286
286
  ip_address: string;
287
287
  account_id: string;
288
- product: Generated<"account" | "streams" | "index" | "sentinel">;
288
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
289
289
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
290
290
  areas: unknown | null;
291
291
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
292
292
  last_used_at: Date | null;
293
293
  revoked_at: Date | null;
294
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
295
+ expires_at: Date | null;
294
296
  created_at: Generated<Date>;
295
297
  }
296
298
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -708,7 +710,7 @@ interface PendingForkBlocksTable {
708
710
  payload: ColumnType<unknown, unknown, unknown>;
709
711
  received_at: Generated<Date>;
710
712
  }
711
- type ObserverJournalStatus = "received" | "processed" | "failed";
713
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
712
714
  /**
713
715
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
714
716
  * processing columns are updated only after the current derived-state write
@@ -284,12 +284,14 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
291
291
  last_used_at: Date | null;
292
292
  revoked_at: Date | null;
293
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
294
+ expires_at: Date | null;
293
295
  created_at: Generated<Date>;
294
296
  }
295
297
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -707,7 +709,7 @@ interface PendingForkBlocksTable {
707
709
  payload: ColumnType<unknown, unknown, unknown>;
708
710
  received_at: Generated<Date>;
709
711
  }
710
- type ObserverJournalStatus = "received" | "processed" | "failed";
712
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
711
713
  /**
712
714
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
713
715
  * processing columns are updated only after the current derived-state write
@@ -284,12 +284,14 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
291
291
  last_used_at: Date | null;
292
292
  revoked_at: Date | null;
293
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
294
+ expires_at: Date | null;
293
295
  created_at: Generated<Date>;
294
296
  }
295
297
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -707,7 +709,7 @@ interface PendingForkBlocksTable {
707
709
  payload: ColumnType<unknown, unknown, unknown>;
708
710
  received_at: Generated<Date>;
709
711
  }
710
- type ObserverJournalStatus = "received" | "processed" | "failed";
712
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
711
713
  /**
712
714
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
713
715
  * processing columns are updated only after the current derived-state write
@@ -284,12 +284,14 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
291
291
  last_used_at: Date | null;
292
292
  revoked_at: Date | null;
293
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
294
+ expires_at: Date | null;
293
295
  created_at: Generated<Date>;
294
296
  }
295
297
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -707,7 +709,7 @@ interface PendingForkBlocksTable {
707
709
  payload: ColumnType<unknown, unknown, unknown>;
708
710
  received_at: Generated<Date>;
709
711
  }
710
- type ObserverJournalStatus = "received" | "processed" | "failed";
712
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
711
713
  /**
712
714
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
713
715
  * processing columns are updated only after the current derived-state write
@@ -284,12 +284,14 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
291
291
  last_used_at: Date | null;
292
292
  revoked_at: Date | null;
293
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
294
+ expires_at: Date | null;
293
295
  created_at: Generated<Date>;
294
296
  }
295
297
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -707,7 +709,7 @@ interface PendingForkBlocksTable {
707
709
  payload: ColumnType<unknown, unknown, unknown>;
708
710
  received_at: Generated<Date>;
709
711
  }
710
- type ObserverJournalStatus = "received" | "processed" | "failed";
712
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
711
713
  /**
712
714
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
713
715
  * processing columns are updated only after the current derived-state write
@@ -284,12 +284,14 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
291
291
  last_used_at: Date | null;
292
292
  revoked_at: Date | null;
293
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
294
+ expires_at: Date | null;
293
295
  created_at: Generated<Date>;
294
296
  }
295
297
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -707,7 +709,7 @@ interface PendingForkBlocksTable {
707
709
  payload: ColumnType<unknown, unknown, unknown>;
708
710
  received_at: Generated<Date>;
709
711
  }
710
- type ObserverJournalStatus = "received" | "processed" | "failed";
712
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
711
713
  /**
712
714
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
713
715
  * processing columns are updated only after the current derived-state write
@@ -284,12 +284,14 @@ interface ApiKeysTable {
284
284
  rate_limit: Generated<number>;
285
285
  ip_address: string;
286
286
  account_id: string;
287
- product: Generated<"account" | "streams" | "index" | "sentinel">;
287
+ product: Generated<"account" | "streams" | "index" | "sentinel" | "docs">;
288
288
  /** Sentinel permission areas; set only on `product = 'sentinel'` keys. */
289
289
  areas: unknown | null;
290
290
  tier: "free" | "build" | "scale" | "enterprise" | "internal" | null;
291
291
  last_used_at: Date | null;
292
292
  revoked_at: Date | null;
293
+ /** Set only on `docs` keys (24h). A key past this instant never authenticates. */
294
+ expires_at: Date | null;
293
295
  created_at: Generated<Date>;
294
296
  }
295
297
  /** Singleton self-host / publisher identity. Network is immutable. */
@@ -707,7 +709,7 @@ interface PendingForkBlocksTable {
707
709
  payload: ColumnType<unknown, unknown, unknown>;
708
710
  received_at: Generated<Date>;
709
711
  }
710
- type ObserverJournalStatus = "received" | "processed" | "failed";
712
+ type ObserverJournalStatus = "received" | "processed" | "failed" | "retried";
711
713
  /**
712
714
  * Exact observer callback receipt. `raw_body` is immutable evidence; the
713
715
  * processing columns are updated only after the current derived-state write