@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.
- package/CHANGELOG.md +31 -0
- package/Dockerfile +14 -0
- package/IMPLEMENTATION.md +484 -0
- package/README.md +46 -0
- package/dist/client/bootstrap.mjs +58 -0
- package/dist/client/bootstrap.mjs.map +7 -0
- package/dist/client/chunk-CT47Z5WU.mjs +21 -0
- package/dist/client/chunk-CT47Z5WU.mjs.map +7 -0
- package/dist/client/chunk-RHEDTRKF.mjs +235 -0
- package/dist/client/chunk-RHEDTRKF.mjs.map +7 -0
- package/dist/client/index.html +147 -0
- package/dist/client/index.mjs +2 -0
- package/dist/client/index.mjs.map +7 -0
- package/dist/client/script.mjs +2 -0
- package/dist/client/script.mjs.map +7 -0
- package/dist/client/style.css +605 -0
- package/dist/respec-gib.node.mjs +5 -0
- package/dist/server/server.mjs +20157 -0
- package/dist/server/server.mjs.map +7 -0
- package/generate-version-file.js +35 -0
- package/package.json +27 -0
- package/src/client/AUTO-GENERATED-version.mts +11 -0
- package/src/client/README.md +19 -0
- package/src/client/api/function-infos.web.mts +38 -0
- package/src/client/api/space-gib-api-bridge.mts +85 -0
- package/src/client/bootstrap.mts +49 -0
- package/src/client/components/keystone-creator/keystone-creator.css +139 -0
- package/src/client/components/keystone-creator/keystone-creator.html +26 -0
- package/src/client/components/keystone-creator/keystone-creator.mts +229 -0
- package/src/client/constants.mts +76 -0
- package/src/client/custom.d.ts +11 -0
- package/src/client/dev-tools.mts +540 -0
- package/src/client/helpers.web.mts +178 -0
- package/src/client/index.html +147 -0
- package/src/client/index.mts +59 -0
- package/src/client/script.mts +13 -0
- package/src/client/style.css +605 -0
- package/src/client/types.mts +85 -0
- package/src/client/ui/shell/space-gib-shell-constants.mts +24 -0
- package/src/client/ui/shell/space-gib-shell-service.mts +233 -0
- package/src/client/ui/shell/space-gib-shell-types.mts +5 -0
- package/src/client/witness/app/space-gib/space-gib-app-v1.mts +160 -0
- package/src/client/witness/app/space-gib/space-gib-constants.mts +38 -0
- package/src/client/witness/app/space-gib/space-gib-helper.mts +72 -0
- package/src/client/witness/app/space-gib/space-gib-types.mts +47 -0
- package/src/common/keystone-policies.mts +159 -0
- package/src/respec-gib.node.mts +6 -0
- package/src/server/README.md +18 -0
- package/src/server/bootstrap-helper.mts +141 -0
- package/src/server/bootstrap-helper.respec.mts +100 -0
- package/src/server/metaspace-nodeindexedspace/metaspace-nodeindexedspace.mts +85 -0
- package/src/server/path-constants.mts +89 -0
- package/src/server/path-helper.mts +101 -0
- package/src/server/path-helper.respec.mts +94 -0
- package/src/server/serve-gib/CHANGELOG.md +29 -0
- package/src/server/serve-gib/README.md +34 -0
- package/src/server/serve-gib/constants.mts +1 -0
- package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +104 -0
- package/src/server/serve-gib/handlers/api/health.handler.mts +23 -0
- package/src/server/serve-gib/handlers/api/health.respec.mts +51 -0
- package/src/server/serve-gib/handlers/api/ibgib/ibgib-handler-types.mts +49 -0
- package/src/server/serve-gib/handlers/api/ibgib/ibgib.handler.mts +176 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +261 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +146 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.handler.mts +198 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +107 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-handler-types.mts +29 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +70 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-post.respec.mts +130 -0
- package/src/server/serve-gib/handlers/error-handler.mts +36 -0
- package/src/server/serve-gib/handlers/handler-base.mts +383 -0
- package/src/server/serve-gib/handlers/static-handler.mts +82 -0
- package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +498 -0
- package/src/server/serve-gib/handlers/ws/ws-helper.mts +111 -0
- package/src/server/serve-gib/handlers/ws/ws-types.mts +53 -0
- package/src/server/serve-gib/serve-gib-helpers.mts +32 -0
- package/src/server/serve-gib/serve-gib-v1.mts +172 -0
- package/src/server/serve-gib/serve-gib.respec.mts +90 -0
- package/src/server/serve-gib/types.mts +102 -0
- package/src/server/server-constants.mts +2 -0
- package/src/server/server.mts +96 -0
- package/tsconfig.json +29 -0
- package/tsconfig.server.json +29 -0
- 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.
|