feat: added a hidden toggle availible in frontmatter which renders the note unlisted, also updated agent behavior with a compliance skill

This commit is contained in:
venus
2026-09-23 21:58:54 -05:00
parent dd866427f0
commit c211ad3a04
13 changed files with 831 additions and 14 deletions
+293
View File
@@ -0,0 +1,293 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import http from "node:http";
import { isPostHidden, isPostPublic, postComparator } from "../src/_utils/sort-posts.js";
import { createServer, normalizeObsidianMarkdown } from "../server.js";
test("Hidden Posts: isPostHidden accurately identifies hidden flags across data shapes", () => {
// 1. Explicit boolean true
assert.equal(isPostHidden({ data: { hidden: true } }), true, "Explicit boolean true in data");
assert.equal(isPostHidden({ hidden: true }), true, "Root level hidden true");
// 2. Truthy string representations (case-insensitive & trimmed)
assert.equal(isPostHidden({ data: { hidden: "true" } }), true, "String 'true'");
assert.equal(isPostHidden({ data: { hidden: "TRUE" } }), true, "Uppercase string 'TRUE'");
assert.equal(isPostHidden({ data: { hidden: " true " } }), true, "Whitespace-padded 'true'");
assert.equal(isPostHidden({ data: { hidden: "yes" } }), true, "String 'yes'");
assert.equal(isPostHidden({ data: { hidden: "YES" } }), true, "String 'YES'");
assert.equal(isPostHidden({ data: { hidden: "1" } }), true, "String '1'");
assert.equal(isPostHidden({ data: { hidden: 1 } }), true, "Numeric 1");
// 3. Falsy representations
assert.equal(isPostHidden({ data: { hidden: false } }), false, "Explicit boolean false");
assert.equal(isPostHidden({ data: { hidden: "false" } }), false, "String 'false'");
assert.equal(isPostHidden({ data: { hidden: "no" } }), false, "String 'no'");
assert.equal(isPostHidden({ data: { hidden: "0" } }), false, "String '0'");
assert.equal(isPostHidden({ data: { hidden: 0 } }), false, "Numeric 0");
assert.equal(isPostHidden({ data: { hidden: "" } }), false, "Empty string");
assert.equal(isPostHidden({ data: { hidden: null } }), false, "Null property");
assert.equal(isPostHidden({ data: {} }), false, "Missing hidden property");
assert.equal(isPostHidden({}), false, "Empty post object");
// 4. Adversarial edge cases
assert.equal(isPostHidden(null), false, "Null input");
assert.equal(isPostHidden(undefined), false, "Undefined input");
assert.equal(isPostHidden("unexpected-string"), false, "Primitive string input");
assert.equal(isPostHidden(12345), false, "Primitive number input");
assert.equal(isPostHidden([]), false, "Array input");
});
test("Hidden Posts: isPostPublic requires post to be neither draft nor hidden", () => {
// Public post: not draft, not hidden
assert.equal(
isPostPublic({ data: { title: "Public Post", draft: false, hidden: false } }),
true,
"Standard post is public"
);
assert.equal(
isPostPublic({ data: { title: "Public Post" } }),
true,
"Post without draft or hidden is public"
);
// Hidden post is NOT public
assert.equal(
isPostPublic({ data: { title: "Hidden Post", hidden: true } }),
false,
"Hidden post is not public"
);
assert.equal(
isPostPublic({ data: { title: "Hidden Post", hidden: "true" } }),
false,
"Hidden string post is not public"
);
// Draft post is NOT public
assert.equal(
isPostPublic({ data: { title: "Draft Post", draft: true } }),
false,
"Draft post is not public"
);
// Both draft and hidden is NOT public
assert.equal(
isPostPublic({ data: { title: "Draft and Hidden", draft: true, hidden: true } }),
false,
"Draft and hidden post is not public"
);
// Adversarial checks
assert.equal(isPostPublic(null), false, "Null is not public");
assert.equal(isPostPublic(undefined), false, "Undefined is not public");
assert.equal(isPostPublic("random"), false, "Non-object is not public");
});
test("Hidden Posts: Collection filtering excludes hidden posts from public posts list and tagList", () => {
const posts = [
{
data: { title: "Normal Post", tags: ["tech"] },
date: new Date("2026-09-10")
},
{
data: { title: "Pinned Post", pinned: true, tags: ["tech", "featured"] },
date: new Date("2026-09-01")
},
{
data: { title: "Draft Post", draft: true, tags: ["wip"] },
date: new Date("2026-09-15")
},
{
data: { title: "Secret Hidden Post", hidden: true, tags: ["secret-only"] },
date: new Date("2026-09-20")
},
{
data: { title: "Another Hidden Post", hidden: "yes", tags: ["internal"] },
date: new Date("2026-09-21")
},
{
data: { title: "Explicit Unhidden Post", hidden: false, tags: ["news"] },
date: new Date("2026-09-12")
}
];
// Filter public posts using isPostPublic (or !draft && !isPostHidden)
const publicPosts = posts.filter(isPostPublic).sort(postComparator);
const titles = publicPosts.map((p) => p.data.title);
assert.deepEqual(
titles,
["Pinned Post", "Explicit Unhidden Post", "Normal Post"],
"Public posts must include only non-draft, non-hidden posts in proper sorted order"
);
// Verify secret-only tags from hidden posts do not leak into public tag list
const publicTags = new Set();
posts
.filter(isPostPublic)
.forEach((item) => {
const tags = item.data.tags || [];
for (const t of tags) publicTags.add(t);
});
assert.ok(!publicTags.has("secret-only"), "Hidden post tag 'secret-only' must not appear in public tag list");
assert.ok(!publicTags.has("internal"), "Hidden post tag 'internal' must not appear in public tag list");
assert.ok(!publicTags.has("wip"), "Draft post tag 'wip' must not appear in public tag list");
assert.ok(publicTags.has("tech"), "Public post tag 'tech' must appear in public tag list");
assert.ok(publicTags.has("featured"), "Public post tag 'featured' must appear in public tag list");
});
test("Hidden Posts: Post layout injects noindex meta tag when hidden is true", () => {
const postLayoutPath = path.resolve("src/_includes/layouts/post.njk");
assert.ok(fs.existsSync(postLayoutPath), "post.njk must exist");
const postLayout = fs.readFileSync(postLayoutPath, "utf-8");
// Adversarial verification: hidden posts must instruct search crawlers not to index or follow
assert.match(
postLayout,
/noindex,\s*nofollow/i,
"post.njk must define 'noindex, nofollow' robots meta tag when hidden"
);
assert.match(
postLayout,
/{%\s*if\s+hidden\b/i,
"post.njk must conditionally check if post is hidden"
);
});
test("Hidden Posts: Obsidian normalization preserves hidden frontmatter property", () => {
const obsidianNote = `---
title: "Confidential System Blueprint"
created: 2026-09-23
tags: ["#architecture", "#secret"]
hidden: true
---
# Blueprint
Details for authorized eyes only.
`;
const normalized = normalizeObsidianMarkdown(obsidianNote);
assert.match(normalized, /hidden:\s*true/, "Normalized markdown must preserve 'hidden: true'");
assert.match(normalized, /title:\s*"Confidential System Blueprint"/, "Preserves title");
assert.match(normalized, /date:\s*2026-09-23/, "Bridges created to date");
assert.match(normalized, /layout:\s*layouts\/post\.njk/, "Adds post layout");
});
test("Hidden Posts: End-to-end static compilation and accessibility via direct link", async () => {
const tmpDir = fs.mkdtempSync(path.join(process.cwd(), "test-tmp-site-"));
const tmpPosts = fs.mkdtempSync(path.join(process.cwd(), "test-tmp-posts-"));
try {
// 1. Create a public post and a hidden post
const publicPostContent = `---
title: "Public Announcement"
date: 2026-09-20
layout: layouts/post.njk
tags: ["news"]
excerpt: "This is a public announcement for everyone to read."
---
Public content visible to everyone.`;
const hiddenPostContent = `---
title: "Private Unlisted Memo"
date: 2026-09-22
layout: layouts/post.njk
tags: ["confidential"]
excerpt: "This is an unlisted memo meant for direct link access only."
hidden: true
---
Secret memo content available by link only.`;
fs.writeFileSync(path.join(tmpPosts, "2026-09-20-public-post.md"), publicPostContent, "utf-8");
fs.writeFileSync(path.join(tmpPosts, "2026-09-22-secret-memo.md"), hiddenPostContent, "utf-8");
// 2. Set up server with custom directories
process.env.API_SECRET = "test-secret";
const server = createServer(tmpDir, tmpPosts);
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const port = server.address().port;
try {
// Simulate static files created by build
fs.mkdirSync(path.join(tmpDir, "posts/2026-09-22-secret-memo"), { recursive: true });
fs.mkdirSync(path.join(tmpDir, "posts/2026-09-20-public-post"), { recursive: true });
const secretHtml = `<!DOCTYPE html><html><head><title>Private Unlisted Memo</title><meta name="robots" content="noindex, nofollow"></head><body>Secret memo content available by link only.</body></html>`;
const publicHtml = `<!DOCTYPE html><html><head><title>Public Announcement</title></head><body>Public content visible to everyone.</body></html>`;
const indexHtml = `<!DOCTYPE html><html><body><a href="/posts/2026-09-20-public-post/">Public Announcement</a></body></html>`;
fs.writeFileSync(path.join(tmpDir, "posts/2026-09-22-secret-memo/index.html"), secretHtml);
fs.writeFileSync(path.join(tmpDir, "posts/2026-09-20-public-post/index.html"), publicHtml);
fs.writeFileSync(path.join(tmpDir, "index.html"), indexHtml);
// Verify direct access to hidden post succeeds (HTTP 200 OK)
const resSecret = await fetch(`http://127.0.0.1:${port}/posts/2026-09-22-secret-memo/`);
assert.strictEqual(resSecret.status, 200, "Direct link to hidden post must return 200 OK");
const secretBody = await resSecret.text();
assert.match(secretBody, /Secret memo content available by link only/, "Hidden post body is accessible");
assert.match(secretBody, /noindex,\s*nofollow/, "Hidden post includes noindex meta tag");
// Verify public index does NOT contain link or title to hidden post
const resIndex = await fetch(`http://127.0.0.1:${port}/`);
assert.strictEqual(resIndex.status, 200);
const indexBody = await resIndex.text();
assert.match(indexBody, /Public Announcement/, "Public post is listed on index");
assert.doesNotMatch(indexBody, /Private Unlisted Memo/, "Hidden post MUST NOT appear on index");
assert.doesNotMatch(indexBody, /secret-memo/, "Hidden post link MUST NOT appear on index");
} finally {
await new Promise((resolve) => server.close(resolve));
}
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
fs.rmSync(tmpPosts, { recursive: true, force: true });
}
});
test("Hidden Posts: Obsidian API ingestion correctly writes hidden frontmatter", async () => {
const tmpDir = fs.mkdtempSync(path.join(process.cwd(), "test-tmp-site-"));
const tmpPosts = fs.mkdtempSync(path.join(process.cwd(), "test-tmp-posts-"));
process.env.API_SECRET = "test-secret";
const server = createServer(tmpDir, tmpPosts);
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const port = server.address().port;
try {
const res = await fetch(`http://127.0.0.1:${port}/api/posts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer test-secret"
},
body: JSON.stringify({
slug: "unlisted-post",
content: `---
title: "Direct Share Only"
created: 2026-09-23
hidden: true
tags: ["#private"]
---
Secret note content.`
})
});
assert.strictEqual(res.status, 200);
const json = await res.json();
assert.strictEqual(json.status, "published");
assert.strictEqual(json.path, "/posts/unlisted-post/");
const writtenFile = path.join(tmpPosts, "unlisted-post.md");
assert.ok(fs.existsSync(writtenFile), "Post file must exist");
const writtenContent = fs.readFileSync(writtenFile, "utf-8");
assert.match(writtenContent, /hidden:\s*true/);
assert.match(writtenContent, /layout:\s*layouts\/post\.njk/);
} finally {
await new Promise((resolve) => server.close(resolve));
fs.rmSync(tmpDir, { recursive: true, force: true });
fs.rmSync(tmpPosts, { recursive: true, force: true });
}
});