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
16 changes: 0 additions & 16 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,6 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
analyze-python:
name: Analyze Python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: python
build-mode: none # Python is interpreted; no build needed

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:python"
analyze-typescript:
name: Analyze TypeScript
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
cache: pnpm
cache-dependency-path: website/pnpm-lock.yaml

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/kndl-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "24"
cache: "npm"
cache-dependency-path: packages/kndl-memory/package-lock.json
- name: Install dependencies
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
[![CI](https://github.com/artdaw/KNDL/actions/workflows/kndl-workflow.yml/badge.svg)](https://github.com/artdaw/KNDL/actions/workflows/kndl-workflow.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-00e5a0.svg)](LICENSE)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.4-3178C6.svg)](packages/kndl-memory)
[![Docs](https://img.shields.io/badge/Docs-kndl.artdaw.com-00e5a0.svg)](https://kndl.artdaw.com)
[![Website](https://img.shields.io/badge/Website-kndl.artdaw.com-00e5a0?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEyIDJhMTAgMTAgMCAxIDAgMCAyMEExMCAxMCAwIDAgMCAxMiAyek04IDEyYTQgNCAwIDEgMSA4IDAgNCA0IDAgMCAxLTggMHoiLz48L3N2Zz4=)](https://kndl.artdaw.com)

### **[→ kndl.artdaw.com](https://kndl.artdaw.com)** — live docs, protocol reference, interactive examples, explorer

</div>

Expand Down
39 changes: 39 additions & 0 deletions website/public/schema/kndl-memory.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://kndl.artdaw.com/schema/fact.schema.json",
"title": "KNDL Fact",
"description": "One immutable assertion in the KNDL memory protocol. Every fact carries confidence, decay, provenance, and bitemporal validity. Facts are stored as individual JSON-LD files and never edited — updates are made via supersession.",
"type": "object",
"required": ["@id", "@type", "statement", "confidence", "source", "validFrom", "recordedAt"],
"additionalProperties": true,
"properties": {
"@context": { "type": ["string", "object"], "description": "JSON-LD context. Recommended: https://kndl.artdaw.com/context/v1.jsonld" },
"@id": { "type": "string", "minLength": 1, "description": "Globally unique fact identifier. Recommended pattern: fact:<subject>-<predicate>-<timestamp>-<hash8>" },
"@type": { "type": "string", "description": "Usually 'Fact'. Use 'Action' for intents/rules." },

"statement": { "type": "string", "minLength": 1, "description": "Plain-language one-sentence assertion" },
"subject": { "type": "string", "description": "Entity URI (for structured triple queries, e.g. customer:9281)" },
"predicate": { "type": "string", "description": "Property name (for structured triple queries, e.g. creditScore)" },
"object": { "description": "Value or object URI" },

"confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Epistemic certainty 0.0–1.0. Reserve 1.0 for axiomatic facts only." },
"decay": { "type": "string", "pattern": "^[0-9.]+/[0-9.]+(?:ns|us|ms|s|m|h|d|w|mo|y)$", "description": "Half-life decay spec: rate/window, e.g. '0.5/30d' halves confidence every 30 days." },

"source": { "type": "string", "description": "URI of asserting entity. Use human://<name> for user input, doi:<id> for publications." },
"validFrom": { "type": "string", "format": "date-time", "description": "ISO datetime — when the fact became true in the world" },
"validUntil": { "type": "string", "format": "date-time", "description": "ISO datetime — explicit expiry (omit for open-ended)" },
"observedAt": { "type": "string", "format": "date-time", "description": "ISO datetime — when an agent or sensor directly observed it (vs. heard about it)" },
"recordedAt": { "type": "string", "format": "date-time", "description": "ISO datetime — when it entered memory (auto-set by the store)" },

"supersedes": { "type": "string", "description": "@id of the older fact this assertion replaces. The old fact is hidden from active queries but preserved for as-of time-travel." },
"derivedFrom": { "type": "array", "items": { "type": "string" }, "description": "@ids of source facts this was inferred from" },
"inference": { "type": "string", "description": "@id of the rule that produced this derived fact" },
"negated": { "type": "boolean", "description": "true = this fact is known-false (positive negation under open-world assumption). Never substitute for 'unknown'." },

"classification": { "type": "string", "enum": ["PII", "PHI", "PCI", "CONFIDENTIAL", "INTERNAL", "PUBLIC"], "description": "Sensitivity label. PHI facts are filtered by default; require allowPhi:true and consent." },
"consent": { "type": "string", "description": "@id of the consent scope covering PHI access" },
"retention": { "type": "string", "description": "ISO duration or date for scheduled deletion, e.g. P7Y" },
"tenant": { "type": "string", "description": "Opaque multi-tenant isolation key" },
"tags": { "type": "array", "items": { "type": "string" }, "description": "Free-form labels for search and grouping" }
}
}
6 changes: 6 additions & 0 deletions website/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ const router = createBrowserRouter([
{ path: "spec", element: <Navigate to="/protocol" replace /> },
{ path: "spec/full", element: <Navigate to="/protocol" replace /> },
{ path: "workflow", element: <Navigate to="/skill" replace /> },
// Schema file aliases — redirect to the canonical static file so the
// browser fetches the raw JSON rather than showing a router error.
// (Static files in public/ take precedence once deployed; these
// redirects are a fallback for the GitHub Pages SPA 404 intercept.)
{ path: "schema/kndl-memory.schema.json", element: <Navigate to="/schema/fact.schema.json" replace /> },
{ path: "schema/kndl-context.jsonld", element: <Navigate to="/context/v1.jsonld" replace /> },
],
},
]);
Expand Down
86 changes: 85 additions & 1 deletion website/src/pages/ProtocolPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,90 @@
import { SEO, techArticleSchema } from "../components/SEO";
import styles from "./ProtocolPage.module.css";

// ── JSON syntax highlighter ───────────────────────────────────────────────────
// Single-pass tokenizer — no external library.

type Token = { type: string; value: string };

function tokenizeJson(src: string): Token[] {
const tokens: Token[] = [];
let i = 0;
while (i < src.length) {
// Whitespace / structure
if (/[\s,[\]{}]/.test(src[i])) {
let v = "";
while (i < src.length && /[\s,[\]{}]/.test(src[i])) v += src[i++];
tokens.push({ type: "punct", value: v });
continue;
}
// Colon
if (src[i] === ":") {
tokens.push({ type: "punct", value: src[i++] });
continue;
}
// String
if (src[i] === '"') {
let v = '"';
i++;
while (i < src.length && src[i] !== '"') {
if (src[i] === "\\") { v += src[i++]; }
v += src[i++];
}
v += '"';
i++;
// Determine if it's a key (next non-space char is ":")
let j = i;
while (j < src.length && src[j] === " ") j++;
const isKey = src[j] === ":";
const raw = v.slice(1, -1);
if (isKey) {
const type = raw.startsWith("@") ? "at-key" : "key";
tokens.push({ type, value: v });
} else {
// URL vs plain string value
const type = raw.startsWith("http") || raw.startsWith("fact:") || raw.startsWith("human://") || raw.startsWith("sensor://") ? "url" : "string";
tokens.push({ type, value: v });
}
continue;
}
// Number
if (/[-\d]/.test(src[i])) {
let v = "";
while (i < src.length && /[-\d.eE+]/.test(src[i])) v += src[i++];
tokens.push({ type: "number", value: v });
continue;
}
// true / false / null
if (src.startsWith("true", i)) { tokens.push({ type: "bool", value: "true" }); i += 4; continue; }
if (src.startsWith("false", i)) { tokens.push({ type: "bool", value: "false" }); i += 5; continue; }
if (src.startsWith("null", i)) { tokens.push({ type: "null", value: "null" }); i += 4; continue; }
tokens.push({ type: "punct", value: src[i++] });
}
return tokens;
}

const TOKEN_COLORS: Record<string, string> = {
"at-key": "var(--accent)",
"key": "var(--accent2)",
"string": "var(--accent4)",
"url": "#7dd3fc",
"number": "#f97316",
"bool": "var(--accent3)",
"null": "var(--text-dim)",
"punct": "var(--text-dim)",
};

function JsonHighlight({ src }: { src: string }) {
const tokens = tokenizeJson(src);
return (
<pre className={styles.pre}>
{tokens.map((t, i) => (
<span key={i} style={{ color: TOKEN_COLORS[t.type] }}>{t.value}</span>
))}
</pre>
);
}

const FACT_EXAMPLE = `{
"@id": "fact:customer-9281-creditscore-20260425T235249Z-b6c88774",
"@type": "Fact",
Expand Down Expand Up @@ -91,7 +175,7 @@ export default function ProtocolPage() {
</p>
<div className={styles.codeWrap}>
<div className={styles.codeLabel}>fact-customer-9281-creditscore.fact.json</div>
<pre className={styles.pre}>{FACT_EXAMPLE}</pre>
<JsonHighlight src={FACT_EXAMPLE} />
</div>
</section>

Expand Down
Loading