@secondlayer/shared 11.7.0 → 11.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/archive/copy-loader.d.ts +1 -1
- package/dist/src/archive/copy-loader.js +4 -20
- package/dist/src/archive/copy-loader.js.map +2 -2
- package/dist/src/archive/derived-stage-report.d.ts +1 -1
- package/dist/src/archive/derived-stage-report.js +3 -19
- package/dist/src/archive/derived-stage-report.js.map +2 -2
- package/dist/src/archive/disk-guard.d.ts +1 -1
- package/dist/src/archive/disk-guard.js +3 -19
- package/dist/src/archive/disk-guard.js.map +2 -2
- package/dist/src/archive/instance-diagnosis.d.ts +1 -1
- package/dist/src/archive/instance-diagnosis.js +6 -22
- package/dist/src/archive/instance-diagnosis.js.map +2 -2
- package/dist/src/archive/key-registry.d.ts +1 -1
- package/dist/src/archive/key-registry.js +12 -12
- package/dist/src/archive/key-registry.js.map +1 -1
- package/dist/src/archive/per-height-digest.d.ts +1 -1
- package/dist/src/archive/per-height-digest.js +3 -17
- package/dist/src/archive/per-height-digest.js.map +5 -5
- package/dist/src/archive/progress.d.ts +1 -1
- package/dist/src/archive/progress.js +4 -20
- package/dist/src/archive/progress.js.map +2 -2
- package/dist/src/archive/range-digest.d.ts +25 -30
- package/dist/src/archive/range-digest.js +4 -20
- package/dist/src/archive/range-digest.js.map +2 -2
- package/dist/src/archive/reference.d.ts +1 -1
- package/dist/src/archive/reference.js +14 -12
- package/dist/src/archive/reference.js.map +5 -5
- package/dist/src/archive/root-key.d.ts +1 -1
- package/dist/src/archive/root-key.js +3 -19
- package/dist/src/archive/root-key.js.map +2 -2
- package/dist/src/archive/semantic-digest-builder.d.ts +25 -30
- package/dist/src/archive/semantic-digest-builder.js +5 -19
- package/dist/src/archive/semantic-digest-builder.js.map +5 -5
- package/dist/src/archive/semantic-digest.d.ts +1 -1
- package/dist/src/archive/semantic-digest.js +4 -20
- package/dist/src/archive/semantic-digest.js.map +2 -2
- package/dist/src/archive/status.d.ts +1 -1
- package/dist/src/archive/status.js +4 -20
- package/dist/src/archive/status.js.map +2 -2
- package/dist/src/coverage/index.d.ts +24 -29
- package/dist/src/coverage/index.js +80 -96
- package/dist/src/coverage/index.js.map +2 -2
- package/dist/src/crypto/ed25519.d.ts +1 -1
- package/dist/src/crypto/ed25519.js +13 -13
- package/dist/src/crypto/ed25519.js.map +1 -1
- package/dist/src/crypto/hmac.d.ts +1 -1
- package/dist/src/crypto/hmac.js +9 -9
- package/dist/src/crypto/hmac.js.map +1 -1
- package/dist/src/crypto/secondlayer-webhook.d.ts +1 -1
- package/dist/src/crypto/secondlayer-webhook.js +14 -14
- package/dist/src/crypto/secondlayer-webhook.js.map +2 -2
- package/dist/src/crypto/secrets.d.ts +1 -1
- package/dist/src/crypto/secrets.js +15 -29
- package/dist/src/crypto/secrets.js.map +5 -5
- package/dist/src/crypto/standard-webhooks.d.ts +1 -1
- package/dist/src/crypto/standard-webhooks.js +3 -19
- package/dist/src/crypto/standard-webhooks.js.map +2 -2
- package/dist/src/db/index.d.ts +46 -57
- package/dist/src/db/index.js +42 -58
- package/dist/src/db/index.js.map +6 -6
- package/dist/src/db/jsonb.d.ts +1 -1
- package/dist/src/db/jsonb.js +3 -19
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/chain-reorgs.d.ts +24 -29
- package/dist/src/db/queries/chain-reorgs.js +29 -45
- package/dist/src/db/queries/chain-reorgs.js.map +6 -6
- package/dist/src/db/queries/contracts.d.ts +24 -29
- package/dist/src/db/queries/contracts.js +9 -23
- package/dist/src/db/queries/contracts.js.map +5 -5
- package/dist/src/db/queries/instance.d.ts +24 -29
- package/dist/src/db/queries/instance.js +7 -23
- package/dist/src/db/queries/instance.js.map +2 -2
- package/dist/src/db/queries/integrity.d.ts +25 -30
- package/dist/src/db/queries/integrity.js +8 -23
- package/dist/src/db/queries/integrity.js.map +3 -3
- package/dist/src/db/queries/subgraph-gaps.d.ts +24 -29
- package/dist/src/db/queries/subgraph-gaps.js +5 -21
- package/dist/src/db/queries/subgraph-gaps.js.map +2 -2
- package/dist/src/db/queries/subgraph-operations.d.ts +24 -29
- package/dist/src/db/queries/subgraph-operations.js +17 -33
- package/dist/src/db/queries/subgraph-operations.js.map +2 -2
- package/dist/src/db/queries/subgraphs.d.ts +23 -44
- package/dist/src/db/queries/subgraphs.js +15 -46
- package/dist/src/db/queries/subgraphs.js.map +5 -5
- package/dist/src/db/queries/sync-scope.d.ts +24 -29
- package/dist/src/db/queries/sync-scope.js +6 -22
- package/dist/src/db/queries/sync-scope.js.map +2 -2
- package/dist/src/db/queries/webhooks.d.ts +24 -29
- package/dist/src/db/queries/webhooks.js +54 -50
- package/dist/src/db/queries/webhooks.js.map +5 -5
- package/dist/src/db/schema.d.ts +27 -37
- package/dist/src/env.d.ts +1 -1
- package/dist/src/env.js +7 -23
- package/dist/src/env.js.map +2 -2
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +15 -31
- package/dist/src/errors.js.map +2 -2
- package/dist/src/index-http.d.ts +1 -1
- package/dist/src/index-http.js +3 -19
- package/dist/src/index-http.js.map +2 -2
- package/dist/src/index-internal-auth.d.ts +1 -1
- package/dist/src/index-internal-auth.js +7 -23
- package/dist/src/index-internal-auth.js.map +2 -2
- package/dist/src/index.d.ts +164 -187
- package/dist/src/index.js +261 -316
- package/dist/src/index.js.map +13 -13
- package/dist/src/leader.d.ts +1 -1
- package/dist/src/leader.js +7 -21
- package/dist/src/leader.js.map +5 -5
- package/dist/src/logger.js +1 -17
- package/dist/src/logger.js.map +2 -2
- package/dist/src/mode.d.ts +1 -1
- package/dist/src/mode.js +5 -21
- package/dist/src/mode.js.map +2 -2
- package/dist/src/node/archive-client.d.ts +1 -1
- package/dist/src/node/archive-client.js +9 -23
- package/dist/src/node/archive-client.js.map +5 -5
- package/dist/src/node/client.d.ts +1 -1
- package/dist/src/node/client.js +4 -18
- package/dist/src/node/client.js.map +5 -5
- package/dist/src/node/consensus.d.ts +1 -1
- package/dist/src/node/consensus.js +5 -21
- package/dist/src/node/consensus.js.map +2 -2
- package/dist/src/node/hiro-client.d.ts +1 -1
- package/dist/src/node/hiro-client.js +1 -17
- package/dist/src/node/hiro-client.js.map +2 -2
- package/dist/src/node/hiro-pg-client.js +1 -17
- package/dist/src/node/hiro-pg-client.js.map +2 -2
- package/dist/src/node/local-client.d.ts +24 -29
- package/dist/src/node/local-client.js +3 -19
- package/dist/src/node/local-client.js.map +2 -2
- package/dist/src/node/nakamoto.d.ts +1 -1
- package/dist/src/node/nakamoto.js +12 -28
- package/dist/src/node/nakamoto.js.map +2 -2
- package/dist/src/queue/listener.d.ts +1 -1
- package/dist/src/queue/listener.js +4 -20
- package/dist/src/queue/listener.js.map +2 -2
- package/dist/src/runtime/index.d.ts +1 -1
- package/dist/src/runtime/index.js +51 -67
- package/dist/src/runtime/index.js.map +2 -2
- package/dist/src/schemas/index.d.ts +1 -4
- package/dist/src/schemas/index.js +19 -37
- package/dist/src/schemas/index.js.map +3 -3
- package/dist/src/schemas/subgraphs.d.ts +1 -4
- package/dist/src/schemas/subgraphs.js +1 -18
- package/dist/src/schemas/subgraphs.js.map +3 -3
- package/dist/src/schemas/webhooks.d.ts +1 -1
- package/dist/src/schemas/webhooks.js +21 -37
- package/dist/src/schemas/webhooks.js.map +2 -2
- package/dist/src/streams-bulk-manifest.d.ts +1 -1
- package/dist/src/streams-bulk-manifest.js +9 -9
- package/dist/src/streams-bulk-manifest.js.map +1 -1
- package/dist/src/streams-rows/index.d.ts +1 -1
- package/dist/src/streams-rows/index.js +32 -48
- package/dist/src/streams-rows/index.js.map +2 -2
- package/dist/src/subgraphs/spec.d.ts +1 -11
- package/dist/src/subgraphs/spec.js +27 -93
- package/dist/src/subgraphs/spec.js.map +3 -3
- package/dist/src/types.d.ts +1 -1
- package/migrations/0134_waitlists.ts +64 -0
- package/migrations/0135_drop_hosted_play_and_meters.ts +58 -0
- package/migrations/0136_drop_subgraph_visibility.ts +34 -0
- package/package.json +1 -1
|
@@ -46,4 +46,4 @@ declare function writeRowsToCopyStream(params: {
|
|
|
46
46
|
onProgress?: (rowsWritten: number) => void
|
|
47
47
|
progressEvery?: number
|
|
48
48
|
}): Promise<number>;
|
|
49
|
-
export {
|
|
49
|
+
export { ArchiveDataset, ArchiveRow, copyStatement, csvField, toCsvLine, writeRowsToCopyStream };
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
// src/archive/copy-loader.ts
|
|
18
2
|
import { once } from "node:events";
|
|
19
3
|
function csvField(value) {
|
|
@@ -101,11 +85,11 @@ async function writeRowsToCopyStream(params) {
|
|
|
101
85
|
return count;
|
|
102
86
|
}
|
|
103
87
|
export {
|
|
104
|
-
|
|
105
|
-
toCsvLine,
|
|
88
|
+
copyStatement,
|
|
106
89
|
csvField,
|
|
107
|
-
|
|
90
|
+
toCsvLine,
|
|
91
|
+
writeRowsToCopyStream
|
|
108
92
|
};
|
|
109
93
|
|
|
110
|
-
//# debugId=
|
|
94
|
+
//# debugId=4643C93B767098ED64756E2164756E21
|
|
111
95
|
//# sourceMappingURL=copy-loader.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
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"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "4643C93B767098ED64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -96,4 +96,4 @@ type ManifestValidationIssue = {
|
|
|
96
96
|
* - Every canonical_input is a real dataset name.
|
|
97
97
|
*/
|
|
98
98
|
declare function validateServiceManifests(manifests: ReadonlyArray<ServiceManifest>): ManifestValidationIssue[];
|
|
99
|
-
export {
|
|
99
|
+
export { CanonicalInput, DERIVED_STAGE_REPORT_SCHEMA_VERSION, DerivedStageReport, ExternalInput, ManifestValidationIssue, Output, ServiceKind, ServiceManifest, buildDerivedStageReport, validateServiceManifests };
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
// src/archive/derived-stage-report.ts
|
|
18
2
|
var DERIVED_STAGE_REPORT_SCHEMA_VERSION = 1;
|
|
19
3
|
function buildDerivedStageReport(manifests, generatedAt) {
|
|
@@ -55,10 +39,10 @@ function validateServiceManifests(manifests) {
|
|
|
55
39
|
return issues;
|
|
56
40
|
}
|
|
57
41
|
export {
|
|
58
|
-
|
|
42
|
+
DERIVED_STAGE_REPORT_SCHEMA_VERSION,
|
|
59
43
|
buildDerivedStageReport,
|
|
60
|
-
|
|
44
|
+
validateServiceManifests
|
|
61
45
|
};
|
|
62
46
|
|
|
63
|
-
//# debugId=
|
|
47
|
+
//# debugId=664A04684A589A7B64756E2164756E21
|
|
64
48
|
//# sourceMappingURL=derived-stage-report.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Derived-stage acceptance — what each service reads, writes, and needs\n * beyond the canonical archive.\n *\n * The canonical archive rebuilds `blocks`, `transactions`, and `events`\n * deterministically from R2 alone. Every stage downstream of those tables\n * (decoders, subgraphs, webhook delivery) has its own inputs, outputs,\n * and rebuild requirements — and an operator restoring from the archive\n * needs to know exactly which of those it also needs to source, replay, or\n * re-derive.\n *\n * This module defines the reporting shape. Each producer/service declares\n * itself once (a `ServiceManifest`) and the aggregator emits one report per\n * service to `reports/derived/<service>.json`. Stateful verification —\n * actually re-running the service and comparing outputs — is Phase 4 work\n * (see P4.10 effect manifests, P4.13 stateful deep verify); this shape is\n * what Phase 4 will populate.\n *\n * The report answers three operator questions:\n * 1. What canonical inputs does this service consume? (archive suffices)\n * 2. What external inputs does it need? (RPC endpoints, envelope journal,\n * contract source, ORACLE feeds — anything not in the archive)\n * 3. If I restore from R2 alone, can this service resume? If not, what\n * is missing?\n *\n * Silence is the failure mode we most want to prevent: a service that ships\n * without a manifest is a service whose rebuild story is undocumented.\n * `assertAllServicesDeclared()` fails a build when a known service is missing.\n */\n\nexport const DERIVED_STAGE_REPORT_SCHEMA_VERSION = 1 as const;\n\nexport type ServiceKind =\n\t| \"decoder\"\n\t| \"subgraph-runtime\"\n\t| \"webhook-runtime\"\n\t| \"protocol-producer\";\n\nexport type CanonicalInput = \"blocks\" | \"transactions\" | \"events\";\n\nexport interface ExternalInput {\n\t/** Short name for the dependency: `stacks-node-rpc`, `hiro-extended-api`,\n\t * `observer-journal`, `contract-source-registry`, etc. */\n\tname: string;\n\t/** Why the archive can't provide this. One sentence. */\n\treason: string;\n\t/** Where an operator can source it. `bundled-stacks`, `own-node`,\n\t * `hiro-public-api`, `re-derive-from-events`, `manual-import`. */\n\tsource: string;\n\t/** True if the archive supplies enough to re-derive this dependency\n\t * without a live external source. Rare but happens (e.g. contract ABIs\n\t * derivable from `transactions.raw_tx`). */\n\trebuildable_from_archive: boolean;\n}\n\nexport interface Output {\n\t/** Table/topic/queue name in the operator's runtime. */\n\ttarget: string;\n\t/** How the operator verifies this output — e.g. `row-count`,\n\t * `semantic-digest`, `effect-manifest`, `dlq-empty`. */\n\tverification: string;\n}\n\nexport interface ServiceManifest {\n\tname: string;\n\tkind: ServiceKind;\n\tdescription: string;\n\tcanonical_inputs: CanonicalInput[];\n\texternal_inputs: ExternalInput[];\n\toutputs: Output[];\n\t/**\n\t * Explicit judgment on whether R2 alone can rebuild this service.\n\t * `false` REQUIRES `external_inputs` to name what else is needed.\n\t */\n\tr2_alone_can_rebuild: boolean;\n\t/** When true, this service can only replay forward from a starting point\n\t * the operator supplies (e.g. a webhook consumer's last-delivered id).\n\t * Publish it, don't hide it. */\n\trequires_operator_state: boolean;\n\t/** Repo path where the service lives, for cross-reference. */\n\tsource_path: string;\n}\n\nexport interface DerivedStageReport {\n\tschema_version: typeof DERIVED_STAGE_REPORT_SCHEMA_VERSION;\n\tgenerated_at: string;\n\tservices: ServiceManifest[];\n}\n\n/**\n * Compose a report from a set of registered manifests. Sorts services by name\n * so the JSON is stable across runs — a re-emit with unchanged manifests\n * produces byte-identical output, so an operator can diff two archive\n * publish cycles cleanly.\n */\nexport function buildDerivedStageReport(\n\tmanifests: ReadonlyArray<ServiceManifest>,\n\tgeneratedAt: string,\n): DerivedStageReport {\n\tconst sorted = [...manifests].sort((a, b) => a.name.localeCompare(b.name));\n\treturn {\n\t\tschema_version: DERIVED_STAGE_REPORT_SCHEMA_VERSION,\n\t\tgenerated_at: generatedAt,\n\t\tservices: sorted,\n\t};\n}\n\nexport type ManifestValidationIssue = {\n\tname: string;\n\tproblem: string;\n};\n\n/**\n * Enforce the invariants the report shape leaves ambiguous:\n * - `r2_alone_can_rebuild: false` REQUIRES at least one external input.\n * Otherwise the report claims a rebuild gap it can't explain, which is\n * worse than declaring nothing.\n * - Names are unique — a duplicate is almost always a copy-paste that\n * silently shadows the earlier declaration.\n * - Every canonical_input is a real dataset name.\n */\nexport function validateServiceManifests(\n\tmanifests: ReadonlyArray<ServiceManifest>,\n): ManifestValidationIssue[] {\n\tconst issues: ManifestValidationIssue[] = [];\n\tconst seen = new Set<string>();\n\tconst canonicalDatasets: ReadonlySet<CanonicalInput> = new Set([\n\t\t\"blocks\",\n\t\t\"transactions\",\n\t\t\"events\",\n\t]);\n\tfor (const m of manifests) {\n\t\tif (seen.has(m.name)) {\n\t\t\tissues.push({ name: m.name, problem: \"duplicate service name\" });\n\t\t}\n\t\tseen.add(m.name);\n\t\tif (!m.r2_alone_can_rebuild && m.external_inputs.length === 0) {\n\t\t\tissues.push({\n\t\t\t\tname: m.name,\n\t\t\t\tproblem:\n\t\t\t\t\t\"declares r2_alone_can_rebuild=false but lists no external_inputs — the gap is undocumented\",\n\t\t\t});\n\t\t}\n\t\tfor (const input of m.canonical_inputs) {\n\t\t\tif (!canonicalDatasets.has(input)) {\n\t\t\t\tissues.push({\n\t\t\t\t\tname: m.name,\n\t\t\t\t\tproblem: `canonical_inputs contains unknown dataset: ${String(input)}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\treturn issues;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AA8BO,IAAM,sCAAsC;AAiE5C,SAAS,uBAAuB,CACtC,WACA,aACqB;AAAA,EACrB,MAAM,SAAS,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,EACzE,OAAO;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,UAAU;AAAA,EACX;AAAA;AAiBM,SAAS,wBAAwB,CACvC,WAC4B;AAAA,EAC5B,MAAM,SAAoC,CAAC;AAAA,EAC3C,MAAM,OAAO,IAAI;AAAA,EACjB,MAAM,oBAAiD,IAAI,IAAI;AAAA,IAC9D;AAAA,IACA;AAAA,IACA;AAAA,EACD,CAAC;AAAA,EACD,WAAW,KAAK,WAAW;AAAA,IAC1B,IAAI,KAAK,IAAI,EAAE,IAAI,GAAG;AAAA,MACrB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,yBAAyB,CAAC;AAAA,IAChE;AAAA,IACA,KAAK,IAAI,EAAE,IAAI;AAAA,IACf,IAAI,CAAC,EAAE,wBAAwB,EAAE,gBAAgB,WAAW,GAAG;AAAA,MAC9D,OAAO,KAAK;AAAA,QACX,MAAM,EAAE;AAAA,QACR,SACC;AAAA,MACF,CAAC;AAAA,IACF;AAAA,IACA,WAAW,SAAS,EAAE,kBAAkB;AAAA,MACvC,IAAI,CAAC,kBAAkB,IAAI,KAAK,GAAG;AAAA,QAClC,OAAO,KAAK;AAAA,UACX,MAAM,EAAE;AAAA,UACR,SAAS,8CAA8C,OAAO,KAAK;AAAA,QACpE,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAAA,EACA,OAAO;AAAA;",
|
|
8
|
+
"debugId": "664A04684A589A7B64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -43,4 +43,4 @@ declare class DiskSpaceExhausted extends Error {
|
|
|
43
43
|
* start — because the pressure that matters arrives mid-job.
|
|
44
44
|
*/
|
|
45
45
|
declare function waitForDiskSpace(options: DiskGuardOptions): Promise<void>;
|
|
46
|
-
export {
|
|
46
|
+
export { DiskGuardOptions, DiskSpace, DiskSpaceExhausted, readDiskSpace, waitForDiskSpace };
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
// src/archive/disk-guard.ts
|
|
18
2
|
import { statfs } from "node:fs/promises";
|
|
19
3
|
async function readDiskSpace(path) {
|
|
@@ -60,10 +44,10 @@ async function waitForDiskSpace(options) {
|
|
|
60
44
|
}
|
|
61
45
|
}
|
|
62
46
|
export {
|
|
63
|
-
|
|
47
|
+
DiskSpaceExhausted,
|
|
64
48
|
readDiskSpace,
|
|
65
|
-
|
|
49
|
+
waitForDiskSpace
|
|
66
50
|
};
|
|
67
51
|
|
|
68
|
-
//# debugId=
|
|
52
|
+
//# debugId=F4103F3B5631788664756E2164756E21
|
|
69
53
|
//# sourceMappingURL=disk-guard.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { statfs } from \"node:fs/promises\";\n\n/**\n * Free-space guard for bulk archive jobs.\n *\n * Twice on 2026-08-12 a bulk job filled the disk that production Postgres\n * shares: a backfill died at 9.2M/14.4M rows on ENOSPC, and a restore died at\n * 86% of events. Prod survived both, but only just — 2.9GB free with the\n * indexer still ingesting.\n *\n * The failure mode that makes this worth guarding rather than monitoring: the\n * nightly `pg_basebackup` transiently consumes ~150GB, so a job that starts\n * with comfortable headroom can still hit the wall an hour later. Checking\n * once at startup is not enough; the check has to be repeated as the job runs.\n *\n * Waiting is the right response, not failing. The spike passes, and an\n * archive job that pauses for twenty minutes is strictly better than one that\n * dies at 86% or, worse, takes the database down with it.\n */\n\nexport type DiskSpace = { freeBytes: number; totalBytes: number };\n\nexport async function readDiskSpace(path: string): Promise<DiskSpace> {\n\tconst stats = await statfs(path);\n\treturn {\n\t\tfreeBytes: stats.bavail * stats.bsize,\n\t\ttotalBytes: stats.blocks * stats.bsize,\n\t};\n}\n\nexport type DiskGuardOptions = {\n\t/** Any path on the filesystem to watch. */\n\tpath: string;\n\t/** Pause below this many bytes free. */\n\tminFreeBytes?: number;\n\t/** How long to wait between re-checks while paused. */\n\tpollMs?: number;\n\t/** Give up after this long rather than pausing forever. */\n\tmaxWaitMs?: number;\n\tonPause?: (free: DiskSpace, waitedMs: number) => void;\n\tsleep?: (ms: number) => Promise<void>;\n\treadSpace?: (path: string) => Promise<DiskSpace>;\n};\n\n/** 100GB: comfortably above the ~39GB an export needs plus the backup spike. */\nconst DEFAULT_MIN_FREE_BYTES = 100 * 1024 ** 3;\nconst DEFAULT_POLL_MS = 60_000;\nconst DEFAULT_MAX_WAIT_MS = 2 * 3_600_000;\n/**\n * A threshold larger than a share of the whole disk is unsatisfiable, not\n * protective: on a 14GB CI runner or a modest VPS, demanding 100GB free would\n * block forever waiting for space that machine can never have. The absolute\n * default is tuned for the production host; this keeps it from becoming a wall\n * everywhere else.\n */\nconst MAX_THRESHOLD_FRACTION_OF_DISK = 0.25;\n\nfunction effectiveThreshold(configured: number, totalBytes: number): number {\n\tif (!Number.isFinite(totalBytes) || totalBytes <= 0) return configured;\n\treturn Math.min(configured, totalBytes * MAX_THRESHOLD_FRACTION_OF_DISK);\n}\n\nexport class DiskSpaceExhausted extends Error {\n\tconstructor(free: number, required: number, waitedMs: number) {\n\t\tsuper(\n\t\t\t`only ${(free / 1024 ** 3).toFixed(1)}GB free (need ${(required / 1024 ** 3).toFixed(1)}GB) after waiting ${Math.round(waitedMs / 60_000)}m`,\n\t\t);\n\t\tthis.name = \"DiskSpaceExhausted\";\n\t}\n}\n\n/**\n * Block until there is enough headroom, or throw after `maxWaitMs`.\n *\n * Call this between units of work — before each partition, not once at the\n * start — because the pressure that matters arrives mid-job.\n */\nexport async function waitForDiskSpace(\n\toptions: DiskGuardOptions,\n): Promise<void> {\n\tconst minFree = options.minFreeBytes ?? DEFAULT_MIN_FREE_BYTES;\n\tconst pollMs = options.pollMs ?? DEFAULT_POLL_MS;\n\tconst maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;\n\tconst readSpace = options.readSpace ?? readDiskSpace;\n\tconst sleep =\n\t\toptions.sleep ??\n\t\t((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));\n\n\tlet waited = 0;\n\twhile (true) {\n\t\tconst space = await readSpace(options.path);\n\t\tconst threshold = effectiveThreshold(minFree, space.totalBytes);\n\t\tif (space.freeBytes >= threshold) return;\n\t\tif (waited >= maxWaitMs) {\n\t\t\tthrow new DiskSpaceExhausted(space.freeBytes, threshold, waited);\n\t\t}\n\t\toptions.onPause?.(space, waited);\n\t\tawait sleep(pollMs);\n\t\twaited += pollMs;\n\t}\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAAA;AAsBA,eAAsB,aAAa,CAAC,MAAkC;AAAA,EACrE,MAAM,QAAQ,MAAM,OAAO,IAAI;AAAA,EAC/B,OAAO;AAAA,IACN,WAAW,MAAM,SAAS,MAAM;AAAA,IAChC,YAAY,MAAM,SAAS,MAAM;AAAA,EAClC;AAAA;AAkBD,IAAM,yBAAyB,MAAM,QAAQ;AAC7C,IAAM,kBAAkB;AACxB,IAAM,sBAAsB,IAAI;AAQhC,IAAM,iCAAiC;AAEvC,SAAS,kBAAkB,CAAC,YAAoB,YAA4B;AAAA,EAC3E,IAAI,CAAC,OAAO,SAAS,UAAU,KAAK,cAAc;AAAA,IAAG,OAAO;AAAA,EAC5D,OAAO,KAAK,IAAI,YAAY,aAAa,8BAA8B;AAAA;AAAA;AAGjE,MAAM,2BAA2B,MAAM;AAAA,EAC7C,WAAW,CAAC,MAAc,UAAkB,UAAkB;AAAA,IAC7D,MACC,SAAS,OAAO,QAAQ,GAAG,QAAQ,CAAC,mBAAmB,WAAW,QAAQ,GAAG,QAAQ,CAAC,sBAAsB,KAAK,MAAM,WAAW,KAAM,IACzI;AAAA,IACA,KAAK,OAAO;AAAA;AAEd;AAQA,eAAsB,gBAAgB,CACrC,SACgB;AAAA,EAChB,MAAM,UAAU,QAAQ,gBAAgB;AAAA,EACxC,MAAM,SAAS,QAAQ,UAAU;AAAA,EACjC,MAAM,YAAY,QAAQ,aAAa;AAAA,EACvC,MAAM,YAAY,QAAQ,aAAa;AAAA,EACvC,MAAM,QACL,QAAQ,UACP,CAAC,OAAe,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EAExE,IAAI,SAAS;AAAA,EACb,OAAO,MAAM;AAAA,IACZ,MAAM,QAAQ,MAAM,UAAU,QAAQ,IAAI;AAAA,IAC1C,MAAM,YAAY,mBAAmB,SAAS,MAAM,UAAU;AAAA,IAC9D,IAAI,MAAM,aAAa;AAAA,MAAW;AAAA,IAClC,IAAI,UAAU,WAAW;AAAA,MACxB,MAAM,IAAI,mBAAmB,MAAM,WAAW,WAAW,MAAM;AAAA,IAChE;AAAA,IACA,QAAQ,UAAU,OAAO,MAAM;AAAA,IAC/B,MAAM,MAAM,MAAM;AAAA,IAClB,UAAU;AAAA,EACX;AAAA;",
|
|
8
|
+
"debugId": "F4103F3B5631788664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -98,4 +98,4 @@ interface ContextExplanation {
|
|
|
98
98
|
* derived from a live probe of the same instance, never assumed.
|
|
99
99
|
*/
|
|
100
100
|
declare function explainContextNulls(snapshot: Record<string, unknown>, probe: ContextProbe): ContextExplanation;
|
|
101
|
-
export {
|
|
101
|
+
export { BOOTSTRAP_STEP, ContextExplanation, ContextProbe, InstanceDiagnosis, InstanceIssue, InstanceState, OBSERVER_STEP, PublicStatus, PublicStatusDecoder, PublicStatusService, SELF_HOST_DOCS_STEP, diagnoseInstanceStatus, explainContextNulls, hasNoChainData };
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
1
|
// src/archive/instance-diagnosis.ts
|
|
18
2
|
var BOOTSTRAP_STEP = "Restore verified history: secondlayer bootstrap --against <manifest>";
|
|
19
3
|
var OBSERVER_STEP = "Confirm your node streams into this instance — secondlayer observer --mode indexer --endpoint secondlayer:3700 prints the stanza its config needs";
|
|
@@ -122,13 +106,13 @@ function unresolvedReadReason(probe, blocks) {
|
|
|
122
106
|
return probe.hasCredential ? `${where} Check the surface directly: secondlayer status --json` : `${where} No credential resolved: export INSTANCE_TOKEN — an instance published past loopback requires it on every /v1 read.`;
|
|
123
107
|
}
|
|
124
108
|
export {
|
|
125
|
-
|
|
126
|
-
explainContextNulls,
|
|
127
|
-
diagnoseInstanceStatus,
|
|
128
|
-
SELF_HOST_DOCS_STEP,
|
|
109
|
+
BOOTSTRAP_STEP,
|
|
129
110
|
OBSERVER_STEP,
|
|
130
|
-
|
|
111
|
+
SELF_HOST_DOCS_STEP,
|
|
112
|
+
diagnoseInstanceStatus,
|
|
113
|
+
explainContextNulls,
|
|
114
|
+
hasNoChainData
|
|
131
115
|
};
|
|
132
116
|
|
|
133
|
-
//# debugId=
|
|
117
|
+
//# debugId=16118BA3EA771CAD64756E2164756E21
|
|
134
118
|
//# sourceMappingURL=instance-diagnosis.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
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 / SL_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 SL_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": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "16118BA3EA771CAD64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -49,4 +49,4 @@ declare function publicKeyFor(registry: KeyRegistry, keyId: string): string | un
|
|
|
49
49
|
/** Derive the registry entry id for a public key, so operators cannot mislabel
|
|
50
50
|
* a key by hand-editing the registry. */
|
|
51
51
|
declare function keyIdFor(publicKeyPem: string): string;
|
|
52
|
-
export {
|
|
52
|
+
export { KEY_REGISTRY_SCHEMA_VERSION, KeyRegistry, KeyStatus, KeyTrustResult, RegisteredKey, canonicalRegistryPayload, checkKeyTrust, keyIdFor, publicKeyFor, verifyRegistry };
|
|
@@ -17,13 +17,13 @@ var __export = (target, all) => {
|
|
|
17
17
|
// src/crypto/ed25519.ts
|
|
18
18
|
var exports_ed25519 = {};
|
|
19
19
|
__export(exports_ed25519, {
|
|
20
|
-
|
|
21
|
-
signEd25519: () => signEd25519,
|
|
22
|
-
publicKeyPemFromPrivate: () => publicKeyPemFromPrivate,
|
|
23
|
-
loadEd25519PublicKey: () => loadEd25519PublicKey,
|
|
24
|
-
loadEd25519PrivateKey: () => loadEd25519PrivateKey,
|
|
20
|
+
ed25519KeyId: () => ed25519KeyId,
|
|
25
21
|
generateEd25519KeyPair: () => generateEd25519KeyPair,
|
|
26
|
-
|
|
22
|
+
loadEd25519PrivateKey: () => loadEd25519PrivateKey,
|
|
23
|
+
loadEd25519PublicKey: () => loadEd25519PublicKey,
|
|
24
|
+
publicKeyPemFromPrivate: () => publicKeyPemFromPrivate,
|
|
25
|
+
signEd25519: () => signEd25519,
|
|
26
|
+
verifyEd25519: () => verifyEd25519
|
|
27
27
|
});
|
|
28
28
|
import {
|
|
29
29
|
createHash,
|
|
@@ -156,13 +156,13 @@ function keyIdFor(publicKeyPem) {
|
|
|
156
156
|
return ed25519KeyId(publicKeyPem);
|
|
157
157
|
}
|
|
158
158
|
export {
|
|
159
|
-
|
|
160
|
-
publicKeyFor,
|
|
161
|
-
keyIdFor,
|
|
162
|
-
checkKeyTrust,
|
|
159
|
+
KEY_REGISTRY_SCHEMA_VERSION,
|
|
163
160
|
canonicalRegistryPayload,
|
|
164
|
-
|
|
161
|
+
checkKeyTrust,
|
|
162
|
+
keyIdFor,
|
|
163
|
+
publicKeyFor,
|
|
164
|
+
verifyRegistry
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
-
//# debugId=
|
|
167
|
+
//# debugId=9FC501E6F648D69864756E2164756E21
|
|
168
168
|
//# sourceMappingURL=key-registry.js.map
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
"import {\n\ted25519KeyId,\n\tloadEd25519PublicKey,\n\tverifyEd25519,\n} from \"../crypto/ed25519.ts\";\n\n/**\n * Trust root for the canonical archive.\n *\n * Day-to-day signing uses an ONLINE key that lives on the publishing host, so\n * it is exposed to every risk that host is. The archive's actual trust anchor\n * is an OFFLINE ROOT key that never touches a server: it signs a registry of\n * online keys, and nothing else. A consumer pins the root public key, verifies\n * the registry against it, and only then decides whether a manifest's signature\n * means anything.\n *\n * The distinction that makes rotation survivable:\n *\n * retired / rotated → the key stops signing NEW objects, but everything it\n * signed while valid stays valid. Without this, every\n * rotation would invalidate the entire published history.\n * compromised → every signature by that key is suspect, including\n * historical ones, because an attacker holding the key\n * could have backdated anything.\n *\n * Those two cases look identical in a naive \"is this key current?\" check, and\n * conflating them either destroys your history on every rotation or accepts\n * forgeries after a leak.\n */\n\nexport const KEY_REGISTRY_SCHEMA_VERSION = 1;\n\nexport type KeyStatus = \"active\" | \"retired\" | \"compromised\";\n\nexport type RegisteredKey = {\n\tkey_id: string;\n\tpublic_key_pem: string;\n\tstatus: KeyStatus;\n\t/** ISO timestamp this key became valid for signing. */\n\tvalid_from: string;\n\t/** ISO timestamp it stopped signing. Null while active. */\n\tvalid_until: string | null;\n\t/** For `compromised`: the moment trust is withdrawn. Everything this key\n\t * signed is suspect regardless of when, so this is provenance, not a\n\t * validity bound. */\n\tcompromised_at?: string;\n};\n\nexport type KeyRegistry = {\n\tschema_version: typeof KEY_REGISTRY_SCHEMA_VERSION;\n\tnetwork: string;\n\tkeys: RegisteredKey[];\n\tupdated_at: string;\n\t/** Root signature over the registry minus this envelope. */\n\tsignature?: string;\n\troot_key_id?: string;\n};\n\nexport type KeyTrustResult = {\n\ttrusted: boolean;\n\t/** Machine-readable so callers can map to exit codes and messages. */\n\treason:\n\t\t| \"trusted\"\n\t\t| \"registry-unsigned\"\n\t\t| \"registry-signature-invalid\"\n\t\t| \"unknown-key\"\n\t\t| \"key-compromised\"\n\t\t| \"signed-outside-validity\";\n\tdetail: string;\n};\n\n/** The exact bytes the root signature covers: the registry minus its envelope. */\nexport function canonicalRegistryPayload(registry: KeyRegistry): string {\n\tconst { signature: _s, root_key_id: _r, ...rest } = registry;\n\treturn JSON.stringify(rest);\n}\n\n/**\n * Verify the registry itself against the pinned root public key. Everything\n * downstream is meaningless if this fails — an attacker who can serve a\n * registry can otherwise nominate their own signing key.\n */\nexport function verifyRegistry(\n\tregistry: KeyRegistry,\n\trootPublicKeyPem: string,\n): KeyTrustResult {\n\tif (!registry.signature) {\n\t\treturn {\n\t\t\ttrusted: false,\n\t\t\treason: \"registry-unsigned\",\n\t\t\tdetail: \"key registry carries no root signature\",\n\t\t};\n\t}\n\tlet valid = false;\n\ttry {\n\t\tvalid = verifyEd25519(\n\t\t\tcanonicalRegistryPayload(registry),\n\t\t\tregistry.signature,\n\t\t\tloadEd25519PublicKey(rootPublicKeyPem),\n\t\t);\n\t} catch (err) {\n\t\treturn {\n\t\t\ttrusted: false,\n\t\t\treason: \"registry-signature-invalid\",\n\t\t\tdetail: err instanceof Error ? err.message : \"root verification failed\",\n\t\t};\n\t}\n\treturn valid\n\t\t? {\n\t\t\t\ttrusted: true,\n\t\t\t\treason: \"trusted\",\n\t\t\t\tdetail: \"registry verified against root\",\n\t\t\t}\n\t\t: {\n\t\t\t\ttrusted: false,\n\t\t\t\treason: \"registry-signature-invalid\",\n\t\t\t\tdetail: \"registry signature does not match the pinned root key\",\n\t\t\t};\n}\n\n/**\n * Decide whether an object signed by `keyId` at `signedAt` should be trusted.\n *\n * `signedAt` is the object's own generation time, NOT now — that is what lets a\n * rotated key keep vouching for what it signed while it was valid.\n */\nexport function checkKeyTrust(\n\tregistry: KeyRegistry,\n\tkeyId: string,\n\tsignedAt: string,\n): KeyTrustResult {\n\tconst key = registry.keys.find((k) => k.key_id === keyId);\n\tif (!key) {\n\t\treturn {\n\t\t\ttrusted: false,\n\t\t\treason: \"unknown-key\",\n\t\t\tdetail: `key ${keyId} is not in the registry`,\n\t\t};\n\t}\n\tif (key.status === \"compromised\") {\n\t\treturn {\n\t\t\ttrusted: false,\n\t\t\treason: \"key-compromised\",\n\t\t\tdetail: `key ${keyId} was compromised${key.compromised_at ? ` (${key.compromised_at})` : \"\"}; every signature it made is suspect`,\n\t\t};\n\t}\n\n\tconst signedTime = Date.parse(signedAt);\n\tconst from = Date.parse(key.valid_from);\n\tif (Number.isNaN(signedTime) || Number.isNaN(from)) {\n\t\treturn {\n\t\t\ttrusted: false,\n\t\t\treason: \"signed-outside-validity\",\n\t\t\tdetail: \"unparseable timestamp on the object or the key\",\n\t\t};\n\t}\n\tif (signedTime < from) {\n\t\treturn {\n\t\t\ttrusted: false,\n\t\t\treason: \"signed-outside-validity\",\n\t\t\tdetail: `object predates key validity (${signedAt} < ${key.valid_from})`,\n\t\t};\n\t}\n\tif (key.valid_until !== null) {\n\t\tconst until = Date.parse(key.valid_until);\n\t\tif (!Number.isNaN(until) && signedTime > until) {\n\t\t\treturn {\n\t\t\t\ttrusted: false,\n\t\t\t\treason: \"signed-outside-validity\",\n\t\t\t\tdetail: `object postdates key retirement (${signedAt} > ${key.valid_until})`,\n\t\t\t};\n\t\t}\n\t}\n\n\t// A retired key that was valid at signing time is fine — that is the whole\n\t// point of recording a validity window rather than a boolean.\n\treturn {\n\t\ttrusted: true,\n\t\treason: \"trusted\",\n\t\tdetail:\n\t\t\tkey.status === \"active\"\n\t\t\t\t? `signed by active key ${keyId}`\n\t\t\t\t: `signed by ${keyId} while it was valid (now ${key.status})`,\n\t};\n}\n\n/** Look up a key's public PEM for signature verification. */\nexport function publicKeyFor(\n\tregistry: KeyRegistry,\n\tkeyId: string,\n): string | undefined {\n\treturn registry.keys.find((k) => k.key_id === keyId)?.public_key_pem;\n}\n\n/** Derive the registry entry id for a public key, so operators cannot mislabel\n * a key by hand-editing the registry. */\nexport function keyIdFor(publicKeyPem: string): string {\n\treturn ed25519KeyId(publicKeyPem);\n}\n"
|
|
7
7
|
],
|
|
8
8
|
"mappings": "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",
|
|
9
|
-
"debugId": "
|
|
9
|
+
"debugId": "9FC501E6F648D69864756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -96,4 +96,4 @@ interface DigestIndexPartition {
|
|
|
96
96
|
sha256: string;
|
|
97
97
|
digest_spec: typeof SEMANTIC_DIGEST_SPEC_V1;
|
|
98
98
|
}
|
|
99
|
-
export {
|
|
99
|
+
export { DigestIndexPartition, PerHeightDigestAccumulator, PerHeightDigestRow };
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
1
|
+
// src/archive/per-height-digest.ts
|
|
2
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
16
3
|
|
|
17
4
|
// src/archive/semantic-digest.ts
|
|
18
5
|
import { createHash } from "node:crypto";
|
|
@@ -134,7 +121,6 @@ class SemanticDigestRollup {
|
|
|
134
121
|
}
|
|
135
122
|
|
|
136
123
|
// src/archive/per-height-digest.ts
|
|
137
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
138
124
|
class PerHeightDigestAccumulator {
|
|
139
125
|
rows = new Map;
|
|
140
126
|
setBlockDigest(height, blockDigestHex) {
|
|
@@ -190,5 +176,5 @@ export {
|
|
|
190
176
|
PerHeightDigestAccumulator
|
|
191
177
|
};
|
|
192
178
|
|
|
193
|
-
//# debugId=
|
|
179
|
+
//# debugId=4E237BBF53E5627D64756E2164756E21
|
|
194
180
|
//# sourceMappingURL=per-height-digest.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/archive/
|
|
3
|
+
"sources": ["../src/archive/per-height-digest.ts", "../src/archive/semantic-digest.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { createHash } from \"node:crypto\";\
|
|
6
|
-
"import { createHash } from \"node:crypto\";\
|
|
5
|
+
"import { createHash } from \"node:crypto\";\nimport { SEMANTIC_DIGEST_SPEC_V1 } from \"./semantic-digest.ts\";\n\n/**\n * Per-height semantic digest sidecars — enable \"verify one block\" without\n * downloading a whole 50k-block partition.\n *\n * The partition-level semantic digest is the shape a byte-level comparator\n * wants: one string per partition, cheap to compare in bulk. But a caller who\n * only cares about block 8_500_017 has to pull an entire Parquet file to check\n * one row. The per-height sidecar closes that gap: one 32-byte block digest and\n * two roll-up digests per height (transactions and events), addressable by\n * height without touching the raw datasets.\n *\n * Rollup rules (v1) — MUST match the exporter's stream order or an honest\n * re-derivation will disagree:\n * - transactions_rollup: sha256(concat(tx_digest_bytes, tx_index asc, tx_id asc))\n * `null` when the height has no transactions.\n * - events_rollup: sha256(concat(event_digest_bytes, event_index asc, tx_id asc))\n * `null` when the height has no events.\n * - block_digest: the semantic-v1 block digest itself (one row per\n * height, so no rollup needed).\n *\n * `null` is deliberate: an empty rollup is not the sha256 of nothing (which\n * would collide across two empty datasets); an absent-here-but-present-elsewhere\n * dataset must never compare equal to a truly-empty one.\n */\n\nexport interface PerHeightDigestRow {\n\theight: number;\n\tblock_digest: string;\n\ttransactions_rollup: string | null;\n\tevents_rollup: string | null;\n}\n\ninterface Accumulator {\n\tblock_digest: string | null;\n\ttx: ReturnType<typeof createHash>;\n\ttx_count: number;\n\tevent: ReturnType<typeof createHash>;\n\tevent_count: number;\n}\n\nexport class PerHeightDigestAccumulator {\n\tprivate readonly rows = new Map<number, Accumulator>();\n\n\tsetBlockDigest(height: number, blockDigestHex: string): void {\n\t\tthis.ensure(height).block_digest = blockDigestHex;\n\t}\n\n\tappendTransactionDigest(height: number, digestHex: string): void {\n\t\tconst a = this.ensure(height);\n\t\ta.tx.update(Buffer.from(digestHex, \"hex\"));\n\t\ta.tx_count += 1;\n\t}\n\n\tappendEventDigest(height: number, digestHex: string): void {\n\t\tconst a = this.ensure(height);\n\t\ta.event.update(Buffer.from(digestHex, \"hex\"));\n\t\ta.event_count += 1;\n\t}\n\n\t/**\n\t * Drain the accumulator into height-ascending rows. Heights with no block\n\t * digest are dropped with a thrown error — a canonical partition MUST have\n\t * a block at every height it covers, and silently emitting a row without\n\t * one would hide the missing block from downstream verifiers.\n\t */\n\tdrain(fromBlock: number, toBlock: number): PerHeightDigestRow[] {\n\t\tconst rows: PerHeightDigestRow[] = [];\n\t\tfor (let h = fromBlock; h <= toBlock; h++) {\n\t\t\tconst a = this.rows.get(h);\n\t\t\tif (!a) continue; // Missing entirely — surfaced by canonical audit, not here.\n\t\t\tif (!a.block_digest) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`per-height digest at ${h} has tx/event rollups but no block digest`,\n\t\t\t\t);\n\t\t\t}\n\t\t\trows.push({\n\t\t\t\theight: h,\n\t\t\t\tblock_digest: a.block_digest,\n\t\t\t\ttransactions_rollup:\n\t\t\t\t\ta.tx_count === 0 ? null : a.tx.copy().digest(\"hex\"),\n\t\t\t\tevents_rollup:\n\t\t\t\t\ta.event_count === 0 ? null : a.event.copy().digest(\"hex\"),\n\t\t\t});\n\t\t}\n\t\treturn rows;\n\t}\n\n\tspec(): typeof SEMANTIC_DIGEST_SPEC_V1 {\n\t\treturn SEMANTIC_DIGEST_SPEC_V1;\n\t}\n\n\tprivate ensure(height: number): Accumulator {\n\t\tlet a = this.rows.get(height);\n\t\tif (!a) {\n\t\t\ta = {\n\t\t\t\tblock_digest: null,\n\t\t\t\ttx: createHash(\"sha256\"),\n\t\t\t\ttx_count: 0,\n\t\t\t\tevent: createHash(\"sha256\"),\n\t\t\t\tevent_count: 0,\n\t\t\t};\n\t\t\tthis.rows.set(height, a);\n\t\t}\n\t\treturn a;\n\t}\n}\n\n/**\n * Registered in the snapshot manifest under `digest_index[]`. One entry per\n * partition, addressable at height granularity without downloading raw data.\n */\nexport interface DigestIndexPartition {\n\tfrom_block: number;\n\tto_block: number;\n\tpath: string;\n\trow_count: number;\n\tbyte_size: number;\n\tsha256: string;\n\tdigest_spec: typeof SEMANTIC_DIGEST_SPEC_V1;\n}\n",
|
|
6
|
+
"import { createHash } from \"node:crypto\";\n\n/**\n * v1 canonical byte encoding for chain identity — the shape that lets\n * independently-run runtimes agree on the same block.\n *\n * The exact-body digest (`sha256:parquet-object`) proves capture fidelity to one\n * source: replay the same bytes, get the same rows. It cannot prove two nodes\n * observed the same chain, because their Parquet encoders legally differ in\n * column padding, compression, JSON key order, and null placement — two\n * operators processing the same block can ship byte-different Parquets that\n * carry identical content.\n *\n * The semantic digest is the shape that lets independent runtimes agree. Every\n * value on a block/transaction/event is normalized into a canonical byte string\n * by rules that MUST NOT change without a version bump. Two v1 computations\n * over the same range agree, or one is wrong about the chain — never about\n * formatting.\n *\n * Encoding rules (v1):\n * - Fields join with `\\x1f` (ASCII US, unit separator — cannot legally\n * appear in any chain value; a printable delimiter risks collision with\n * contract-emitted strings).\n * - Null encodes as `\\x00` (single NUL). Distinct from empty string `\"\"`.\n * - Integers encode as their base-10 string. bigint → decimal string.\n * - JSON payload columns (function_args, event data) encode as canonical\n * JSON: object keys sorted lexicographically, no whitespace, bigint →\n * quoted decimal string, non-finite numbers rejected.\n * - Field order per record type is fixed and never reordered.\n * - Digest = sha256(bytes), lowercase hex.\n *\n * Rollup rules (v1) — order MUST match the archive's stream order or an\n * honest re-export will disagree with the manifest:\n * - blocks: (height asc)\n * - transactions: (block_height asc, tx_index asc, tx_id asc)\n * - events: (block_height asc, event_index asc, tx_id asc)\n * Per-partition rollup is `sha256(concat(row_digest_bytes))` in stream order.\n *\n * Test vectors under `packages/shared/src/archive/__fixtures__/semantic-digest/`\n * pin the encoding. Any change to bytes must bump the spec version.\n */\n\nexport const SEMANTIC_DIGEST_SPEC_V1 = \"sha256:semantic-v1\" as const;\nexport type SemanticDigestSpec = typeof SEMANTIC_DIGEST_SPEC_V1;\n\nconst FIELD_SEP = \"\\x1f\";\nconst NULL_MARKER = \"\\x00\";\n\nconst textEncoder = new TextEncoder();\n\n/**\n * Stable JSON — sorted keys, no whitespace, bigint → decimal string. Distinct\n * from `JSON.stringify(x)` which respects insertion order and rejects bigints.\n * Also rejects non-finite numbers, since NaN/Infinity have no on-chain meaning\n * and would silently hash as `null` under `JSON.stringify`.\n */\nexport function canonicalJson(value: unknown): string {\n\tif (value === null) return \"null\";\n\tif (typeof value === \"string\" || typeof value === \"boolean\") {\n\t\treturn JSON.stringify(value);\n\t}\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) {\n\t\t\tthrow new Error(\"canonicalJson: non-finite number\");\n\t\t}\n\t\treturn JSON.stringify(value);\n\t}\n\tif (typeof value === \"bigint\") return JSON.stringify(value.toString());\n\tif (Array.isArray(value)) {\n\t\treturn `[${value.map((item) => canonicalJson(item)).join(\",\")}]`;\n\t}\n\tif (typeof value === \"object\") {\n\t\tconst object = value as Record<string, unknown>;\n\t\treturn `{${Object.keys(object)\n\t\t\t.sort()\n\t\t\t.map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`)\n\t\t\t.join(\",\")}}`;\n\t}\n\t// undefined / function / symbol — never present in decoded chain rows.\n\t// Fall through as null so tests catch unexpected shapes.\n\treturn \"null\";\n}\n\nfunction scalar(value: string | number | bigint | null | undefined): string {\n\tif (value === null || value === undefined) return NULL_MARKER;\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) {\n\t\t\tthrow new Error(\"semanticDigest: non-finite number\");\n\t\t}\n\t\tif (!Number.isInteger(value)) {\n\t\t\t// Chain heights, indexes, timestamps are all integer; a fractional\n\t\t\t// value here is a schema bug we want to surface loudly, not silently\n\t\t\t// serialize with float precision that varies by runtime.\n\t\t\tthrow new Error(`semanticDigest: non-integer scalar ${value}`);\n\t\t}\n\t\treturn String(value);\n\t}\n\tif (typeof value === \"bigint\") return value.toString();\n\treturn value;\n}\n\nfunction jsonScalar(value: unknown): string {\n\treturn value === null || value === undefined\n\t\t? NULL_MARKER\n\t\t: canonicalJson(value);\n}\n\nfunction sha256Hex(input: Uint8Array | string): string {\n\treturn createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\nexport interface BlockDigestInput {\n\theight: number | bigint;\n\thash: string;\n\tparent_hash: string;\n\tburn_block_height: number | bigint;\n\tburn_block_hash: string | null;\n\tindex_block_hash: string | null;\n\ttimestamp: number | bigint;\n}\n\nexport interface TransactionDigestInput {\n\ttx_id: string;\n\tblock_height: number | bigint;\n\ttx_index: number;\n\ttype: string;\n\tsender: string;\n\tstatus: string;\n\tcontract_id: string | null;\n\tfunction_name: string | null;\n\tfunction_args: unknown;\n\traw_result: string | null;\n\traw_tx: string;\n}\n\nexport interface EventDigestInput {\n\ttx_id: string;\n\tblock_height: number | bigint;\n\tevent_index: number;\n\ttype: string;\n\tdata: unknown;\n}\n\nfunction encodeBlock(row: BlockDigestInput): string {\n\treturn [\n\t\tscalar(row.height),\n\t\tscalar(row.hash),\n\t\tscalar(row.parent_hash),\n\t\tscalar(row.burn_block_height),\n\t\tscalar(row.burn_block_hash),\n\t\tscalar(row.index_block_hash),\n\t\tscalar(row.timestamp),\n\t].join(FIELD_SEP);\n}\n\nfunction encodeTransaction(row: TransactionDigestInput): string {\n\treturn [\n\t\tscalar(row.tx_id),\n\t\tscalar(row.block_height),\n\t\tscalar(row.tx_index),\n\t\tscalar(row.type),\n\t\tscalar(row.sender),\n\t\tscalar(row.status),\n\t\tscalar(row.contract_id),\n\t\tscalar(row.function_name),\n\t\tjsonScalar(row.function_args),\n\t\tscalar(row.raw_result),\n\t\tscalar(row.raw_tx),\n\t].join(FIELD_SEP);\n}\n\nfunction encodeEvent(row: EventDigestInput): string {\n\treturn [\n\t\tscalar(row.tx_id),\n\t\tscalar(row.block_height),\n\t\tscalar(row.event_index),\n\t\tscalar(row.type),\n\t\tjsonScalar(row.data),\n\t].join(FIELD_SEP);\n}\n\n/**\n * v1 per-row digests. Each is `sha256(encoded_row_bytes)` in lowercase hex.\n * The encoded byte form is stable across runtimes; the sha256 is stable\n * across CPU architectures. Never mutate an encoder without a v-bump.\n */\nexport interface SemanticDigestV1 {\n\treadonly spec: SemanticDigestSpec;\n\treadonly encodeBlock: (row: BlockDigestInput) => string;\n\treadonly encodeTransaction: (row: TransactionDigestInput) => string;\n\treadonly encodeEvent: (row: EventDigestInput) => string;\n\treadonly block: (row: BlockDigestInput) => string;\n\treadonly transaction: (row: TransactionDigestInput) => string;\n\treadonly event: (row: EventDigestInput) => string;\n}\n\nexport interface SemanticDigestApi {\n\treadonly v1: SemanticDigestV1;\n}\n\nexport const semanticDigest: SemanticDigestApi = {\n\tv1: {\n\t\tspec: SEMANTIC_DIGEST_SPEC_V1,\n\t\tencodeBlock,\n\t\tencodeTransaction,\n\t\tencodeEvent,\n\t\tblock: (row: BlockDigestInput): string =>\n\t\t\tsha256Hex(textEncoder.encode(encodeBlock(row))),\n\t\ttransaction: (row: TransactionDigestInput): string =>\n\t\t\tsha256Hex(textEncoder.encode(encodeTransaction(row))),\n\t\tevent: (row: EventDigestInput): string =>\n\t\t\tsha256Hex(textEncoder.encode(encodeEvent(row))),\n\t},\n};\n\n/**\n * Streaming rollup — feed rows one at a time in their declared order, read the\n * rolled digest at the end. Two callers passing the same rows in the same order\n * produce the same rollup, regardless of batching.\n *\n * The rollup composes: partition_digest = sha256(concat(row_digest_bytes)),\n * where each row_digest_bytes = sha256(encode(row)). This is why the streaming\n * implementation can be constant-memory — the outer sha256 accepts row digests\n * incrementally.\n */\nexport class SemanticDigestRollup {\n\tprivate readonly hasher = createHash(\"sha256\");\n\tprivate count = 0;\n\n\tstatic forDataset(\n\t\t_dataset: \"blocks\" | \"transactions\" | \"events\",\n\t): SemanticDigestRollup {\n\t\treturn new SemanticDigestRollup();\n\t}\n\n\tappendRowDigest(rowDigestHex: string): void {\n\t\tthis.hasher.update(Buffer.from(rowDigestHex, \"hex\"));\n\t\tthis.count += 1;\n\t}\n\n\trowCount(): number {\n\t\treturn this.count;\n\t}\n\n\t/** Empty rollups return `null`, not the sha256 of nothing, so an empty\n\t * range never compares equal to another empty range on a different\n\t * dataset. Callers assign `null` semantics deliberately. */\n\tdigest(): string | null {\n\t\treturn this.count === 0 ? null : this.hasher.copy().digest(\"hex\");\n\t}\n\n\tspec(): SemanticDigestSpec {\n\t\treturn SEMANTIC_DIGEST_SPEC_V1;\n\t}\n}\n\n/**\n * Per-partition digest record — the shape that lands in the snapshot manifest.\n * Complements the existing `sha256:parquet-object` per-partition digest by\n * proving semantic identity independent of file encoding.\n */\nexport interface PartitionSemanticDigest {\n\tdataset: \"blocks\" | \"transactions\" | \"events\";\n\tfrom_block: number;\n\tto_block: number;\n\trow_count: number;\n\tdigest: string | null;\n\tdigest_spec: SemanticDigestSpec;\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "4E237BBF53E5627D64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -33,4 +33,4 @@ type ProgressOptions = {
|
|
|
33
33
|
};
|
|
34
34
|
declare function formatDuration(seconds: number): string;
|
|
35
35
|
declare function createProgressReporter(options: ProgressOptions): ProgressReporter;
|
|
36
|
-
export {
|
|
36
|
+
export { ProgressOptions, ProgressReporter, createProgressReporter, formatDuration };
|