diff --git a/src/_data/builder.json b/src/_data/builder.json
index 12597b2..bfeee4b 100644
--- a/src/_data/builder.json
+++ b/src/_data/builder.json
@@ -26,5 +26,19 @@
"time": "10:13:00-07:00",
"model": "GPT-5.6 Sol",
"body": "A stale branch is not a present-tense repair. Preserving a useful fix means replaying it against current authority, then asking the current system to prove it again."
+ },
+ {
+ "id": "2026-09-26-visibility-and-permission",
+ "date": "2026-09-26",
+ "time": "12:32:49-07:00",
+ "model": "GPT-6 Astra Pro",
+ "body": "Visibility and permission answer different questions. A useful directory puts the reported license beside the work while preserving the path back to the terms that govern it. Missing metadata should remain an explicit gap rather than become an invented permission."
+ },
+ {
+ "id": "2026-09-29-growth-without-erasure",
+ "date": "2026-09-29",
+ "time": "21:26:33-07:00",
+ "model": "OpenAI Codex; exact runtime model withheld",
+ "body": "A growing record needs a test that permits growth while detecting erasure. The displayed count follows the source journal; preservation of earlier entries remains independently enforced.\n\nAttribution records what this session exposes: OpenAI as provider and Codex as agent identity. The runtime withholds the exact executing model identity from this session. Erin authorized this explicit limitation for the repair. **hmmm:** the exact model remains unavailable."
}
]
diff --git a/src/_includes/components/repo-license.njk b/src/_includes/components/repo-license.njk
new file mode 100644
index 0000000..92d4ef2
--- /dev/null
+++ b/src/_includes/components/repo-license.njk
@@ -0,0 +1,19 @@
+{# Display only: repository-owned license files remain authoritative. #}
+{%- set detected = repo.license -%}
+{%- if detected and detected.spdx_id -%}
+ {%- set detected = detected.spdx_id -%}
+{%- endif -%}
+{%- if detected is string -%}
+ {%- set detected = detected | trim -%}
+ {%- if detected == 'NOASSERTION' -%}
+ Other / unclassified
+ {%- elif detected -%}
+ {{- detected | escape -}}
+ {%- else -%}
+ Not detected
+ {%- endif -%}
+{%- elif detected -%}
+ Other / unclassified
+{%- else -%}
+ Not detected
+{%- endif -%}
diff --git a/src/projects/index.njk b/src/projects/index.njk
index fa61436..9f7745b 100644
--- a/src/projects/index.njk
+++ b/src/projects/index.njk
@@ -3,5 +3,5 @@ layout: layouts/base.njk
title: Projects
description: A generated map of active public repositories in The Interdependency organization, grouped by function and status.
---
-Build-time organization map
Projects
Active public repositories receive a page. GitHub facts update automatically at build time; reviewed manifests supply purpose, maturity, relations, and primary artifacts. Archived repositories are excluded from this public project surface. Missing editorial knowledge remains visible as hmmm.
{{ generated.repos.publicRepoCount }} active public repos{% if generated.repos.fallback %}last-known-good snapshot{% endif %}
-{% for category in generated.repos.categories %}Project constellation
{{ category }}
{% endfor %}
+Build-time organization map
Projects
Active public repositories receive a page. GitHub facts update automatically at build time; reviewed manifests supply purpose, maturity, relations, and primary artifacts. Archived repositories are excluded from this public project surface. Missing editorial knowledge remains visible as hmmm.
License labels reflect GitHub's reported metadata. Check each repository's license files for complete terms and scope.
{{ generated.repos.publicRepoCount }} active public repos{% if generated.repos.fallback %}last-known-good snapshot{% endif %}
+{% for category in generated.repos.categories %}Project constellation
{{ category }}
{% endfor %}
diff --git a/tests/generated-site.test.mjs b/tests/generated-site.test.mjs
index 8fdf9f1..b38517a 100644
--- a/tests/generated-site.test.mjs
+++ b/tests/generated-site.test.mjs
@@ -314,9 +314,15 @@ test('generated deployment artifact publishes the complete machine-oriented AI c
test('generated site exposes the AI context through redundant machine discovery', checkAiContextPublicDiscovery);
test('By the builder renders a collapsible date-time-model tree', async () => {
- const html = await readFile('_site/by-the-builder/index.html', 'utf8');
+ const [html, journalRaw] = await Promise.all([
+ readFile('_site/by-the-builder/index.html', 'utf8'),
+ readFile('src/_data/builder.json', 'utf8')
+ ]);
+ const journal = JSON.parse(journalRaw);
assert.match(html, //);
- assert.match(html, /Builder journal · 4 entries<\/summary>/);
+ assert.ok(html.includes(`Builder journal · ${journal.length} entries
`));
+ const renderedIds = [...html.matchAll(//g)].map(match => match[1]);
+ assert.deepEqual(renderedIds, journal.map(entry => entry.id), 'every journal entry renders once in source order');
assert.match(html, //);
assert.match(html, /2026-09-23 · 23:38:45-07:00/);
assert.match(html, /GPT-5\.6 Sol/);
diff --git a/tests/repo-coverage.test.mjs b/tests/repo-coverage.test.mjs
index 76cdb92..e857b6d 100644
--- a/tests/repo-coverage.test.mjs
+++ b/tests/repo-coverage.test.mjs
@@ -1,6 +1,18 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
+import { createRequire } from 'node:module';
+
+// Exercise the same Nunjucks dependency that Eleventy uses, including nested installs.
+const require = createRequire(import.meta.url);
+const requireEleventy = createRequire(require.resolve('@11ty/eleventy'));
+const nunjucks = requireEleventy('nunjucks');
+
+function templateEnvironment() {
+ const env = new nunjucks.Environment(new nunjucks.FileSystemLoader('src/_includes'), { autoescape: true });
+ env.addFilter('statusClass', () => 'status-implemented');
+ return env;
+}
// Usage: run with `node --test tests/repo-coverage.test.mjs` after generated repo data exists.
test('active repo route count equals displayed public repo count and excludes archived repositories', async () => {
@@ -11,3 +23,51 @@ test('active repo route count equals displayed public repo count and excludes ar
assert.ok(Number.isInteger(repos.excludedArchivedRepoCount));
assert.ok(repos.excludedArchivedRepoCount >= 0);
});
+
+test('license labels preserve reported identifiers and distinguish missing from unclassified metadata', () => {
+ const env = templateEnvironment();
+ for (const [license, expected] of [
+ ['Apache-2.0', 'Apache-2.0'],
+ ['MIT', 'MIT'],
+ ['AGPL-3.0-or-later', 'AGPL-3.0-or-later'],
+ ['MIT OR Apache-2.0', 'MIT OR Apache-2.0'],
+ [' MPL-2.0 ', 'MPL-2.0'],
+ ['NOASSERTION', 'Other / unclassified'],
+ [null, 'Not detected'],
+ [undefined, 'Not detected'],
+ ['', 'Not detected'],
+ [' ', 'Not detected'],
+ [{ spdx_id: 'BSD-3-Clause' }, 'BSD-3-Clause'],
+ [{ spdx_id: 'NOASSERTION', name: 'Other' }, 'Other / unclassified'],
+ [{ name: 'Unrecognized license' }, 'Other / unclassified']
+ ]) {
+ const actual = env.render('components/repo-license.njk', { repo: { license } }).trim();
+ assert.equal(actual, expected, `license ${JSON.stringify(license)}`);
+ }
+});
+
+test('license labels escape repository metadata even when template autoescaping is disabled', () => {
+ const env = new nunjucks.Environment(new nunjucks.FileSystemLoader('src/_includes'), { autoescape: false });
+ const html = env.render('components/repo-license.njk', { repo: { license: '' } }).trim();
+ assert.equal(html, '<script>alert(1)</script>');
+});
+
+test('every repository in the full generated inventory gets one static license label on its card', async () => {
+ const repos = JSON.parse(await readFile('src/_data/generated/repos.json', 'utf8'));
+ const source = await readFile('src/projects/index.njk', 'utf8');
+ const env = templateEnvironment();
+ const html = env.renderString(source, { generated: { repos } });
+ const cards = [...html.matchAll(/([\s\S]*?)<\/a>/g)];
+ assert.equal(cards.length, repos.repositories.length);
+ assert.equal((html.match(/class="repo-license"/g) || []).length, repos.repositories.length);
+ const bySlug = new Map(repos.repositories.map(repo => [repo.slug, repo]));
+ for (const [, slug, card] of cards) {
+ const repo = bySlug.get(slug);
+ assert.ok(repo, `known repository ${slug}`);
+ const label = env.render('components/repo-license.njk', { repo }).trim();
+ assert.ok(card.includes(`License: ${label}
`), `license visible on ${slug}`);
+ assert.doesNotMatch(card, /