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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,40 @@ const result = await guarded.execute(privateInput);
// an exact full-ID readback. A rejection throws KnowledgeGuardedWriteRejectedError.
```

For callers that must keep returned titles and bodies off public result
surfaces, use `executePrivate(...)` and `readbackPrivate(...)`. They return an
opaque private-result descriptor whose JSON form contains only its kind, item
count, expiry, and result digest. `inspectKnowledgePrivateResult(...)`
materializes a metadata-only proof inside the process: full item ID, version,
title/content/source/tag/metadata digests, and replay receipt identity, never
the title or body.

Deduplicate reviewed sources by an exact title only through the package-owned
private title descriptor:

```ts
import {
createKnowledgePrivateTitleLookupDescriptor,
inspectKnowledgePrivateResult,
} from '@hasna/knowledge';

const lookup = createKnowledgePrivateTitleLookupDescriptor({
operation_id: 'doctrine-rollout-2026-08',
step_id: 'lookup-one',
binding,
title: privateReviewedTitle,
});
const proof = inspectKnowledgePrivateResult(await guarded.lookupTitle(lookup));
```

The producer sends the title only in the authenticated bounded request. The
server matches the exact authority, tenant, scope, parent, and title, selects at
most two rows to detect ambiguity, and returns zero or one digest-only proof.
Two matching titles fail closed with `private_title_lookup_ambiguous`; the
endpoint never falls back to a collection scan and never returns item bodies or
titles. `knowledge guarded capabilities --json` reports this private transport
support without reading configuration, opening a store, or making a request.

The deterministic key is:

```text
Expand Down
2 changes: 1 addition & 1 deletion bin/knowledge-mcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -14997,7 +14997,7 @@ import { existsSync as existsSync15, readFileSync as readFileSync14, writeFileSy
// package.json
var package_default = {
name: "@hasna/knowledge",
version: "0.2.99",
version: "0.2.100",
description: "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
type: "module",
exports: {
Expand Down
150 changes: 150 additions & 0 deletions bin/knowledge-serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@ function makeShortId(id) {
import { createHash as createHash2, randomUUID } from "crypto";
var KNOWLEDGE_GUARDED_WRITE_CONTRACT = "FCAME-1";
var KNOWLEDGE_PRIVATE_INPUT_SCHEMA = "hasna.knowledge.private-input.v1";
var KNOWLEDGE_PRIVATE_TITLE_LOOKUP_SCHEMA = "hasna.knowledge.private-title-lookup.v1";
var DEFAULT_KNOWLEDGE_GUARDED_LIMITS = Object.freeze({
submission: Object.freeze({
max_calls: 1,
Expand All @@ -753,6 +754,8 @@ var MAX_GUARDED_BYTES = 4 * 1024 * 1024;
var MAX_GUARDED_WALL_TIME_MS = 30000;
var MAX_DESCRIPTOR_LIFETIME_MS = 60 * 60 * 1000;
var PRIVATE_PAYLOADS = new WeakMap;
var PRIVATE_TITLE_LOOKUPS = new WeakMap;
var PRIVATE_RESULTS = new WeakMap;
function assertObjectKeys(value, field, allowed, required = allowed) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`${field} must be an object.`);
Expand Down Expand Up @@ -1188,6 +1191,18 @@ function assertKnowledgeGuardedPayload(verb, payload) {
throw new Error("update payload must change at least one field.");
}
}
function knowledgePrivateItemProof(item) {
return Object.freeze({
id: item.id,
version: Number(item.version ?? 1),
title_sha256: knowledgeGuardedContentSha256(item.title),
content_sha256: knowledgeGuardedContentSha256(item.content),
url_sha256: item.url === null || item.url === undefined ? null : knowledgeGuardedContentSha256(item.url),
tags_sha256: knowledgeGuardedDigest(item.tags ?? []),
metadata_sha256: knowledgeGuardedDigest(item.metadata ?? {}),
archived: item.archived === true
});
}
function evaluateKnowledgeGuardedManifestCompletion(steps) {
if (steps.length === 0 || steps.some((step) => step.state === "unverified_external_authority" || step.recovery_state === "unverified_external_authority")) {
return { terminal_complete: false, accepted_complete: false };
Expand Down Expand Up @@ -2564,6 +2579,37 @@ class GuardedWriteRepo {
limits
};
}
async lookupTitle(title, binding, limits) {
const rows = await this.client.many(`SELECT * FROM knowledge_items
WHERE title = $1
AND authority_classification = $2
AND authority_id = $3
AND tenant_id = $4
AND scope = $5
AND parent_id = $6
ORDER BY id
LIMIT 2`, [
title,
binding.authority.classification,
binding.authority.authority_id,
binding.tenant_id,
binding.scope,
binding.parent_id
]);
if (rows.length > 1)
throw new PrivateTitleLookupAmbiguousError;
const items = rows.map((row) => knowledgePrivateItemProof(rowToItem(row)));
return {
contract: KNOWLEDGE_GUARDED_WRITE_CONTRACT,
exact: true,
bounded: true,
item_count: items.length,
binding,
title_digest: knowledgeGuardedContentSha256(title),
items,
limits
};
}
async readback(fullId, binding, limits) {
const row = await this.client.get(`SELECT * FROM knowledge_items
WHERE id = $1
Expand Down Expand Up @@ -2593,6 +2639,13 @@ class GuardedWriteRepo {
};
}
}

class PrivateTitleLookupAmbiguousError extends Error {
constructor() {
super("more than one exact title exists in the frozen binding.");
this.name = "PrivateTitleLookupAmbiguousError";
}
}
function knowledgeOpenApi(version) {
const noteSchema = {
type: "object",
Expand Down Expand Up @@ -2950,6 +3003,17 @@ function knowledgeOpenApi(version) {
}
}
},
"/v1/guarded-writes/lookups/title": {
post: {
operationId: "lookupGuardedKnowledgeTitle",
summary: "Bounded exact-title lookup under one frozen FCAME-1 binding",
description: "Returns zero or one metadata-only item proof. More than one exact title is an ambiguity error; " + "item bodies and titles are never returned.",
responses: {
"200": { description: "Exact bounded metadata-only result containing zero or one item proof." },
"409": { description: "More than one exact title exists under the frozen binding." }
}
}
},
"/v1/guarded-adoptions": {
post: {
operationId: "executeGuardedKnowledgeAdoption",
Expand Down Expand Up @@ -3305,6 +3369,67 @@ function validateGuardedEnvelope(value, headerBounds, authority, idempotencyKey)
throw new HttpError(400, error instanceof Error ? error.message : "invalid guarded write envelope.");
}
}
function validatePrivateTitleLookupEnvelope(value, headerBounds, authority) {
try {
if (!value || typeof value !== "object") {
throw new Error("private title lookup envelope is required.");
}
const envelope = value;
assertExactRequestKeys(value, "private title lookup envelope", ["contract", "descriptor", "title", "limits"]);
if (envelope.contract !== KNOWLEDGE_GUARDED_WRITE_CONTRACT) {
throw new Error("unsupported guarded-write contract.");
}
const descriptor = envelope.descriptor;
if (!descriptor || descriptor.contract !== KNOWLEDGE_GUARDED_WRITE_CONTRACT || descriptor.schema !== KNOWLEDGE_PRIVATE_TITLE_LOOKUP_SCHEMA) {
throw new Error("invalid private title lookup descriptor schema.");
}
assertExactRequestKeys(descriptor, "private title lookup descriptor", [
"contract",
"schema",
"operation_id",
"step_id",
"title_digest",
"binding_digest",
"binding",
"expires_at"
]);
if (typeof descriptor.operation_id !== "string" || descriptor.operation_id.length === 0 || typeof descriptor.step_id !== "string" || descriptor.step_id.length === 0 || typeof envelope.title !== "string" || envelope.title.length === 0 || envelope.title.length > 2048) {
throw new Error("private title lookup operation, step, and bounded title are required.");
}
const descriptorExpiresAt = Date.parse(descriptor.expires_at);
const descriptorNow = Date.now();
if (!Number.isFinite(descriptorExpiresAt) || descriptorExpiresAt <= descriptorNow || descriptorExpiresAt > descriptorNow + 60 * 60 * 1000) {
throw new Error("private title lookup descriptor is expired or malformed.");
}
assertKnowledgeGuardedBinding(descriptor.binding);
assertConfiguredAuthority(descriptor.binding, authority);
assertKnowledgeGuardedBounds(envelope.limits, "private title lookup bounds");
if (canonicalKnowledgeGuardedJson(envelope.limits) !== canonicalKnowledgeGuardedJson(headerBounds)) {
throw new Error("private title lookup limits must exactly match the producer bound headers.");
}
const titleDigest = knowledgeGuardedContentSha256(envelope.title);
if (titleDigest !== descriptor.title_digest) {
throw new Error("private title lookup digest does not match the frozen descriptor.");
}
const bindingDigest = knowledgeGuardedDigest({
binding: descriptor.binding,
operation_id: descriptor.operation_id,
step_id: descriptor.step_id,
title_digest: descriptor.title_digest
});
if (bindingDigest !== descriptor.binding_digest) {
throw new Error("private title lookup binding digest does not match.");
}
if (knowledgeGuardedUtf8Bytes(envelope) > headerBounds.max_bytes) {
throw new Error("private title lookup envelope exceeds the producer byte cap.");
}
return envelope;
} catch (error) {
if (error instanceof HttpError)
throw error;
throw new HttpError(400, error instanceof Error ? error.message : "invalid private title lookup envelope.");
}
}
function validateGuardedAdoptionEnvelope(value, headerBounds, authority, idempotencyKey) {
try {
if (!value || typeof value !== "object") {
Expand Down Expand Up @@ -3596,6 +3721,31 @@ function createServeHandler(deps) {
throw error;
}
}
if (path === "/v1/guarded-writes/lookups/title" && method === "POST") {
if (!guardedRepo) {
return json({ error: "guarded_authority_unconfigured" }, 503);
}
const startedAt = Date.now();
const tenantId = req.headers.get("x-knowledge-tenant-id");
if (!tenantId)
throw new HttpError(400, "x-knowledge-tenant-id is required.");
await authOrThrow(req, ["knowledge:read"], tenantId);
const bounds = guardedBoundsFromHeaders(req);
const raw = await readBoundedJson(req, bounds, startedAt);
const envelope = validatePrivateTitleLookupEnvelope(raw, bounds, guardedRepo.authority);
if (envelope.descriptor.binding.tenant_id !== tenantId) {
throw new HttpError(403, "descriptor tenant does not match the authenticated request tenant.");
}
try {
const result = await guardedRepo.lookupTitle(envelope.title, envelope.descriptor.binding, bounds);
return boundedJson(result, 200, bounds, startedAt);
} catch (error) {
if (error instanceof PrivateTitleLookupAmbiguousError) {
return boundedJson({ error: "private_title_lookup_ambiguous" }, 409, bounds, startedAt);
}
throw error;
}
}
const guardedReceiptMatch = path.match(/^\/v1\/guarded-writes\/receipts\/([^/]+)$/);
if (guardedReceiptMatch) {
if (method !== "GET")
Expand Down
Loading
Loading