@ibgib/space-gib 0.0.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 (84) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/Dockerfile +14 -0
  3. package/IMPLEMENTATION.md +484 -0
  4. package/README.md +46 -0
  5. package/dist/client/bootstrap.mjs +58 -0
  6. package/dist/client/bootstrap.mjs.map +7 -0
  7. package/dist/client/chunk-CT47Z5WU.mjs +21 -0
  8. package/dist/client/chunk-CT47Z5WU.mjs.map +7 -0
  9. package/dist/client/chunk-RHEDTRKF.mjs +235 -0
  10. package/dist/client/chunk-RHEDTRKF.mjs.map +7 -0
  11. package/dist/client/index.html +147 -0
  12. package/dist/client/index.mjs +2 -0
  13. package/dist/client/index.mjs.map +7 -0
  14. package/dist/client/script.mjs +2 -0
  15. package/dist/client/script.mjs.map +7 -0
  16. package/dist/client/style.css +605 -0
  17. package/dist/respec-gib.node.mjs +5 -0
  18. package/dist/server/server.mjs +20157 -0
  19. package/dist/server/server.mjs.map +7 -0
  20. package/generate-version-file.js +35 -0
  21. package/package.json +27 -0
  22. package/src/client/AUTO-GENERATED-version.mts +11 -0
  23. package/src/client/README.md +19 -0
  24. package/src/client/api/function-infos.web.mts +38 -0
  25. package/src/client/api/space-gib-api-bridge.mts +85 -0
  26. package/src/client/bootstrap.mts +49 -0
  27. package/src/client/components/keystone-creator/keystone-creator.css +139 -0
  28. package/src/client/components/keystone-creator/keystone-creator.html +26 -0
  29. package/src/client/components/keystone-creator/keystone-creator.mts +229 -0
  30. package/src/client/constants.mts +76 -0
  31. package/src/client/custom.d.ts +11 -0
  32. package/src/client/dev-tools.mts +540 -0
  33. package/src/client/helpers.web.mts +178 -0
  34. package/src/client/index.html +147 -0
  35. package/src/client/index.mts +59 -0
  36. package/src/client/script.mts +13 -0
  37. package/src/client/style.css +605 -0
  38. package/src/client/types.mts +85 -0
  39. package/src/client/ui/shell/space-gib-shell-constants.mts +24 -0
  40. package/src/client/ui/shell/space-gib-shell-service.mts +233 -0
  41. package/src/client/ui/shell/space-gib-shell-types.mts +5 -0
  42. package/src/client/witness/app/space-gib/space-gib-app-v1.mts +160 -0
  43. package/src/client/witness/app/space-gib/space-gib-constants.mts +38 -0
  44. package/src/client/witness/app/space-gib/space-gib-helper.mts +72 -0
  45. package/src/client/witness/app/space-gib/space-gib-types.mts +47 -0
  46. package/src/common/keystone-policies.mts +159 -0
  47. package/src/respec-gib.node.mts +6 -0
  48. package/src/server/README.md +18 -0
  49. package/src/server/bootstrap-helper.mts +141 -0
  50. package/src/server/bootstrap-helper.respec.mts +100 -0
  51. package/src/server/metaspace-nodeindexedspace/metaspace-nodeindexedspace.mts +85 -0
  52. package/src/server/path-constants.mts +89 -0
  53. package/src/server/path-helper.mts +101 -0
  54. package/src/server/path-helper.respec.mts +94 -0
  55. package/src/server/serve-gib/CHANGELOG.md +29 -0
  56. package/src/server/serve-gib/README.md +34 -0
  57. package/src/server/serve-gib/constants.mts +1 -0
  58. package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +104 -0
  59. package/src/server/serve-gib/handlers/api/health.handler.mts +23 -0
  60. package/src/server/serve-gib/handlers/api/health.respec.mts +51 -0
  61. package/src/server/serve-gib/handlers/api/ibgib/ibgib-handler-types.mts +49 -0
  62. package/src/server/serve-gib/handlers/api/ibgib/ibgib.handler.mts +176 -0
  63. package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +261 -0
  64. package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +146 -0
  65. package/src/server/serve-gib/handlers/api/keystone/keystone-get.handler.mts +198 -0
  66. package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +107 -0
  67. package/src/server/serve-gib/handlers/api/keystone/keystone-handler-types.mts +29 -0
  68. package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +70 -0
  69. package/src/server/serve-gib/handlers/api/keystone/keystone-post.respec.mts +130 -0
  70. package/src/server/serve-gib/handlers/error-handler.mts +36 -0
  71. package/src/server/serve-gib/handlers/handler-base.mts +383 -0
  72. package/src/server/serve-gib/handlers/static-handler.mts +82 -0
  73. package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +498 -0
  74. package/src/server/serve-gib/handlers/ws/ws-helper.mts +111 -0
  75. package/src/server/serve-gib/handlers/ws/ws-types.mts +53 -0
  76. package/src/server/serve-gib/serve-gib-helpers.mts +32 -0
  77. package/src/server/serve-gib/serve-gib-v1.mts +172 -0
  78. package/src/server/serve-gib/serve-gib.respec.mts +90 -0
  79. package/src/server/serve-gib/types.mts +102 -0
  80. package/src/server/server-constants.mts +2 -0
  81. package/src/server/server.mts +96 -0
  82. package/tsconfig.json +29 -0
  83. package/tsconfig.server.json +29 -0
  84. package/tsconfig.test.json +27 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # @ibgib/space-gib changelog
2
+
3
+ _note: as you implement features/fixes/etc., please document them here under the "Working Version" section. These will be moved to a concrete version number during the next publish._
4
+
5
+ ## Working Version
6
+
7
+ * feat: serve-gib microframework and core architecture
8
+ * established dual-target architecture for browser SPA client and Node.js server.
9
+ * implemented the `serve-gib` microframework with pipeline-based orchestration and async handlers.
10
+ * implemented `ServeGib_V1` with robust `try..catch..finally` logging and error handling.
11
+ * implemented `ServeGibHandlerBase` "pit of success" plumbing for automated matching and param parsing.
12
+ * established hierarchical folder-based handler organization mirroring URL paths (e.g., `api/ibgib/`).
13
+ * migrated all routing logic to modular, single-file handlers.
14
+ * feat: space-service and API endpoints
15
+ * implemented `SpaceService` with initial filesystem-backed storage.
16
+ * implemented `GET /api/health` liveness probe.
17
+ * implemented `GET /api/ibgib/:addr` and `GET /api/ibgib/graph/:addr` for graph retrieval.
18
+ * implemented `POST /api/keystone` for genesis and evolution turns with keystone-specific validation logic.
19
+ * feat: security hardening and path validation
20
+ * implemented `path-helper.mts` with hardened regex-based whitelists and blacklists.
21
+ * added `isSafePath` to block directory traversal, null bytes, redundant slashes, and dot-file access.
22
+ * enforced `MAX_PATH_LENGTH` (2048 chars) to prevent DoS-style payload attacks.
23
+ * integrated deep ibGib address validation using `validateIbGibAddr` from `ts-gib`.
24
+ * enforced V1 address schema (at most one dot in `gib` segment).
25
+ * feat: build and test infrastructure
26
+ * refactored `BuildSpaceGib` to use parallel, color-coded, and stateful error-tagged logging.
27
+ * established `tsconfig.server.json` and `tsconfig.test.json` for isolated environment builds.
28
+ * integrated `respec-gib` testing framework with a comprehensive suite for path validation.
29
+ * added `test:space-gib` orchestration to the monorepo root.
30
+ * feat: client-side components
31
+ * implemented `keystone-creator` component for initiating identity keystones.
package/Dockerfile ADDED
@@ -0,0 +1,14 @@
1
+ FROM node:22-alpine
2
+ WORKDIR /app
3
+
4
+ # The server is bundled into a single zero-dependency ESM file via esbuild
5
+ COPY dist/server ./server
6
+
7
+ # Client SPA (served as static files by the node server)
8
+ COPY dist/client ./client
9
+
10
+ ENV PORT=3000
11
+ ENV DATA_DIR=/data/ibgib-space
12
+
13
+ EXPOSE 3000
14
+ CMD ["node", "server/server.mjs"]
@@ -0,0 +1,484 @@
1
+ # space-gib — Implementation Spec
2
+
3
+ `space-gib` is the ibgib storage and identity provider service, deployed as a Docker
4
+ container alongside `blank-gib`. It is a single container that serves both:
5
+ 1. A **Node.js API server** wrapping `NodeFilesystemSpace_V1` (ibgib storage + keystone ops)
6
+ 2. A **browser SPA** (four-panel ibgib web app, client-side UI)
7
+
8
+ Published to: `https://ibgib.space`
9
+
10
+ ---
11
+
12
+ ## Core Design Decisions
13
+
14
+ ### Keystone-as-Scope (not Keystone-as-Identity)
15
+ Keystones here are **authorization contexts**, not identity credentials (identity comes
16
+ later and is a specialization of this pattern). Any ibgib write requires solving the
17
+ current context keystone. The keystone evolution history IS the audit log of what was
18
+ written under that scope.
19
+
20
+ - **Public reads**: all ibgibs are publicly readable — no auth required for GET
21
+ - **Keyed writes**: all mutations (put ibgib, evolve keystone) require a valid keystone evolution proof
22
+ - **Nested scopes**: keystones can have child keystones. Parent must be evolved to
23
+ create a child. Child has `parentTjpGib` in its `data` (soft-link, not rel8n).
24
+ Operating within child scope does NOT require parent secrets.
25
+
26
+ ### Manifest Ibgib Pattern
27
+ Content is NOT added to a scope directly. The flow is:
28
+ 1. Create content ibgib(s) + their DNA/dependency graph (existing ibgib machinery)
29
+ 2. **Server creates a manifest ibgib** listing the dependency graph addresses
30
+ 3. Context keystone is evolved with `claim.target = manifestIbGibAddr`
31
+ 4. Only then are the content ibgibs stored in the space
32
+
33
+ The keystone chain is therefore an audit log of manifest addresses — each frame
34
+ authorizes exactly one set of content additions.
35
+
36
+ ### Address Conventions
37
+ - Full ibgib address `ib^gib` is used everywhere (NOT just `tjpGib`)
38
+ - `tjpGib` is a reference convenience inside keystones and other ibgibs (stable timeline ID), not a full address
39
+ - URL-encode the `^` delimiter in route params: `ib%5Egib`
40
+ - Example full addr: `comment abc123%5E789def`
41
+ - **Fast TJP Extraction**: Use `getGibInfo` from `@ibgib/ts-gib/src/V1/transforms/transform-helper.mts` to efficiently extract `tjpGib` for routing and path mapping.
42
+
43
+ ### Storage Backend
44
+ `NodeFilesystemSpace_V1` from `@ibgib/core-gib`. Known pain point: filesystem
45
+ path length limits (255 char max). The space has built-in `mitigateLongPaths` logic.
46
+ Data volume mounted at `/data/ibgib-space` inside the container.
47
+
48
+ ---
49
+
50
+ ## Multitenant Architecture: Keystone-as-Domain
51
+
52
+ `space-gib` implements a horizontal scaling, multitenant architecture where the **Keystone** is the primary mechanism for circumscribing "Digital Private Property."
53
+
54
+ ### Domain Boundaries & Isolation
55
+ * **The Keystone is the Domain**: A "user" (human, org, or agent) is represented by a top-level keystone. This keystone acts as the boundary of a secure domain.
56
+ * **Composite Multitenancy**: Subscoped keystones create nested domains, allowing for independently evolving security contexts within a larger organization or project.
57
+ * **Physical Space Isolation**: Each top-level domain is isolated into its own **self-contained physical space** on the server's filesystem.
58
+ * **Bootstrap-per-Domain**: Every isolated domain has its own "Zero Space" and `bootstrap^gib`. This ensures that a domain is a completely portable, independent unit of the ibGib graph.
59
+
60
+ ### Deterministic Pathing & Scaling
61
+ To achieve horizontal scaling and combat OS path limits, physical storage paths are derived deterministically from the keystone's `tjpGib`:
62
+ * **Deterministic Mapping**: The relationship between a Domain (Identity) and its Physical Location is inherent in its `tjpGib`. No central registry or lookup database is required for V1 (Stateless Sharding).
63
+ * **Path Nesting & Balancing**: To prevent filesystem performance degradation, paths use substrings of the `tjpGib` for distribution:
64
+ * **Git-Style Fan-out**: Substrings are applied at each level to balance OS folders (e.g., `/[tjp_sub1]/[tjp_sub2]/[child_tjp_sub1]/...`).
65
+ * **Collision Resistance**: Substrings should be long enough (e.g., 8-12 chars) to avoid collisions while staying within path length limits.
66
+ * **Independent Parallelism**: The `lockSpace` mechanism is domain-specific. Updates to `User A` never contend for locks with `User B`, allowing for massive concurrent throughput across different domains.
67
+
68
+ ### Security & Visibility
69
+ * **Public Keystones**: Keystones are considered publicly visible records.
70
+ * **Secret Strength**: Security depends on the strength of the keystone's secret/passphrase (similar to BIP-39 mnemonic phrases in cryptowallets).
71
+ * **Auditability**: The keystone evolution chain within a domain provides a complete, immutable audit log of all authorized content manifest additions for that property boundary.
72
+
73
+ ### Identity: Aggregate & Checkpoint Details
74
+ To manage identity metadata without bloating every frame or requiring external ibGibs:
75
+ - **`checkpointDetails`**: Every X frames, a "checkpoint" is written into the keystone's `frameDetails`.
76
+ - **Rehydration**: To resolve the current identity metadata (e.g., username), the system walks back from the tip to the nearest checkpoint or genesis, then replays forward while aggregating the state using a **Last-Write-Wins** strategy.
77
+ - **Aggregate State**: This allows for evolving metadata (email, display name) while maintaining a verifiable, audit-friendly identity timeline.
78
+
79
+ ### Sync Sagas: Temp & Durable Spaces
80
+ The synchronization engine (Sync Saga) handles multi-device consistency using a "Commit Phase":
81
+ - **Temp Space**: Content is initially uploaded/created in an ephemeral `TempSpace`. This allows for long-running transfers without locking the primary domain.
82
+ - **Durable Space**: The "real" persistent storage.
83
+ - **Atomic Commit**: Once the content manifest is validated and stored in `TempSpace`, the system should lock the keystone context, evolves the keystone, and performs a straight-forward copy from `Temp` to `Durable`. Current implementation does not support identity correctly, only fully passes tests with some concrete implementations without identity.
84
+ * todo: incorporate sync engine with space-gib.
85
+
86
+ ### Near-Term Goal: Self-Contained Multitenancy
87
+ Our immediate priority is establishing the V1 multitenant foundation:
88
+ 1. **API Scoping**: Hit an endpoint like `PUT /api/keystone/evolve/:addr`.
89
+ 2. **Domain Resolution**: ServeGib_V1 extracts `tjpGib` and calculates `domainRootPath`.
90
+ 3. **Factory Closure Pattern**: Inject runtime factory functions (`fnZeroSpaceFactory`, `fnDefaultLocalSpaceFactory`) into the `Metaspace_Nodespace` that capture the `domainRootPath` in their closure.
91
+ 4. **Metaspace Orchestration**: Bootstrap the isolated domain and its private user space.
92
+ 5. **Keystone Evolution**: Execute authorized evolutions within that isolated boundary.
93
+
94
+ ### Factory Closure Pattern
95
+ To isolate domains without modifying core library types:
96
+ - **Runtime Injection**: The `MetaspaceFactory` is created dynamically per request (or domain).
97
+ - **Closure Capture**: The factory functions capture the calculated `domainRootPath` in their scope.
98
+ - **Zero Space Localization**: `fnZeroSpaceFactory` initializes the `NodeFilesystemSpace_V1` with the captured `baseDir`.
99
+ - **New Space Localization**: `fnDefaultLocalSpaceFactory` ensures any new local spaces created (e.g. on first run) are placed within the domain's subfolder.
100
+
101
+ ### Future Evolution
102
+ * **Sharding (V2+)**: The deterministic `tjpGib`-to-path mapping allows load balancers to route requests to specific shards without needing stateful knowledge of the user's location.
103
+ * **Manual Balancing (V3+)**: A future migration/balancing layer can add an optional stateful mapping to override default locations when domains need to move across physical nodes.
104
+
105
+ ---
106
+
107
+ ## Directory Structure
108
+
109
+ ```
110
+ apps/space-gib/
111
+ ├── src/
112
+ │ ├── client/ ← Browser SPA (ibgib-create-app skill pattern)
113
+ │ │ ├── index.html
114
+ │ │ ├── index.mts
115
+ │ │ ├── script.mts
116
+ │ │ ├── bootstrap.mts
117
+ │ │ ├── constants.mts
118
+ │ │ ├── types.mts
119
+ │ │ ├── helpers.web.mts
120
+ │ │ ├── style.css
121
+ │ │ ├── app/ ← App Witness (ibgib-create-app-witness skill)
122
+ │ │ │ ├── space-gib-app.mts
123
+ │ │ │ ├── space-gib-app-constants.mts
124
+ │ │ │ ├── space-gib-app-helpers.mts
125
+ │ │ │ └── space-gib-app-types.mts
126
+ │ │ ├── shell/ ← UI Shell (ibgib-create-shell skill)
127
+ │ │ │ ├── space-gib-shell.mts
128
+ │ │ │ ├── space-gib-shell-constants.mts
129
+ │ │ │ └── space-gib-shell-types.mts
130
+ │ │ └── components/ ← ibgib components (ibgib-create-component skill)
131
+ │ │ └── space-main/
132
+ │ │ ├── space-main.mts
133
+ │ │ ├── space-main.html
134
+ │ │ └── space-main.css
135
+ │ └── server/ ← Node.js API server
136
+ │ ├── server.mts ← Express entry point
137
+ │ ├── space.mts ← NodeFilesystemSpace_V1 singleton wrapper
138
+ │ ├── middleware/
139
+ │ │ └── validate-keystone.mts ← Middleware: verify evolution proof before write
140
+ │ └── routes/
141
+ │ ├── ibgib.routes.mts ← /api/ibgib/* routes
142
+ │ └── keystone.routes.mts ← /api/keystone/* routes
143
+ ├── dist/
144
+ │ ├── client/ ← Compiled SPA output
145
+ │ └── server/ ← Compiled Node.js output
146
+ ├── Dockerfile
147
+ ├── nginx.conf ← NOT used; kept for reference only
148
+ ├── package.json
149
+ ├── tsconfig.json ← Client compilation (browser target)
150
+ └── tsconfig.server.json ← Server compilation (Node.js target, ESM)
151
+ ```
152
+
153
+ ---
154
+
155
+ ## API Routes
156
+
157
+ All routes are under `/api/`. SPA is served at all other paths.
158
+
159
+ ### IbGib Routes (`/api/ibgib/`)
160
+
161
+ ```
162
+ POST /api/ibgib
163
+ Body: { ibGib: IbGib_V1 } ← single ibgib
164
+ Or: { ibGibs: IbGib_V1[] } ← batch (pack)
165
+ Auth: none (genesis/content ibgibs are self-legitimizing)
166
+ Notes: server validates internal ibgib structure; stores via NodeFilesystemSpace_V1
167
+
168
+ GET /api/ibgib/:domainAddr/:ibGibAddr
169
+ Param: domainAddr = URL-encoded domain keystone address
170
+ Param: ibGibAddr = URL-encoded ib^gib address
171
+ Query: ?getLatest=true|false (default false) — resolves the given addr to its latest timeline tip
172
+ Query: ?getGraph=true|false (default false) — returns the entire dependency graph instead of a single ibgib
173
+ Query: ?addrOnly=true|false (default false) — strips the response down to address(es) only, skipping ibgib body transmission.
174
+ - With getGraph=false: returns { addr, clientAddr } — cheap tip-check; if addr !== clientAddr, sync is needed.
175
+ - With getGraph=true: fetches the graph but returns { addr, clientAddr, addrs: string[] } — address manifest
176
+ for delta negotiation (client compares addrs against what it already has to compute the missing set).
177
+ Auth: none (all reads are public)
178
+ Returns (single): { ibGib: IbGib_V1 } or 404
179
+ Returns (graph): { addr: string, count: number, graph: Record<string, IbGib_V1> } or 404
180
+ Returns (addrOnly, no graph): { addr: string, clientAddr: string }
181
+ Returns (addrOnly + graph): { addr: string, clientAddr: string, addrs: string[] }
182
+ Notes: If getGraph is true, server walks rel8ns recursively. May be large.
183
+
184
+ POST /api/ibgib/pack
185
+ Body: { knownAddrs: IbGibAddr[] } ← addresses the client already has
186
+ Auth: none
187
+ Returns: { ibGibs: IbGib_V1[] } ← only the missing ones
188
+ Notes: future expansion for incremental graph sync (requires HTTP/2 or WebSocket)
189
+ ```
190
+
191
+ ### Keystone Routes (`/api/keystone/`)
192
+
193
+ ```
194
+ POST /api/keystone
195
+ Body: { ibGib: KeystoneIbGib_V1 } ← genesis frame (first frame, no prior)
196
+ Auth: none (genesis is self-legitimizing)
197
+ Returns: { addr: IbGibAddr, tjpGib: Gib }
198
+ Notes: server validates internal keystone structure; stores genesis frame
199
+
200
+ PUT /api/keystone/evolve/:addr
201
+ Param: addr = URL-encoded CURRENT TIP's full ib^gib address (CAS token)
202
+ Body: { ibGib: KeystoneIbGib_V1 } ← the new evolution frame (pre-computed by client)
203
+ Auth: implicit — valid challenge solutions in the frame ARE the auth
204
+ Returns: { addr: IbGibAddr } ← address of the accepted new frame
205
+ Error: 409 Conflict if :addr is not the current tip (stale — client must retry)
206
+ Notes: server validates: (1) chain continuity (hash pre-images match commitments),
207
+ (2) CAS check (:addr === current tip). Rejects if either fails.
208
+
209
+ GET /api/keystone/:domainAddr
210
+ Param: domainAddr = URL-encoded full ib^gib address (any frame OR tjpGib)
211
+ Query: ?getLatest=true|false (default true) — resolves the keystone to its latest evolution
212
+ Query: ?getGraph=true|false (default true) — returns the entire keystone timeline chain map
213
+ Auth: none
214
+ Returns: { domainGraph: Record<string, KeystoneIbGib_V1> } ← genesis to current tip mapped by address
215
+ Notes: domainAddr is always the full keystone address, never just the tjpGib. The tjpGib is used as an id sometimes, but an api addr param must be the full address.
216
+ ```
217
+
218
+ **On `/evolve` vs bare `PUT /api/keystone/:addr`:**
219
+ We use `PUT /api/keystone/evolve/:addr` (not `PUT /api/keystone/:addr`) because:
220
+ - Evolution has distinct server-side semantics: chain continuity check + CAS
221
+ - Avoids router ambiguity with the `GET /api/keystone/:domainAddr` route
222
+ - The `:domainAddr` being BEFORE the route specific term makes native Node routing more complicated
223
+ - Explicit `/evolve` makes intent clear in logs, proxies, and future middleware
224
+
225
+ ---
226
+
227
+ ## Server Entry Point Sketch
228
+
229
+ ```typescript
230
+ // src/server/server.mts
231
+ import * as http from 'http';
232
+ import { fileURLToPath } from 'url';
233
+ import { dirname, join } from 'path';
234
+ import { readFileSync, statSync } from 'fs';
235
+
236
+ const PORT = process.env.PORT ?? 3000;
237
+ const CLIENT_DIST = join(dirname(fileURLToPath(import.meta.url)), '../client');
238
+
239
+ const server = http.createServer((req, res) => {
240
+ try {
241
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
242
+
243
+ // Basic CORS
244
+ res.setHeader('Access-Control-Allow-Origin', '*');
245
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS');
246
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
247
+
248
+ if (req.method === 'OPTIONS') {
249
+ res.writeHead(204);
250
+ res.end();
251
+ return;
252
+ }
253
+
254
+ // API routes
255
+ if (url.pathname.startsWith('/api/ibgib/')) {
256
+ // ... handle ibgib routes natively
257
+ return;
258
+ }
259
+ if (url.pathname.startsWith('/api/keystone/')) {
260
+ // ... handle keystone routes natively
261
+ return;
262
+ }
263
+
264
+ // SPA fallback — serve static files
265
+ let filePath = join(CLIENT_DIST, url.pathname === '/' ? 'index.html' : url.pathname);
266
+ try {
267
+ if (!statSync(filePath).isFile()) filePath = join(CLIENT_DIST, 'index.html');
268
+ } catch {
269
+ filePath = join(CLIENT_DIST, 'index.html');
270
+ }
271
+
272
+ const content = readFileSync(filePath);
273
+ res.writeHead(200);
274
+ res.end(content);
275
+
276
+ } catch (err) {
277
+ res.writeHead(500);
278
+ res.end('Internal Server Error');
279
+ }
280
+ });
281
+
282
+ server.listen(PORT, () => console.log(`space-gib listening on :${PORT}`));
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Space Singleton Sketch
288
+
289
+ ```typescript
290
+ // src/server/space.mts
291
+ import { NodeFilesystemSpace_V1 } from '@ibgib/core-gib/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-v1.mjs';
292
+
293
+ const DATA_DIR = process.env.DATA_DIR ?? '/data/ibgib-space';
294
+
295
+ let _space: NodeFilesystemSpace_V1 | null = null;
296
+
297
+ export async function getSpace(): Promise<NodeFilesystemSpace_V1> {
298
+ if (!_space) {
299
+ _space = new NodeFilesystemSpace_V1({ baseDir: DATA_DIR });
300
+ await _space.initialized;
301
+ }
302
+ return _space;
303
+ }
304
+ ```
305
+
306
+ ---
307
+
308
+ ## IbGib Routes Sketch
309
+
310
+ ```typescript
311
+ // src/server/routes/ibgib.routes.mts
312
+ // ... implemented using native node http Request/Response parsing ...
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Keystone Routes Sketch
318
+
319
+ ```typescript
320
+ // src/server/routes/keystone.routes.mts
321
+ // ... implemented using native node http Request/Response parsing ...
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Dockerfile
327
+
328
+ ```dockerfile
329
+ FROM node:22-alpine
330
+ WORKDIR /app
331
+
332
+ # Server runtime
333
+ COPY dist/server ./server
334
+
335
+ # Client SPA (served as static files)
336
+ COPY dist/client ./client
337
+
338
+ # Only production deps
339
+ COPY package.json package-lock.json ./
340
+ RUN npm ci --omit=dev
341
+
342
+ ENV PORT=3000
343
+ ENV DATA_DIR=/data/ibgib-space
344
+
345
+ EXPOSE 3000
346
+ CMD ["node", "server/server.mjs"]
347
+ ```
348
+
349
+ ---
350
+
351
+ ## docker-compose Additions
352
+
353
+ **`docker-compose.yml`** (base / local):
354
+ ```yaml
355
+ space-gib:
356
+ build: ./apps/space-gib
357
+ volumes:
358
+ - ibgib_space_data:/data/ibgib-space
359
+ labels:
360
+ - "traefik.enable=true"
361
+ - "traefik.http.routers.space-gib.rule=Host(`space.ibgib.localhost`)"
362
+ - "traefik.http.routers.space-gib.entrypoints=websecure"
363
+ - "traefik.http.routers.space-gib.tls=true"
364
+ - "traefik.http.services.space-gib.loadbalancer.server.port=3000"
365
+
366
+ volumes:
367
+ ibgib_space_data:
368
+ ```
369
+
370
+ **`docker-compose.prod.yml`**:
371
+ ```yaml
372
+ space-gib:
373
+ labels:
374
+ - "traefik.http.routers.space-gib.rule=${SPACE_GIB_HOST_RULE:-Host(`ibgib.space`)}"
375
+ - "traefik.http.routers.space-gib.tls.certresolver=myresolver"
376
+ restart: always
377
+ ```
378
+
379
+ ---
380
+
381
+ ## package.json Shape
382
+
383
+ ```json
384
+ {
385
+ "name": "@ibgib/space-gib",
386
+ "private": true,
387
+ "version": "0.1.0",
388
+ "type": "module",
389
+ "description": "ibgib storage and provider service — SaaS at ibgib.space",
390
+ "scripts": {
391
+ "build:client": "node ../../build/dist/concrete-build/build-space-gib.mjs",
392
+ "build:server": "tsc -p tsconfig.server.json",
393
+ "build": "npm run build:client && npm run build:server",
394
+ "start": "node dist/server/server.mjs",
395
+ "dev:server": "node --watch dist/server/server.mjs"
396
+ },
397
+ "dependencies": {
398
+ "@ibgib/core-gib": "*",
399
+ "@ibgib/web-gib": "*"
400
+ },
401
+ "devDependencies": {
402
+ },
403
+ "engines": { "node": ">=22.0.0" }
404
+ }
405
+ ```
406
+
407
+ ---
408
+
409
+ ## tsconfig.server.json Shape
410
+
411
+ ```json
412
+ {
413
+ "compilerOptions": {
414
+ "target": "ES2022",
415
+ "module": "NodeNext",
416
+ "moduleResolution": "NodeNext",
417
+ "outDir": "./dist/server",
418
+ "rootDir": "./src/server",
419
+ "strict": true,
420
+ "esModuleInterop": true,
421
+ "skipLibCheck": true
422
+ },
423
+ "include": ["src/server/**/*.mts"],
424
+ "exclude": ["node_modules", "dist"]
425
+ }
426
+ ```
427
+
428
+ ---
429
+
430
+ ## Implementation Phases
431
+
432
+ ### Phase 1: Scaffold Client SPA
433
+ - Use `ibgib-create-app` skill to generate the 4-panel SPA in `src/client/`
434
+ - Tokens: `APP_NAME=space-gib`, `APP_CLASSNAME_PREFIX=SpaceGib`
435
+ - Initial component: `space-main` — displays a placeholder four-panel layout
436
+ - No real data yet; just confirms the SPA builds and the Docker image boots
437
+
438
+ ### Phase 2: Server Skeleton + Storage
439
+ - Create `src/server/server.mts`, `space.mts`, route files
440
+ - Wire up `NodeFilesystemSpace_V1` with Docker volume
441
+ - Implement and manually test:
442
+ - `POST /api/ibgib` (store any ibgib)
443
+ - `GET /api/ibgib/:addr` (retrieve by full addr)
444
+ - `GET /api/ibgib/graph/:addr` (recursive graph walk)
445
+ - Write `tsconfig.server.json` and `Dockerfile`
446
+ - Add to `docker-compose.yml`
447
+
448
+ ### Phase 3: Keystone Operations
449
+ - Implement `POST /api/keystone` (genesis)
450
+ - Implement `PUT /api/keystone/evolve/:addr` with CAS + chain continuity check
451
+ - Uses `KeystoneService_V1.validate()` from `@ibgib/core-gib`
452
+ - Implement `getKeystoneTip()` via space metastone lookup
453
+ - Implement `GET /api/keystone/:addr` (full chain retrieval)
454
+
455
+ ### Phase 4: Manifest + Scoped Writes
456
+ - Server-side manifest ibgib creation (given a list of content addrs → create manifest ibgib)
457
+ - Enforce: any content write must be accompanied by a valid keystone evolution
458
+ whose `claim.target` = the manifest's addr
459
+ - Validate soft-link parent reference when creating child keystones
460
+ - New route: `POST /api/manifest` → creates manifest ibgib from dependency list
461
+
462
+ ### Phase 5: Client Wires Up
463
+ - `space-main` component navigates keystone hierarchy
464
+ - Create keystone UI (calls `POST /api/keystone`)
465
+ - Evolve keystone UI (client computes evolution, calls `PUT /api/keystone/evolve/:addr`)
466
+ - Browse/view ibgib content in space
467
+
468
+ ### Phase 6: Comment Ibgib Use Case
469
+ - Client creates comment ibgib using existing ibgib transform machinery
470
+ - Server creates manifest from resulting dependency graph
471
+ - Full end-to-end: create comment → manifest → evolve context keystone → store content
472
+
473
+ ---
474
+
475
+ ## Key Known Constraints
476
+
477
+ - **Long path mitigation**: `NodeFilesystemSpace_V1` has `mitigateLongPaths=true` by default.
478
+ Ibgib addresses can exceed 255 chars; the space hashes long paths into a `long/` subdir.
479
+ This is a known pain point — be aware when debugging missing ibgibs.
480
+ - **No auth on reads**: by design; all ibgibs are public. Write auth via keystone proofs only.
481
+ - **CAS on keystone evolve**: clients must retry on 409. The retry loop is the client's
482
+ responsibility. No server-side locking.
483
+ - **Single space instance**: the server is a singleton wrapper; one `NodeFilesystemSpace_V1`
484
+ per process. Horizontal scaling requires a shared storage backend (Postgres — future).
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Space-Gib
2
+
3
+ Space-Gib is the multitenant synchronization peer and hosting environment for the IbGib ecosystem. It features a Node.js Express/serve-gib backend and a dynamic SPA client frontend.
4
+
5
+ ## Local Development Workflow
6
+
7
+ The local development environment uses a dual-target `esbuild` watch process and Docker for hosting the Node.js server behind a Traefik reverse proxy.
8
+
9
+ To run the full development loop, open three separate terminal windows:
10
+
11
+ ### Terminal 1: Watch (Client & Server)
12
+ Run the watch process to continuously type-check and bundle both client and server source files.
13
+ ```bash
14
+ npm run watch:space-gib
15
+ ```
16
+
17
+ ### Terminal 2: Run (Docker)
18
+ Start the Docker environment (Traefik + Node container).
19
+ ```bash
20
+ npm run docker:space-gib
21
+ ```
22
+ Once started, the application is available at **https://space-gib.localhost**
23
+
24
+ ### Terminal 3: Restart (As needed)
25
+ Because the Node.js process runs inside Docker, hot-reloading the server code requires restarting the container.
26
+ * Client changes: Auto-reload when you refresh the browser.
27
+ * Server changes: Run this command to load your newly compiled server code.
28
+ ```bash
29
+ npm run restart:space-gib
30
+ ```
31
+
32
+ ## Directory Structure
33
+
34
+ * [`src/client/`](src/client/): The browser SPA frontend. [See Client README](src/client/README.md).
35
+ * [`src/server/`](src/server/): The Node.js server. [See Server README](src/server/README.md).
36
+ * [`src/common/`](src/common/): Shared logic, types, and constants used by both client and server.
37
+ > [!IMPORTANT]
38
+ > **STRICT RULE**: `src/common` MUST NEVER import from `src/client` or `src/server`. This ensures the shared layer remains portable and prevents circular dependencies.
39
+ * [`dist/`](dist/): The output directory where `esbuild` writes the bundled client (`dist/client/`) and server (`dist/server/`) code.
40
+
41
+ ## Debugging the Server
42
+
43
+ You can attach the VS Code debugger directly to the Node.js process running inside Docker.
44
+ 1. The server runs with the `--inspect=0.0.0.0:9229` flag.
45
+ 2. `docker-compose.yml` maps port `9229` to your host machine.
46
+ 3. Use the VS Code launch configuration `Docker: Attach to Space-Gib` to attach breakpoints.