diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
deleted file mode 100644
index 9b4849d..0000000
--- a/.github/workflows/deploy.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-name: Deploy GitHub Pages
-
-on:
- push:
- branches:
- - main # Set a branch to deploy from
-
-permissions:
- contents: read
- pages: write
- id-token: write
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout your repository
- uses: actions/checkout@v4
-
- - name: Setup Node.js (optional, remove if not needed)
- uses: actions/setup-node@v4
- with:
- node-version: '20'
-
- - name: Setup Pages
- uses: actions/configure-pages@v4
-
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: './' # Adjust this path if your build output is in a subfolder, e.g., './_site'
-
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- needs: build
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index aa0cf90..b19d104 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -28,9 +28,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify canon authority
- run: node -e "const c=require('./src/_data/generated/canon.json'); if(c.source.repository!=='wayseer00/wayseer.github.io'||c.source.path!=='canon/the_interdependent_way.md') process.exit(1)"
+ run: node -e "const c=require('./src/_data/generated/canon.json'); if(c.source.repository!=='wayseer00/main'||c.source.path!=='canon/INTERDEPENDENT_WAY.txt') process.exit(1)"
- name: Verify canon unit evidence
- run: node -e "const c=require('./src/_data/generated/canon.json'); if(!c.source.contentSha256||c.source.contentSha256.length!==64||!c.units.length||c.units.some(u=>!u.id||!u.hash)) process.exit(1)"
+ run: node -e "const c=require('./src/_data/generated/canon.json'); if(!c.source.contentSha256||c.source.contentSha256.length!==64||(!c.source.fallback&&(!c.source.commit||!c.source.blob))||!c.units.length||c.units.some(u=>!u.id||!u.hash)) process.exit(1)"
- name: Verify repository route coverage
run: node -e "const r=require('./src/_data/generated/repos.json'); if(r.publicRepoCount!==r.generatedRouteCount||new Set(r.repositories.map(x=>x.slug)).size!==r.repositories.length) process.exit(1)"
- name: Verify recovery inputs
diff --git a/README.md b/README.md
index cf3b4c8..5aaffa9 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ This repository builds `interdependentway.org`: a static-first, progressively la
## What is authoritative
-The canonical identity remains `wayseer00/wayseer.github.io:canon/the_interdependent_way.md`. The repository copy at `canon/the_interdependent_way.md` is a recovery mirror. Build output records whether the remote source or recovery mirror supplied the current snapshot, together with SHA-256 provenance.
+The canonical text lives in `wayseer00/main:canon/INTERDEPENDENT_WAY.txt`, and nowhere in this repository supersedes it. The repository copy at `canon/the_interdependent_way.md` is a recovery mirror only. Build output records whether the remote source or recovery mirror supplied the current snapshot, together with SHA-256 provenance; successful remote retrieval also records the resolved source commit and blob SHA.
## Architecture
diff --git a/docs/architecture.md b/docs/architecture.md
index b94495f..6c13150 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -4,8 +4,8 @@ The production site is generated into `_site` by Eleventy. Pages are complete HT
## Data paths
-- `scripts/fetch-canon.mjs` retrieves the Wayseer canonical alias `wayseer00/wayseer.github.io:canon/the_interdependent_way.md`. The transferred repository copy at `canon/the_interdependent_way.md` is recovery-only and is recorded as a fallback when used.
-- `scripts/parse-canon.mjs` generates units, line ranges, note text, relationships, and SHA-256 digests.
+- `scripts/fetch-canon.mjs` retrieves the Wayseer text canon from `wayseer00/main:canon/INTERDEPENDENT_WAY.txt`. The transferred repository copy at `canon/the_interdependent_way.md` is recovery-only and is recorded as a fallback when used.
+- `scripts/parse-canon.mjs` generates units, line ranges, note text, relationships, and SHA-256 digests from either Markdown-style recovery mirrors or the plain-text canonical file.
- `scripts/fetch-github-org.mjs` discovers every public organization repository and merges GitHub facts with `.interdependency/project.yml` or reviewed central overrides.
- Pagefind indexes the generated site after Eleventy finishes.
diff --git a/scripts/fetch-canon.mjs b/scripts/fetch-canon.mjs
index 174dd53..eddf3ab 100644
--- a/scripts/fetch-canon.mjs
+++ b/scripts/fetch-canon.mjs
@@ -10,33 +10,70 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
// === END MODULE_BUILD ===
// === BOUNDARIES ===
// id: canon_network_boundary
-// network: read-only HTTPS request to raw.githubusercontent.com
+// network: read-only HTTPS request to allowlisted GitHub API and raw content endpoints
// storage: writes generated snapshots beneath src/_data/snapshots
// failure: falls back to the repository mirror and records fallback=true
// === END BOUNDARIES ===
const canonical = {
- repository: 'wayseer00/wayseer.github.io',
- path: 'canon/the_interdependent_way.md',
+ repository: 'wayseer00/main',
+ path: 'canon/INTERDEPENDENT_WAY.txt',
branch: 'main',
- url: 'https://raw.githubusercontent.com/wayseer00/wayseer.github.io/main/canon/the_interdependent_way.md'
+ webUrl: 'https://github.com/wayseer00/main/blob/main/canon/INTERDEPENDENT_WAY.txt'
};
const localMirror = 'canon/the_interdependent_way.md';
+const githubApiOrigin = 'https://api.github.com';
+const rawOrigin = 'https://raw.githubusercontent.com';
+const allowedOrigins = new Set([githubApiOrigin, rawOrigin]);
+const githubHeaders = ['-H', 'Accept: application/vnd.github+json', '-H', 'X-GitHub-Api-Version: 2022-11-28'];
+if (process.env.GITHUB_TOKEN) githubHeaders.push('-H', `Authorization: Bearer ${process.env.GITHUB_TOKEN}`);
-function fetchRemote() {
- if (process.env.OFFLINE === '1') throw new Error('offline requested');
- return execFileSync('curl', ['-fsSL', '--retry', '2', '--max-time', '30', canonical.url], {
+function curlText(target, extraHeaders = []) {
+ const url = target instanceof URL ? target : new URL(target);
+ if (url.protocol !== 'https:' || !allowedOrigins.has(url.origin)) {
+ throw new Error(`refusing non-allowlisted canon target: ${url.origin}`);
+ }
+ return execFileSync('curl', ['-fsSL', '--retry', '2', '--max-time', '30', ...extraHeaders, url.href], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
}
+function githubApiUrl(pathname, search = {}) {
+ const url = new URL(pathname, githubApiOrigin);
+ for (const [key, value] of Object.entries(search)) url.searchParams.set(key, String(value));
+ return url;
+}
+
+function getJson(target) {
+ return JSON.parse(curlText(target, githubHeaders));
+}
+
+function fetchRemote() {
+ if (process.env.OFFLINE === '1') throw new Error('offline requested');
+ const [owner, repo] = canonical.repository.split('/');
+ const encodedOwner = encodeURIComponent(owner);
+ const encodedRepo = encodeURIComponent(repo);
+ const encodedPath = canonical.path.split('/').map(segment => encodeURIComponent(segment)).join('/');
+ const apiBase = `/repos/${encodedOwner}/${encodedRepo}`;
+ const commitInfo = getJson(githubApiUrl(`${apiBase}/commits/${encodeURIComponent(canonical.branch)}`));
+ const commit = commitInfo.sha;
+ if (!/^[a-f0-9]{40}$/i.test(commit)) throw new Error('canon branch did not resolve to a commit SHA');
+ const fileInfo = getJson(githubApiUrl(`${apiBase}/contents/${encodedPath}`, { ref: commit }));
+ if (fileInfo.type !== 'file' || !fileInfo.sha) throw new Error('canon path did not resolve to a file blob');
+ const resolvedUrl = new URL(`/${encodedOwner}/${encodedRepo}/${commit}/${encodedPath}`, rawOrigin);
+ const text = curlText(resolvedUrl);
+ return { text, commit, blob: fileInfo.sha, resolvedUrl: resolvedUrl.href };
+}
+
await mkdir('src/_data/snapshots', { recursive: true });
let text;
let fallback = false;
let retrievalError = null;
+let remote = { commit: null, blob: null, resolvedUrl: null };
try {
- text = fetchRemote();
+ remote = fetchRemote();
+ text = remote.text;
} catch (error) {
fallback = true;
retrievalError = String(error?.message || error);
@@ -51,6 +88,9 @@ try {
const contentSha256 = createHash('sha256').update(text).digest('hex');
const metadata = {
...canonical,
+ commit: remote.commit,
+ blob: remote.blob,
+ resolvedUrl: remote.resolvedUrl,
retrievedAt: new Date().toISOString(),
contentSha256,
fallback,
diff --git a/scripts/parse-canon.mjs b/scripts/parse-canon.mjs
index 720325e..4ede66f 100644
--- a/scripts/parse-canon.mjs
+++ b/scripts/parse-canon.mjs
@@ -9,7 +9,7 @@ import slugify from 'slugify';
// tests: tests/canon-integrity.test.mjs
// === END MODULE_BUILD ===
-const parserVersion = '0.3.0';
+const parserVersion = '0.4.1';
const provenance = JSON.parse(await readFile('src/_data/snapshots/canon.provenance.json', 'utf8'));
const raw = await readFile('src/_data/snapshots/canon.last-known-good.md', 'utf8');
const text = raw.replace(/^---\n[\s\S]*?\n---\n/, '');
@@ -19,7 +19,6 @@ const units = [];
const sections = [];
let current = null;
let sectionId = 'source';
-const articleBySection = new Map();
function slug(value) {
return slugify(value, { lower: true, strict: true }) || 'unit';
@@ -30,36 +29,69 @@ function boundedRouteSlug(id) {
const suffix = createHash('sha256').update(id).digest('hex').slice(0, 10);
return `${candidate.slice(0, 84).replace(/-+$/, '')}-${suffix}`;
}
+function detectHeading(line) {
+ const markdown = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
+ if (markdown) return { level: markdown[1].length, title: markdown[2].replace(/#+$/, '').trim() };
+ const title = line.trim();
+ if (!title) return null;
+ if (title === 'The Interdependent Way') return { level: 1, title };
+ if (/^(Awakening|The Interdefinables|Human consciousness emerges from|Preamble|Etiquette of the Body Politic)$/i.test(title)) {
+ return { level: 2, title };
+ }
+ if (/^Rights[\w\s’'&\-⁰¹²³⁴⁵⁶⁷⁸⁹]+of The Way[⁰¹²³⁴⁵⁶⁷⁸⁹]*$/i.test(title)) return { level: 2, title };
+ if (/^Addendum:\s+.+$/i.test(title)) return { level: 2, title };
+ if (/^Article\s+(One|Two|Three|Four|Five|Six|Seven|Eight)(?:\s+\([^)]+\))?$/i.test(title)) return { level: 3, title };
+ if (/^(Binary essences meaningfully, divided; then, rooted\.|Trinary perceptual focal states of complex system spirals:.+|Trinary states of social perception:|Archetype passions of possession\..+|Summary|One-sentence takeaway \(exactly as previously given\))$/i.test(title)) {
+ return { level: 3, title };
+ }
+ return null;
+}
+function extractNotes(content) {
+ const notes = [];
+ for (const line of content.split(/\r?\n/)) {
+ const bracket = /^\s*\[([^\]]+)\]\s+(.+)$/.exec(line);
+ if (bracket) {
+ notes.push({ marker: `[${bracket[1]}]`, text: bracket[2].trim() });
+ continue;
+ }
+ const superscript = /^\s*>?\s*([⁰¹²³⁴⁵⁶⁷⁸⁹]+)\s*(.+)$/.exec(line);
+ if (superscript) {
+ notes.push({ marker: superscript[1], text: superscript[2].trim() });
+ continue;
+ }
+ const digit = /^\s*>?\s*(\d+)\s+(.+)$/.exec(line);
+ if (digit) notes.push({ marker: digit[1], text: digit[2].trim() });
+ }
+ return notes;
+}
+function extractNoteMarkers(content) {
+ const markers = [];
+ for (const match of content.matchAll(/\[[^\]]+\]|[⁰¹²³⁴⁵⁶⁷⁸⁹]+/g)) markers.push(match[0]);
+ return [...new Set(markers)];
+}
function finish(endLine) {
if (!current) return;
current.endLine = endLine;
current.content = current.lines.join('\n').trim();
current.hash = createHash('sha256').update(current.content).digest('hex');
- const notePattern = /^\s*\[([^\]]+)\]\s+(.+)$/gm;
- current.notes = [...current.content.matchAll(notePattern)].map(match => ({ marker: `[${match[1]}]`, text: match[2].trim() }));
- current.noteMarkers = [...new Set([...current.content.matchAll(/\[([^\]]+)\]/g)].map(match => `[${match[1]}]`))];
+ current.notes = extractNotes(current.content);
+ current.noteMarkers = extractNoteMarkers(current.content);
units.push(current);
}
for (let index = 0; index < lines.length; index += 1) {
- const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(lines[index]);
+ const heading = detectHeading(lines[index]);
if (!heading) {
if (current) current.lines.push(lines[index]);
continue;
}
finish(index);
- const level = heading[1].length;
- const title = heading[2].replace(/#+$/, '').trim();
- if (level <= 3) {
+ const { level, title } = heading;
+ if (level <= 2) {
sectionId = slug(title).replace(/^the-/, '');
if (!sections.some(section => section.id === sectionId)) sections.push({ id: sectionId, title, level, line: index + 1 });
}
- let localId = slug(title);
- if (/^article\s+/i.test(title)) {
- const count = (articleBySection.get(sectionId) || 0) + 1;
- articleBySection.set(sectionId, count);
- localId = `article-${count}`;
- }
+ const localId = slug(title);
current = {
id: `${sectionId}.${localId}`,
title,
diff --git a/scripts/validate-content.mjs b/scripts/validate-content.mjs
index 8da4827..65c30b6 100644
--- a/scripts/validate-content.mjs
+++ b/scripts/validate-content.mjs
@@ -9,9 +9,10 @@ import { access, readFile } from 'node:fs/promises';
const canon = JSON.parse(await readFile('src/_data/generated/canon.json', 'utf8'));
const repos = JSON.parse(await readFile('src/_data/generated/repos.json', 'utf8'));
-if (canon.source.repository !== 'wayseer00/wayseer.github.io') throw new Error(`unexpected canon repository: ${canon.source.repository}`);
-if (canon.source.path !== 'canon/the_interdependent_way.md') throw new Error(`unexpected canon path: ${canon.source.path}`);
+if (canon.source.repository !== 'wayseer00/main') throw new Error(`unexpected canon repository: ${canon.source.repository}`);
+if (canon.source.path !== 'canon/INTERDEPENDENT_WAY.txt') throw new Error(`unexpected canon path: ${canon.source.path}`);
if (!canon.source.contentSha256 || canon.source.contentSha256.length !== 64) throw new Error('canon missing SHA-256 digest');
+if (!canon.source.fallback && (!canon.source.commit || !canon.source.blob)) throw new Error('remote canon provenance missing commit or blob SHA');
if (!canon.units.length || canon.units.some(unit => !unit.hash || !unit.id)) throw new Error('canon units missing identity or hash');
if (repos.publicRepoCount !== repos.generatedRouteCount) throw new Error('repo route mismatch');
if (new Set(repos.repositories.map(repo => repo.slug)).size !== repos.repositories.length) throw new Error('duplicate project slug');
diff --git a/src/source/unit.njk b/src/source/unit.njk
index a35372d..7f256e4 100644
--- a/src/source/unit.njk
+++ b/src/source/unit.njk
@@ -9,5 +9,5 @@ title: "Source: {{ unit.title }}"
---
Exact source · deliberate depth Verbatim source content and machine-verifiable provenance. Commentary elsewhere on this site is subordinate to this layer.{{ unit.title }}
{{ unit.content }}
diff --git a/tests/canon-integrity.test.mjs b/tests/canon-integrity.test.mjs
index 4f1b2cd..3165fa3 100644
--- a/tests/canon-integrity.test.mjs
+++ b/tests/canon-integrity.test.mjs
@@ -4,9 +4,13 @@ import { readFile } from 'node:fs/promises';
test('canon data preserves Wayseer identity, provenance, and stable unit evidence', async () => {
const canon = JSON.parse(await readFile('src/_data/generated/canon.json', 'utf8'));
- assert.equal(canon.source.repository, 'wayseer00/wayseer.github.io');
- assert.equal(canon.source.path, 'canon/the_interdependent_way.md');
+ assert.equal(canon.source.repository, 'wayseer00/main');
+ assert.equal(canon.source.path, 'canon/INTERDEPENDENT_WAY.txt');
assert.match(canon.source.contentSha256, /^[a-f0-9]{64}$/);
+ if (!canon.source.fallback) {
+ assert.match(canon.source.commit, /^[a-f0-9]{40}$/);
+ assert.match(canon.source.blob, /^[a-f0-9]{40}$/);
+ }
assert.ok(canon.units.length > 0);
const routes = new Set();
for (const unit of canon.units) {