@git-stunts/git-warp 10.8.0 → 11.2.1

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 (70) hide show
  1. package/README.md +53 -32
  2. package/SECURITY.md +64 -0
  3. package/bin/cli/commands/check.js +168 -0
  4. package/bin/cli/commands/doctor/checks.js +422 -0
  5. package/bin/cli/commands/doctor/codes.js +46 -0
  6. package/bin/cli/commands/doctor/index.js +239 -0
  7. package/bin/cli/commands/doctor/types.js +89 -0
  8. package/bin/cli/commands/history.js +73 -0
  9. package/bin/cli/commands/info.js +139 -0
  10. package/bin/cli/commands/install-hooks.js +128 -0
  11. package/bin/cli/commands/materialize.js +99 -0
  12. package/bin/cli/commands/path.js +88 -0
  13. package/bin/cli/commands/query.js +194 -0
  14. package/bin/cli/commands/registry.js +28 -0
  15. package/bin/cli/commands/seek.js +592 -0
  16. package/bin/cli/commands/trust.js +154 -0
  17. package/bin/cli/commands/verify-audit.js +113 -0
  18. package/bin/cli/commands/view.js +45 -0
  19. package/bin/cli/infrastructure.js +336 -0
  20. package/bin/cli/schemas.js +177 -0
  21. package/bin/cli/shared.js +244 -0
  22. package/bin/cli/types.js +85 -0
  23. package/bin/presenters/index.js +6 -0
  24. package/bin/presenters/text.js +136 -0
  25. package/bin/warp-graph.js +5 -2346
  26. package/index.d.ts +32 -2
  27. package/index.js +2 -0
  28. package/package.json +8 -7
  29. package/src/domain/WarpGraph.js +106 -3252
  30. package/src/domain/errors/QueryError.js +2 -2
  31. package/src/domain/errors/TrustError.js +29 -0
  32. package/src/domain/errors/index.js +1 -0
  33. package/src/domain/services/AuditMessageCodec.js +137 -0
  34. package/src/domain/services/AuditReceiptService.js +471 -0
  35. package/src/domain/services/AuditVerifierService.js +693 -0
  36. package/src/domain/services/HttpSyncServer.js +36 -22
  37. package/src/domain/services/MessageCodecInternal.js +3 -0
  38. package/src/domain/services/MessageSchemaDetector.js +2 -2
  39. package/src/domain/services/SyncAuthService.js +69 -3
  40. package/src/domain/services/WarpMessageCodec.js +4 -1
  41. package/src/domain/trust/TrustCanonical.js +42 -0
  42. package/src/domain/trust/TrustCrypto.js +111 -0
  43. package/src/domain/trust/TrustEvaluator.js +180 -0
  44. package/src/domain/trust/TrustRecordService.js +274 -0
  45. package/src/domain/trust/TrustStateBuilder.js +209 -0
  46. package/src/domain/trust/canonical.js +68 -0
  47. package/src/domain/trust/reasonCodes.js +64 -0
  48. package/src/domain/trust/schemas.js +160 -0
  49. package/src/domain/trust/verdict.js +42 -0
  50. package/src/domain/types/git-cas.d.ts +20 -0
  51. package/src/domain/utils/RefLayout.js +59 -0
  52. package/src/domain/warp/PatchSession.js +18 -0
  53. package/src/domain/warp/Writer.js +18 -3
  54. package/src/domain/warp/_internal.js +26 -0
  55. package/src/domain/warp/_wire.js +58 -0
  56. package/src/domain/warp/_wiredMethods.d.ts +100 -0
  57. package/src/domain/warp/checkpoint.methods.js +397 -0
  58. package/src/domain/warp/fork.methods.js +323 -0
  59. package/src/domain/warp/materialize.methods.js +188 -0
  60. package/src/domain/warp/materializeAdvanced.methods.js +339 -0
  61. package/src/domain/warp/patch.methods.js +529 -0
  62. package/src/domain/warp/provenance.methods.js +284 -0
  63. package/src/domain/warp/query.methods.js +279 -0
  64. package/src/domain/warp/subscribe.methods.js +272 -0
  65. package/src/domain/warp/sync.methods.js +549 -0
  66. package/src/infrastructure/adapters/GitGraphAdapter.js +67 -1
  67. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
  68. package/src/ports/CommitPort.js +10 -0
  69. package/src/ports/RefPort.js +17 -0
  70. package/src/hooks/post-merge.sh +0 -60
package/README.md CHANGED
@@ -5,12 +5,14 @@
5
5
  [![npm version](https://badge.fury.io/js/%40git-stunts%2Fgit-warp.svg)](https://www.npmjs.com/package/@git-stunts/git-warp)
6
6
 
7
7
  <p align="center">
8
- <img src="hero.gif" alt="git-warp CLI demo" width="600">
8
+ <img src="docs/images/hero.gif" alt="git-warp CLI demo" width="600">
9
9
  </p>
10
10
 
11
- A multi-writer graph database that uses Git commits as its storage substrate. Graph state is stored as commits pointing to the empty tree (`4b825dc...`), making the data invisible to normal Git workflows while inheriting Git's content-addressing, cryptographic integrity, and distributed replication.
11
+ ## The Core Idea
12
12
 
13
- Writers collaborate without coordination using CRDTs (OR-Set for nodes/edges, LWW registers for properties). Every writer maintains an independent patch chain; materialization deterministically merges all writers into a single consistent view.
13
+ **git-warp** is a graph database that doesn't need a database server. It stores all its data inside a Git repository by abusing a clever trick: every piece of data is a Git commit that points to the **empty tree** — a special object that exists in every Git repo. Because the commits don't reference any actual files, they're completely invisible to normal Git operations like `git log`, `git diff`, or `git status`. Your codebase stays untouched, but there's a full graph database living alongside it.
14
+
15
+ Writers collaborate without coordination using CRDTs (Conflict-free Replicated Data Types) that guarantee deterministic convergence regardless of what order the patches arrive in.
14
16
 
15
17
  ```bash
16
18
  npm install @git-stunts/git-warp @git-stunts/plumbing
@@ -31,19 +33,18 @@ const graph = await WarpGraph.open({
31
33
  persistence,
32
34
  graphName: 'demo',
33
35
  writerId: 'writer-1',
34
- autoMaterialize: true, // auto-materialize on query
35
36
  });
36
37
 
37
- // Write data using the patch builder
38
- await (await graph.createPatch())
39
- .addNode('user:alice')
40
- .setProperty('user:alice', 'name', 'Alice')
41
- .setProperty('user:alice', 'role', 'admin')
42
- .addNode('user:bob')
43
- .setProperty('user:bob', 'name', 'Bob')
44
- .addEdge('user:alice', 'user:bob', 'manages')
45
- .setEdgeProperty('user:alice', 'user:bob', 'manages', 'since', '2024')
46
- .commit();
38
+ // Write data single await with graph.patch()
39
+ await graph.patch(p => {
40
+ p.addNode('user:alice')
41
+ .setProperty('user:alice', 'name', 'Alice')
42
+ .setProperty('user:alice', 'role', 'admin')
43
+ .addNode('user:bob')
44
+ .setProperty('user:bob', 'name', 'Bob')
45
+ .addEdge('user:alice', 'user:bob', 'manages')
46
+ .setEdgeProperty('user:alice', 'user:bob', 'manages', 'since', '2024');
47
+ });
47
48
 
48
49
  // Query the graph
49
50
  const result = await graph.query()
@@ -54,15 +55,21 @@ const result = await graph.query()
54
55
 
55
56
  ## How It Works
56
57
 
57
- Each writer creates **patches**: atomic batches of graph operations (add/remove nodes, add/remove edges, set properties). Patches are serialized as CBOR-encoded Git commit messages pointing to the empty tree, forming a per-writer chain under `refs/warp/<graphName>/writers/<writerId>`.
58
+ ### The Multi-Writer Problem (and How It's Solved)
59
+
60
+ Multiple people (or machines, or processes) can write to the same graph **simultaneously, without any coordination**. There's no central server, no locking, no "wait your turn."
61
+
62
+ Each writer maintains their own independent chain of **patches** — atomic batches of operations like "add this node, set this property, create this edge." These patches are stored as Git commits under refs like `refs/warp/<graphName>/writers/<writerId>`.
58
63
 
59
- **Materialization** replays all patches from all writers, applying CRDT merge semantics:
64
+ When you want to read the graph, you **materialize** which means replaying all patches from all writers and merging them into a single consistent view. The specific CRDT rules are:
60
65
 
61
- - **Nodes and edges** use an Observed-Remove Set (OR-Set). An add wins over a concurrent remove unless the remove has observed the specific add event.
62
- - **Properties** use Last-Write-Wins (LWW) registers, ordered by Lamport timestamp, then writer ID, then patch SHA.
63
- - **Version vectors** track causality across writers, ensuring deterministic convergence regardless of patch arrival order.
66
+ - **Nodes and edges** use an OR-Set (Observed-Remove Set). If Alice adds a node and Bob concurrently deletes it, the add wins — unless Bob's delete specifically observed Alice's add. This is the "add wins over concurrent remove" principle.
67
+ - **Properties** use LWW (Last-Writer-Wins) registers. If two writers set the same property at the same time, the one with the higher Lamport timestamp wins. Ties are broken by writer ID (lexicographic), then by patch SHA.
68
+ - **Version vectors** track causality across writers so the system knows which operations are concurrent vs. causally ordered.
64
69
 
65
- **Checkpoints** snapshot materialized state into a single commit for fast incremental recovery. Subsequent materializations only need to replay patches created after the checkpoint.
70
+ Every operation gets a unique **EventId** `(lamport, writerId, patchSha, opIndex)` which creates a total ordering that makes merge results identical no matter which machine runs them.
71
+
72
+ **Checkpoints** snapshot the materialized state into a single commit for fast incremental recovery. Subsequent materializations only need to replay patches created after the checkpoint.
66
73
 
67
74
  ## Multi-Writer Collaboration
68
75
 
@@ -76,10 +83,9 @@ const graphA = await WarpGraph.open({
76
83
  writerId: 'alice',
77
84
  });
78
85
 
79
- await (await graphA.createPatch())
80
- .addNode('doc:1')
81
- .setProperty('doc:1', 'title', 'Draft')
82
- .commit();
86
+ await graphA.patch(p => {
87
+ p.addNode('doc:1').setProperty('doc:1', 'title', 'Draft');
88
+ });
83
89
 
84
90
  // Writer B (on machine B)
85
91
  const graphB = await WarpGraph.open({
@@ -88,10 +94,9 @@ const graphB = await WarpGraph.open({
88
94
  writerId: 'bob',
89
95
  });
90
96
 
91
- await (await graphB.createPatch())
92
- .addNode('doc:2')
93
- .setProperty('doc:2', 'title', 'Notes')
94
- .commit();
97
+ await graphB.patch(p => {
98
+ p.addNode('doc:2').setProperty('doc:2', 'title', 'Notes');
99
+ });
95
100
 
96
101
  // After git push/pull, materialize merges both writers
97
102
  const state = await graphA.materialize();
@@ -120,13 +125,11 @@ await graphA.syncWith(graphB);
120
125
 
121
126
  ## Querying
122
127
 
123
- Query methods require materialized state. Either call `materialize()` first, or pass `autoMaterialize: true` to `WarpGraph.open()` to handle this automatically.
128
+ Query methods auto-materialize by default. Just open a graph and start querying:
124
129
 
125
130
  ### Simple Queries
126
131
 
127
132
  ```javascript
128
- await graph.materialize();
129
-
130
133
  await graph.getNodes(); // ['user:alice', 'user:bob']
131
134
  await graph.hasNode('user:alice'); // true
132
135
  await graph.getNodeProps('user:alice'); // Map { 'name' => 'Alice', 'role' => 'admin' }
@@ -492,9 +495,27 @@ npm run test:deno # Deno: API integration tests
492
495
  npm run test:matrix # All runtimes in parallel
493
496
  ```
494
497
 
498
+ ## When git-warp is Most Useful
499
+
500
+ - **Distributed configuration management.** A fleet of servers each writing their own state into a shared graph without a central database.
501
+ - **Offline-first field applications.** Collecting data in the field with no connectivity; merging cleanly when back online.
502
+ - **Collaborative knowledge bases.** Researchers curating nodes and relationships independently.
503
+ - **Git-native issue/project tracking.** Embedding a full project graph directly in the repo.
504
+ - **Audit-critical systems.** Tamper-evident records with cryptographic proof (via Audit Receipts).
505
+ - **IoT sensor networks.** Sensors logging readings and relationships, syncing when bandwidth allows.
506
+ - **Game world state.** Modders independently adding content that composes without a central manager.
507
+
508
+ ## When NOT to Use It
509
+
510
+ - **High-throughput transactional workloads.** If you need thousands of writes per second with immediate consistency, use Postgres or Redis.
511
+ - **Large binary or blob storage.** Data lives in Git commit messages (default cap 1 MB). Use object storage for images or videos.
512
+ - **Sub-millisecond read latency.** Materialization has overhead. Use an in-memory database for real-time gaming physics or HFT.
513
+ - **Simple key-value storage.** If you don't have relationships or need traversals, a graph database is overkill.
514
+ - **Non-Git environments.** The value proposition depends on Git infrastructure (push/pull, content-addressing).
515
+
495
516
  ## AIΩN Foundations Series
496
517
 
497
- This package is the reference implementation of WARP (Worldline Algebra for Recursive Provenance) graphs as described in the AIΩN Foundations Series. The papers define WARP graphs as a minimal recursive state object ([Paper I](https://doi.org/10.5281/zenodo.17908005)), equip them with deterministic tick-based operational semantics ([Paper II](https://doi.org/10.5281/zenodo.17934512)), develop computational holography, provenance payloads, and prefix forks ([Paper III](https://doi.org/10.5281/zenodo.17963669)), and introduce observer geometry with rulial distance and temporal logic ([Paper IV](https://doi.org/10.5281/zenodo.18038297)). This codebase implements the core data structures and multi-writer collaboration protocol described in those papers.
518
+ This package is the reference implementation of WARP (Worldline Algebra for Recursive Provenance) graphs as described in the AIΩN Foundations Series. The papers formalize the graph as a minimal recursive state object ([Paper I](https://doi.org/10.5281/zenodo.17908005)), equip it with deterministic tick-based semantics ([Paper II](https://doi.org/10.5281/zenodo.17934512)), develop computational holography and provenance payloads ([Paper III](https://doi.org/10.5281/zenodo.17963669)), and introduce observer geometry with the translation cost metric ([Paper IV](https://doi.org/10.5281/zenodo.18038297)).
498
519
 
499
520
  ## License
500
521
 
package/SECURITY.md CHANGED
@@ -113,6 +113,70 @@ await graph.syncWith('http://peer:3000', {
113
113
  });
114
114
  ```
115
115
 
116
+ ## Dependency Risk Assessment
117
+
118
+ | Package | Type | Risk | Notes |
119
+ |---|---|---|---|
120
+ | `@git-stunts/plumbing` | Runtime | Low | Internal package, spawns git processes with strict whitelist |
121
+ | `@git-stunts/alfred` | Runtime | Low | Retry/backoff utility, no I/O |
122
+ | `@git-stunts/trailer-codec` | Runtime | Low | Pure string encoding, no I/O |
123
+ | `cbor-x` | Runtime | Medium | Binary parser processing untrusted sync payloads; mitigated by body size limit in HttpSyncServer (4 MB default) |
124
+ | `roaring` | Runtime | Medium | Native C++ bindings (N-API); largest attack surface but sandboxed by N-API boundary, no network-facing input |
125
+ | `zod` | Runtime | Low | Schema validation, pure JS |
126
+ | `elkjs` | Runtime (lazy) | Low | ELK layout engine, pure JS, lazy-loaded only for `--view` |
127
+ | `chalk` | CLI-only | Negligible | Terminal coloring, no security surface |
128
+ | `boxen` | CLI-only | Negligible | Terminal box drawing |
129
+ | `cli-table3` | CLI-only | Negligible | Terminal table rendering |
130
+ | `string-width` | CLI-only | Negligible | String measurement |
131
+ | `strip-ansi` | Inlined | Negligible | ANSI escape removal; inlined into `src/visualization/utils/ansi.js` since v10.1.2, no longer a direct dependency |
132
+ | `open` | Transitive | Low | Opens URLs in browser; transitive dependency only, invoked by `--view` flag |
133
+
134
+ ## Accepted Risks
135
+
136
+ | Risk | Severity | Owner | Expiry | Mitigation |
137
+ |---|---|---|---|---|
138
+ | `roaring` native bindings could have memory-safety bugs | Medium | @jross | 2026-08-01 | N-API sandbox; no untrusted input reaches bitmap code directly |
139
+ | `cbor-x` parser handles untrusted sync payloads | Medium | @jross | 2026-08-01 | 4 MB body size limit in HttpSyncServer; schema validation post-parse |
140
+ | Nonce cache lost on restart allows replay within clock skew window | Low | @jross | 2026-08-01 | 5-minute TTL; recommend TLS in production |
141
+ | No rate limiting on sync endpoint | Low | @jross | 2026-08-01 | Deploy behind reverse proxy with rate limiting |
142
+
143
+ ## Threat Model Boundaries
144
+
145
+ | Category | In Scope | Out of Scope |
146
+ |---|---|---|
147
+ | **Data integrity** | CRDT convergence, CAS ref updates, patch ordering | Filesystem corruption, disk failure |
148
+ | **Authentication** | HMAC-SHA256 sync auth, key rotation, replay protection | User identity management, OAuth/OIDC |
149
+ | **Authorization** | Writer whitelist (per-graph allowed writers) | Role-based access control, per-node ACLs |
150
+ | **Transport** | Body signing, tamper detection | TLS termination (delegated to reverse proxy) |
151
+ | **Availability** | Body size limits, streaming for large graphs | DDoS protection, rate limiting (delegated to infrastructure) |
152
+ | **Supply chain** | `npm audit` in CI, pinned dependencies via lockfile | Runtime SCA scanning, SBOM generation |
153
+
154
+ ## Writer Authorization
155
+
156
+ The sync server supports an optional writer whitelist that restricts which writer IDs can submit patches through the HTTP sync protocol.
157
+
158
+ ### Configuration
159
+
160
+ ```js
161
+ await graph.serve({
162
+ port: 3000,
163
+ httpPort: new NodeHttpAdapter(),
164
+ auth: {
165
+ keys: { default: 'your-shared-secret' },
166
+ mode: 'enforce',
167
+ },
168
+ allowedWriters: ['alice', 'bob', 'node-1'],
169
+ });
170
+ ```
171
+
172
+ ### Rules
173
+
174
+ - Writer ID matching is **case-sensitive** and follows the pattern `[A-Za-z0-9._-]+` (1–64 characters)
175
+ - Writer IDs are validated at server startup; invalid IDs throw immediately
176
+ - When `allowedWriters` is not set (default), all authenticated writers are permitted
177
+ - When set, sync requests containing patches from unlisted writers receive HTTP 403 (`FORBIDDEN_WRITER`)
178
+ - The `forbiddenWriterRejects` metric tracks rejected requests
179
+
116
180
  ## Reporting a Vulnerability
117
181
 
118
182
  If you discover a security vulnerability, please send an e-mail to [james@flyingrobots.dev](mailto:james@flyingrobots.dev).
@@ -0,0 +1,168 @@
1
+ import HealthCheckService from '../../../src/domain/services/HealthCheckService.js';
2
+ import ClockAdapter from '../../../src/infrastructure/adapters/ClockAdapter.js';
3
+ import { buildCheckpointRef, buildCoverageRef } from '../../../src/domain/utils/RefLayout.js';
4
+ import { EXIT_CODES } from '../infrastructure.js';
5
+ import { openGraph, applyCursorCeiling, emitCursorWarning, readCheckpointDate, createHookInstaller } from '../shared.js';
6
+
7
+ /** @typedef {import('../types.js').CliOptions} CliOptions */
8
+ /** @typedef {import('../types.js').Persistence} Persistence */
9
+ /** @typedef {import('../types.js').WarpGraphInstance} WarpGraphInstance */
10
+
11
+ /** @param {Persistence} persistence */
12
+ async function getHealth(persistence) {
13
+ const clock = ClockAdapter.global();
14
+ const healthService = new HealthCheckService({ persistence: /** @type {*} */ (persistence), clock }); // TODO(ts-cleanup): narrow port type
15
+ return await healthService.getHealth();
16
+ }
17
+
18
+ /** @param {WarpGraphInstance} graph */
19
+ async function getGcMetrics(graph) {
20
+ await graph.materialize();
21
+ return graph.getGCMetrics();
22
+ }
23
+
24
+ /** @param {WarpGraphInstance} graph */
25
+ async function collectWriterHeads(graph) {
26
+ const frontier = await graph.getFrontier();
27
+ return [...frontier.entries()]
28
+ .sort(([a], [b]) => a.localeCompare(b))
29
+ .map(([writerId, sha]) => ({ writerId, sha }));
30
+ }
31
+
32
+ /**
33
+ * @param {Persistence} persistence
34
+ * @param {string} graphName
35
+ */
36
+ async function loadCheckpointInfo(persistence, graphName) {
37
+ const checkpointRef = buildCheckpointRef(graphName);
38
+ const checkpointSha = await persistence.readRef(checkpointRef);
39
+ const checkpointDate = await readCheckpointDate(persistence, checkpointSha);
40
+ const checkpointAgeSeconds = computeAgeSeconds(checkpointDate);
41
+
42
+ return {
43
+ ref: checkpointRef,
44
+ sha: checkpointSha || null,
45
+ date: checkpointDate,
46
+ ageSeconds: checkpointAgeSeconds,
47
+ };
48
+ }
49
+
50
+ /** @param {string|null} checkpointDate */
51
+ function computeAgeSeconds(checkpointDate) {
52
+ if (!checkpointDate) {
53
+ return null;
54
+ }
55
+ const parsed = Date.parse(checkpointDate);
56
+ if (Number.isNaN(parsed)) {
57
+ return null;
58
+ }
59
+ return Math.max(0, Math.floor((Date.now() - parsed) / 1000));
60
+ }
61
+
62
+ /**
63
+ * @param {Persistence} persistence
64
+ * @param {string} graphName
65
+ * @param {Array<{writerId: string, sha: string}>} writerHeads
66
+ */
67
+ async function loadCoverageInfo(persistence, graphName, writerHeads) {
68
+ const coverageRef = buildCoverageRef(graphName);
69
+ const coverageSha = await persistence.readRef(coverageRef);
70
+ const missingWriters = coverageSha
71
+ ? await findMissingWriters(persistence, writerHeads, coverageSha)
72
+ : [];
73
+
74
+ return {
75
+ ref: coverageRef,
76
+ sha: coverageSha || null,
77
+ missingWriters: missingWriters.sort(),
78
+ };
79
+ }
80
+
81
+ /**
82
+ * @param {Persistence} persistence
83
+ * @param {Array<{writerId: string, sha: string}>} writerHeads
84
+ * @param {string} coverageSha
85
+ */
86
+ async function findMissingWriters(persistence, writerHeads, coverageSha) {
87
+ const missing = [];
88
+ for (const head of writerHeads) {
89
+ const reachable = await persistence.isAncestor(head.sha, coverageSha);
90
+ if (!reachable) {
91
+ missing.push(head.writerId);
92
+ }
93
+ }
94
+ return missing;
95
+ }
96
+
97
+ /**
98
+ * @param {{repo: string, graphName: string, health: *, checkpoint: *, writerHeads: Array<{writerId: string, sha: string}>, coverage: *, gcMetrics: *, hook: *|null, status: *|null}} params
99
+ */
100
+ function buildCheckPayload({
101
+ repo,
102
+ graphName,
103
+ health,
104
+ checkpoint,
105
+ writerHeads,
106
+ coverage,
107
+ gcMetrics,
108
+ hook,
109
+ status,
110
+ }) {
111
+ return {
112
+ repo,
113
+ graph: graphName,
114
+ health,
115
+ checkpoint,
116
+ writers: {
117
+ count: writerHeads.length,
118
+ heads: writerHeads,
119
+ },
120
+ coverage,
121
+ gc: gcMetrics,
122
+ hook: hook || null,
123
+ status: status || null,
124
+ };
125
+ }
126
+
127
+ /** @param {string} repoPath */
128
+ function getHookStatusForCheck(repoPath) {
129
+ try {
130
+ const installer = createHookInstaller();
131
+ return installer.getHookStatus(repoPath);
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Handles the `check` command: reports graph health, GC, and hook status.
139
+ * @param {{options: CliOptions}} params
140
+ * @returns {Promise<{payload: *, exitCode: number}>}
141
+ */
142
+ export default async function handleCheck({ options }) {
143
+ const { graph, graphName, persistence } = await openGraph(options);
144
+ const cursorInfo = await applyCursorCeiling(graph, persistence, graphName);
145
+ emitCursorWarning(cursorInfo, null);
146
+ const health = await getHealth(persistence);
147
+ const gcMetrics = await getGcMetrics(graph);
148
+ const status = await graph.status();
149
+ const writerHeads = await collectWriterHeads(graph);
150
+ const checkpoint = await loadCheckpointInfo(persistence, graphName);
151
+ const coverage = await loadCoverageInfo(persistence, graphName, writerHeads);
152
+ const hook = getHookStatusForCheck(options.repo);
153
+
154
+ return {
155
+ payload: buildCheckPayload({
156
+ repo: options.repo,
157
+ graphName,
158
+ health,
159
+ checkpoint,
160
+ writerHeads,
161
+ coverage,
162
+ gcMetrics,
163
+ hook,
164
+ status,
165
+ }),
166
+ exitCode: EXIT_CODES.OK,
167
+ };
168
+ }