Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ Remote: `origin` → https://github.com/Freshair129/Genesis-Knowledge-System
(private). Local path in cross-repo references is `D:\gks`.

**Call direction (do not invert):** `Zuri / GoVibe -> MSP -> GKS`. GKS never
calls outward to GenesisBlockDB, GoVibe, or MSP — MSP (`D:\msp`) is GKS's
sole caller. GenesisBlockDB is a separate graph/vector engine, not GKS's
calls outward to GenesisBlockDB, GoVibe, or MSP. MSP (`D:\msp`) is the
built-in governed caller. Another system may call GKS only as a provisioned
governed caller (`docs/ADR-GKS-GOVERNED-CALLERS.md`): its own portfolios, its
own provenance namespace, HTTP only, and no pipeline tools. GenesisBlockDB is a separate graph/vector engine, not GKS's
assumed persistence backend.

## Toolchain
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
version: "0.5.0b"
version: "0.6.0b"
created_at: "2026-08-12T10:29:29+07:00,ATHER,working-tree"
last_update: "2026-09-24T10:06:32+07:00,RWANG"
last_update: "2026-09-27T22:00:00+07:00,Claude"
status: "beta"
superseded_by: null
attributes:
Expand All @@ -23,7 +23,11 @@ runtime path is:
Zuri / GoVibe -> MSP -> GKS
```

Zuri and GoVibe call MSP. MSP is the sole caller of GKS in this path.
Zuri and GoVibe call MSP, and MSP calls GKS. MSP is the built-in **governed
caller**. A system that already has its own auth and memory can call GKS
directly, without MSP, if it is provisioned as a governed caller for its own
portfolios under its own provenance namespace
(`docs/ADR-GKS-GOVERNED-CALLERS.md`). In every case GKS never calls outward.

## Knowledge Graph Service boundary

Expand Down Expand Up @@ -168,6 +172,7 @@ verification, not production deployment or Zuri cutover evidence.

| Version | Date | Status | Summary | Commit Hash | Agent |
|---|---|---|---|---|---|
| 0.6.0b | 2026-09-27 | beta | Governed callers (ADR-GKS-GOVERNED-CALLERS): MSP is the built-in governed caller, and other systems can be provisioned for their own portfolios. | working-tree | Claude |
| 0.5.0b | 2026-09-24 | beta | Implements optional hash-backed, scoped direct read-only clients over private HTTP; no default grants or production activation. | working-tree | RWANG |
| 0.4.0b | 2026-09-24 | beta | Records the approved direct read-only client profile while keeping MSP as the governed write/pipeline/receipt path; direct auth is not yet implemented. | working-tree | RWANG |
| 0.3.0b | 2026-09-24 | beta | Defines GKS as the Knowledge Graph Service while preserving MSP-only governed ingress and separating GKS from a physical graph database or broader Semantic Layer. | working-tree | RWANG |
Expand Down
18 changes: 9 additions & 9 deletions apps/gks-server/src/http-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ function sameSecret(left, right) {
function withHttpAuthentication(request, httpRequest, runtime) {
const toolName = request.params?.name;
if (request.method !== "tools/call" || LIVENESS_TOOLS.has(toolName)) {
return { request, directClientGrant: null };
return { request, clientGrant: null };
}
const supplied = bearerCredential(httpRequest);
if (runtime.mspRelayCredential && sameSecret(supplied, runtime.mspRelayCredential)) {
if (!request.params || typeof request.params !== "object") return { request, directClientGrant: null };
if (!request.params || typeof request.params !== "object") return { request, clientGrant: null };
const metadata = request.params._meta && typeof request.params._meta === "object" ? request.params._meta : {};
const auth = metadata.gksMspAuth && typeof metadata.gksMspAuth === "object" ? metadata.gksMspAuth : {};
return {
Expand All @@ -115,12 +115,12 @@ function withHttpAuthentication(request, httpRequest, runtime) {
},
},
},
directClientGrant: null,
clientGrant: null,
};
}
const directClientGrant = findGksClientGrant(supplied, runtime.directClientGrants);
if (!directClientGrant) throw new HttpTransportError("gks_scope_denied", "Bearer authentication is invalid.", 401);
return { request, directClientGrant };
const clientGrant = findGksClientGrant(supplied, runtime.clientGrants);
if (!clientGrant) throw new HttpTransportError("gks_scope_denied", "Bearer authentication is invalid.", 401);
return { request, clientGrant };
}

async function handleMcpRequest(request, response, runtime, state) {
Expand Down Expand Up @@ -156,18 +156,18 @@ async function handleMcpRequest(request, response, runtime, state) {
return;
}
let authenticated;
let directClientGrant;
let clientGrant;
try {
const auth = withHttpAuthentication(parsed, request, runtime);
authenticated = auth.request;
directClientGrant = auth.directClientGrant;
clientGrant = auth.clientGrant;
} catch (error) {
writeJson(response, error.statusCode ?? 401, createJsonRpcToolErrorResponse(parsed.id, error));
return;
}
state.inFlight += 1;
try {
const result = await dispatchJsonRpcRequest(authenticated, { runtime, directClientGrant });
const result = await dispatchJsonRpcRequest(authenticated, { runtime, clientGrant });
if (result) writeJson(response, 200, result);
else writeNoContent(response);
} finally {
Expand Down
50 changes: 32 additions & 18 deletions apps/gks-server/src/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import path from "node:path";
import { TextDecoder } from "node:util";
import { GKS_TOOL_DEFINITIONS, authorizeGksClientRequest, authorizeLegacyMspRequest, automergeFloor, parseGksClientGrants, requiresLegacyMspAuth } from "@freshair129/gks-contracts";
import { GKS_MSP_CALLER, GKS_TOOL_DEFINITIONS, GksScopeDeniedError, authorizeGksClientRequest, authorizeGovernedCallerRequest, authorizeLegacyMspRequest, automergeFloor, governedPortfolios, parseGksClientGrants, requiresLegacyMspAuth } from "@freshair129/gks-contracts";
import { createGksService } from "@freshair129/gks-core";
import { openSqlitePersistence } from "@freshair129/gks-persistence";

Expand Down Expand Up @@ -154,7 +154,9 @@ export function createRuntimeFromEnvironment(env = process.env) {
const mspRelayCredential = env.GKS_MSP_RELAY_CREDENTIAL?.trim() || undefined;
const clientGrantsPath = env.GKS_CLIENT_GRANTS_PATH?.trim();
if (clientGrantsPath && !path.isAbsolute(clientGrantsPath)) throw new Error("GKS_CLIENT_GRANTS_PATH must be an absolute path.");
let directClientGrants = [];
// Read grants and governed-caller grants (ADR-GKS-CLIENT-ACCESS,
// ADR-GKS-GOVERNED-CALLERS) share one file and one credential format.
let clientGrants = [];
if (clientGrantsPath) {
let contents;
try {
Expand All @@ -163,21 +165,21 @@ export function createRuntimeFromEnvironment(env = process.env) {
throw new Error("GKS_CLIENT_GRANTS_PATH could not be read.");
}
try {
directClientGrants = parseGksClientGrants(contents);
clientGrants = parseGksClientGrants(contents);
} catch {
throw new Error("GKS_CLIENT_GRANTS_PATH contains invalid grants.");
}
}
if (directClientGrants.length > 0 && !requireMspAuth) {
throw new Error("GKS_MSP_AUTH_REQUIRED=1 is required when direct client grants are configured.");
if (clientGrants.length > 0 && !requireMspAuth) {
throw new Error("GKS_MSP_AUTH_REQUIRED=1 is required when client grants are configured.");
}
if (requireMspAuth && !mspRelayCredential && directClientGrants.length === 0) {
if (requireMspAuth && !mspRelayCredential && clientGrants.length === 0) {
throw new Error("GKS_MSP_RELAY_CREDENTIAL or a non-empty GKS_CLIENT_GRANTS_PATH is required when GKS_MSP_AUTH_REQUIRED=1.");
}
if (mspRelayCredential) {
const mspCredentialHash = createHash("sha256").update(mspRelayCredential, "utf8").digest("hex");
if (directClientGrants.some((grant) => grant.credentialSha256 === mspCredentialHash)) {
throw new Error("A credential cannot be registered for both MSP and direct-client access.");
if (clientGrants.some((grant) => grant.credentialSha256 === mspCredentialHash)) {
throw new Error("A credential cannot be registered for both MSP and a client grant.");
}
}
const persistence = openSqlitePersistence({ dbPath });
Expand All @@ -186,7 +188,9 @@ export function createRuntimeFromEnvironment(env = process.env) {
defaultPortfolioId: env.GKS_DEFAULT_PORTFOLIO_ID?.trim() || undefined,
requireMspAuth,
mspRelayCredential,
directClientGrants,
clientGrants,
// D7: portfolios a governed caller owns are denied to MSP.
governedPortfolios: governedPortfolios(clientGrants),
// Decision 2: the auto-merge floor is deployment-set (GKS_AUTOMERGE_FLOOR)
// and resolved HERE, at startup, from the same env the rest of the
// runtime reads — an invalid value fails closed before the first promote.
Expand All @@ -197,16 +201,18 @@ export function createRuntimeFromEnvironment(env = process.env) {
};
}

// `caller` is the authenticated governed caller; the three governed writes use
// it for their provenance namespace and attribution.
export function toolHandler(service, name) {
const handlers = {
gks_health: (args) => service.health(args),
gks_knowledge_promote: (args) => service.promoteCandidate(args),
gks_knowledge_promote: (args, caller) => service.promoteCandidate(args, caller),
gks_search: (args) => service.search(args),
gks_entity_get: (args) => service.getEntity(args),
gks_relations_get: (args) => service.getRelations(args),
gks_artifact_link: (args) => service.linkArtifact(args),
gks_artifact_link: (args, caller) => service.linkArtifact(args, caller),
gks_review_list: (args) => service.listUnresolvedMentions(args),
gks_review_apply: (args) => service.applyHumanResolution(args),
gks_review_apply: (args, caller) => service.applyHumanResolution(args, caller),
gks_stage_evidence_export: (args) => service.exportStageEvidence(args),
gks_pipeline_submit: (args) => service.pipelineSubmit(args),
gks_pipeline_claim: (args) => service.pipelineClaim(args),
Expand Down Expand Up @@ -237,7 +243,7 @@ export function createJsonRpcToolErrorResponse(id, error) {
};
}

export async function dispatchJsonRpcRequest(request, { runtime, directClientGrant } = {}) {
export async function dispatchJsonRpcRequest(request, { runtime, clientGrant } = {}) {
if (!runtime) throw new TypeError("runtime is required.");
if (request.method === "notifications/initialized" || request.id === undefined) return null;
if (request.method === "initialize") {
Expand All @@ -255,17 +261,25 @@ export async function dispatchJsonRpcRequest(request, { runtime, directClientGra
return createJsonRpcToolErrorResponse(request.id, { code: "gks_invalid_request", message: "Unknown GKS tool." });
}
try {
if (directClientGrant) {
authorizeGksClientRequest(directClientGrant, { toolName, args: request.params?.arguments ?? {} });
const args = request.params?.arguments ?? {};
let caller = GKS_MSP_CALLER;
if (clientGrant?.profile === "governed") {
// ADR-GKS-GOVERNED-CALLERS: tools, portfolio boundary and provenance
// namespace all come from the server-side grant.
caller = authorizeGovernedCallerRequest(clientGrant, request.params?._meta, { toolName, args, defaultPortfolioId: runtime.defaultPortfolioId });
} else if (clientGrant) {
authorizeGksClientRequest(clientGrant, { toolName, args });
} else if (runtime.requireMspAuth && requiresLegacyMspAuth(toolName)) {
authorizeLegacyMspRequest(request.params?._meta, {
const scope = authorizeLegacyMspRequest(request.params?._meta, {
toolName,
args: request.params?.arguments ?? {},
args,
defaultPortfolioId: runtime.defaultPortfolioId,
relayCredential: runtime.mspRelayCredential,
});
// D7: a portfolio owned by a governed caller is not MSP's.
if (runtime.governedPortfolios?.has(scope.portfolioId)) throw new GksScopeDeniedError("This portfolio is governed by another caller.");
}
const structuredContent = await handler(request.params?.arguments ?? {});
const structuredContent = await handler(args, caller);
return { jsonrpc: "2.0", id: request.id, result: { content: [{ type: "text", text: JSON.stringify(structuredContent) }], structuredContent } };
} catch (error) {
return createJsonRpcToolErrorResponse(request.id, error);
Expand Down
21 changes: 15 additions & 6 deletions docs/ADR-GKS-BOUNDARY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
version: "0.6.1b"
version: "0.7.0b"
created_at: "2026-08-12T10:05:34+07:00,ATHER,working-tree"
last_update: "2026-09-27T09:30:00+07:00,Claude"
last_update: "2026-09-27T22:00:00+07:00,Claude"
status: "beta"
approval_owner: "Boss (บอส)"
approval_recorded_at: "2026-08-12T10:16:19+07:00"
Expand Down Expand Up @@ -57,7 +57,12 @@ physical graph/vector database engine. GenesisBlockDB remains a separate
product, and a physical projection or worker receipt does not transfer canonical
authority away from GKS.

For Phase 1, the governed runtime ingress remains MSP-only. The existing
Governed runtime ingress is limited to **governed callers** ([ADR-GKS-GOVERNED-CALLERS](ADR-GKS-GOVERNED-CALLERS.md), accepted
2026-09-27). MSP is the built-in governed caller. Another system that owns its
own auth and memory can be provisioned as a governed caller for the portfolios
it owns, with its own provenance namespace, and it does not need MSP. The text
below was written when ingress was MSP-only, and it still describes the MSP
path. The existing
registered GKS tools and scope/authentication contracts are preserved; this
decision does not grant direct credentials or access to Zuri, GoVibe, or other
consumers. Personal memory, session-context assembly, MSP policy/approval, and
Expand Down Expand Up @@ -107,8 +112,10 @@ its right. It does not mean source code must be deleted from the caller's repo.

- The configured GKS root owns GKS service runtime, public contracts, canonicalization logic,
backend ports, and service-level tests.
- The configured MSP root keeps its GKS provider/client boundary and remains the sole
governed caller for promotion, review, pipeline and receipt operations. Any
- The configured MSP root keeps its GKS provider/client boundary. MSP is the built-in
governed caller, and the only caller of pipeline and receipt operations.
A provisioned governed caller ([ADR-GKS-GOVERNED-CALLERS](ADR-GKS-GOVERNED-CALLERS.md)) may call promotion, artifact linking
and review, but only for its own portfolios. Any
direct read-only caller must satisfy the distinct grant profile in
`ADR-GKS-CLIENT-ACCESS.md`.
- The configured GoVibe root keeps MSP/GKS names, contracts, disabled direct-GKS shim,
Expand Down Expand Up @@ -156,7 +163,8 @@ API-010 must remain wire compatible during extraction.

## Acceptance criteria

- MSP is the only governed caller of GKS in the Zuri/GoVibe path.
- MSP is the governed caller of GKS in the Zuri/GoVibe path. Other governed callers
are provisioned per [ADR-GKS-GOVERNED-CALLERS](ADR-GKS-GOVERNED-CALLERS.md), and each one owns its portfolios exclusively.
- Direct clients are limited to explicitly granted scoped reads and have no
promotion, review, pipeline or MSP receipt authority.
- GKS owns canonical identity and relations without owning MSP memory/context.
Expand Down Expand Up @@ -206,6 +214,7 @@ the repository or adding another exemption is a boundary change recorded here.

| Version | Date | Status | Summary | Commit Hash | Agent |
|---|---|---|---|---|---|
| 0.7.0b | 2026-09-27 | beta | Amended by ADR-GKS-GOVERNED-CALLERS (accepted 2026-09-27). MSP is no longer the only governed caller; it is the built-in one, and a provisioned governed caller owns its portfolios and provenance namespace. Pipeline and receipt operations stay MSP's. | working-tree | Claude |
| 0.6.1b | 2026-09-27 | beta | Recorded `apps/wiki-desktop` as the one named non-service exemption from the outward-reference boundary check, which is now case-insensitive. | working-tree | Claude |
| 0.6.0b | 2026-09-24 | beta | Records the implemented, opt-in direct read-only HTTP profile; MSP remains the sole governed caller and production access stays disabled by default. | working-tree | RWANG |
| 0.5.0b | 2026-09-24 | beta | Records the approved direct-client read-only exception through a server-side identity/scope grant; MSP remains sole governed caller for writes, pipeline and receipts. | working-tree | RWANG |
Expand Down
15 changes: 10 additions & 5 deletions docs/ADR-GKS-CLIENT-ACCESS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
version: "0.2.0b"
version: "0.3.0b"
created_at: "2026-09-24T07:21:56+07:00,RWANG,working-tree"
last_update: "2026-09-24T10:06:32+07:00,RWANG"
last_update: "2026-09-27T22:00:00+07:00,Claude"
status: "beta"
approval_owner: "Boss (บอส)"
approval_recorded_at: "2026-09-24T07:21:56+07:00"
Expand Down Expand Up @@ -138,13 +138,18 @@ System client -> trusted GKS authentication/grant adapter -> GKS read tools

This decision does not authorize direct writes, general ingestion, promotion,
review, MSP feature replacement, public/anonymous access, a browser-held service
secret, production deployment or canary traffic. Direct write authority needs
a separate decision covering provenance, approval, revocation, idempotency and
non-MSP receipt semantics.
secret, production deployment or canary traffic.

Write authority for systems other than MSP is decided by ADR-GKS-GOVERNED-CALLERS
(accepted 2026-09-27). That ADR adds a `governed` profile to the same grants file
(`gks-client-grants/v2`, where v1 documents keep parsing as read grants). It
covers provenance, revocation, idempotency and receipt semantics. The read
profile defined here is unchanged.

## CHANGELOG

| Version | Date | Status | Summary | Commit Hash | Agent |
|---|---|---|---|---|---|
| 0.3.0b | 2026-09-27 | beta | Out-of-scope note: direct writes are now decided by ADR-GKS-GOVERNED-CALLERS through a `governed` profile in grants v2. The read profile and v1 documents are unchanged. | working-tree | Claude |
| 0.2.0b | 2026-09-24 | beta | Implements per-client 256-bit bearer keys, hash-only server grants, private HTTP read-only authorization and restart-based revoke/rotation; OIDC/mTLS remain out of scope. | working-tree | RWANG |
| 0.1.0b | 2026-09-24 | beta | Owner-approved access profiles: preserve MSP-governed operations and define explicitly granted direct read-only clients; identity verifier and activation remain open. | working-tree | RWANG |
Loading
Loading