diff --git a/.eleventy.js b/.eleventy.js index 74bb358..e0446bb 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,11 +1,29 @@ import markdownIt from 'markdown-it'; -export default function(eleventyConfig) { +// === MODULE_BUILD === +// id: eleventy_site_configuration +// purpose: Build the static-first public knowledge system and copy deliberate fallback artifacts. +// entrypoint: npm run build +// tests: tests/site-contract.test.mjs +// === END MODULE_BUILD === + +export default function configureEleventy(eleventyConfig) { const md = markdownIt({ html: false, linkify: true, typographer: true }); eleventyConfig.setLibrary('md', md); - eleventyConfig.addPassthroughCopy({ 'src/assets': 'assets', CNAME: 'CNAME' }); - eleventyConfig.addFilter('json', v => JSON.stringify(v)); - eleventyConfig.addFilter('dateOnly', v => v ? String(v).slice(0,10) : 'hmmm'); - eleventyConfig.addFilter('where', (arr, key, val) => (arr || []).filter(item => item?.[key] === val)); - return { dir: { input: 'src', output: '_site', includes: '_includes', data: '_data' }, markdownTemplateEngine: 'njk', htmlTemplateEngine: 'njk' }; + eleventyConfig.addPassthroughCopy({ + 'src/assets': 'assets', + 'CNAME': 'CNAME', + 'artifacts/four-cuts-1.html': 'artifacts/four-cuts/index.html', + 'fallback': 'fallback' + }); + eleventyConfig.addFilter('json', value => JSON.stringify(value)); + eleventyConfig.addFilter('dateOnly', value => value ? String(value).slice(0, 10) : 'hmmm'); + eleventyConfig.addFilter('where', (items, key, value) => (items || []).filter(item => item?.[key] === value)); + eleventyConfig.addFilter('statusClass', value => `status-${String(value || 'hmmm').toLowerCase().replace(/[^a-z0-9]+/g, '-')}`); + + return { + dir: { input: 'src', output: '_site', includes: '_includes', data: '_data' }, + markdownTemplateEngine: 'njk', + htmlTemplateEngine: 'njk' + }; } diff --git a/.github/workflows/fallback.yml b/.github/workflows/fallback.yml new file mode 100644 index 0000000..b6de95a --- /dev/null +++ b/.github/workflows/fallback.yml @@ -0,0 +1,37 @@ +name: Deploy emergency static edition +on: + workflow_dispatch: + inputs: + reason: + description: Why the emergency edition is being deployed + required: true + type: string +permissions: + contents: read + pages: write + id-token: write +concurrency: + group: pages + cancel-in-progress: false +jobs: + verify-and-deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - name: Verify explicit fallback artifact + env: + DEPLOY_REASON: ${{ inputs.reason }} + run: | + test -s fallback/index.html + test -s fallback/fallback.css + ! grep -qi ' fallback/DEPLOYMENT_REASON.txt + - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b + - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b + with: + path: fallback + - id: deployment + uses: actions/deploy-pages@d74e976d0eb5d900a0cef4b369a9d84a9b222a53 diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index cf9ee22..8c9b652 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -25,7 +25,14 @@ jobs: cache: npm - run: npm ci - run: npm run check + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b + - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 + with: + name: verified-site-${{ github.sha }} + path: _site + retention-days: 30 - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b with: path: _site diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 9bbe0fa..aa0cf90 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,4 +12,43 @@ jobs: node-version-file: .nvmrc cache: npm - run: npm ci - - run: npm run check + - name: Run clean-checkout tests + run: | + set -o pipefail + npm test 2>&1 | tee clean-test.log + - name: Preserve clean-checkout test failure log + if: failure() + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 + with: + name: clean-test-failure-${{ github.sha }} + path: clean-test.log + retention-days: 3 + - name: Refresh canonical and organization data + run: npm run refresh:data + 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)" + - 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)" + - 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 + run: test -s fallback/index.html && test -s artifacts/four-cuts-1.html + - name: Validate generated content contracts + run: npm run validate + - name: Generate Eleventy site + run: | + set -o pipefail + npx eleventy 2>&1 | tee eleventy-build.log + - name: Preserve Eleventy failure log + if: failure() + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 + with: + name: eleventy-failure-${{ github.sha }} + path: eleventy-build.log + retention-days: 3 + - name: Generate Pagefind search index + run: npx pagefind --site _site + - name: Run generated-site tests + run: npm run test:generated diff --git a/.interdependency/project.yml b/.interdependency/project.yml new file mode 100644 index 0000000..9fd3527 --- /dev/null +++ b/.interdependency/project.yml @@ -0,0 +1,9 @@ +category: Public doctrine & publishing +status: implemented +summary: The static-first public entry point for The Interdependent Way and The Interdependency project constellation. +purpose: Orient visitors, preserve canon provenance, host layered companion readings, and generate one public space for every organization repository. +primary_artifact: https://interdependentway.org +docs: https://github.com/The-Interdependency/The-Interdependency.github.io/tree/main/docs +relationships: + - Reads the Wayseer canon mirror while preserving the wayseer00 canonical identity. + - Generates project pages from public GitHub facts and repository manifests. diff --git a/README.md b/README.md index bf68fb3..cf3b4c8 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,59 @@ -# a0p — Agent Zero Platform +# The Interdependent Way public knowledge system -a0p is a mobile-first autonomous AI agent application. It leverages Gemini function-calling and a mathematically rigorous orchestration engine (EDCMBONE) to execute tasks autonomously. It integrates with Google infrastructure (Gmail, Drive), provides file management with direct phone upload, and includes robust features like cryptographic audit logging and real-time cost tracking. +This repository builds `interdependentway.org`: a static-first, progressively layered entrance to The Interdependent Way, its deliberate tensions, surrounding research, public artifacts, and the repositories attempting implementation. -**Key Design Principles:** -* **Mobile-first**: Optimized for phone browsers and PWA-like experience. -* **Autonomous**: Agent determines tool usage without manual intervention. -* **Auditable**: Every engine action is hash-chained and timestamped. -* **Cost-aware**: Tracks per-token usage with estimated USD costs. -* **Fail-closed**: No silent fallback for critical operations. +## What is authoritative -The core of a0p's intelligence lies in its EDCMBONE engine, utilizing Prime Tensor Circular Architecture (PTCA) and Prime Circular Neural Architecture (PCNA) for sophisticated AI orchestration and decision-making. +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. ---- +## Architecture -# A0 +- Eleventy generates complete HTML into `_site`. +- Pagefind supplies static search. +- GitHub organization and canon data are retrieved at build time, never in a visitor’s browser. +- Every public organization repository receives a generated project page. +- `.interdependency/project.yml` supplies reviewed project purpose, maturity, relationships, and links. +- `fallback/` is a dependency-free emergency edition. +- `artifacts/four-cuts-1.html` is deliberately published at `/artifacts/four-cuts/` through Eleventy passthrough. -A0 is the instantiated runtime embodiment of PTCA. +## Usage guidance -## Core split -- Heartbeat: world-facing cadence, scheduling, sensing, deployment rhythm -- Psi: interpretation, synthesis, discernment, council coordination -- Omega: commitment, integration, release, terminal authority layer +```bash +npm install +npm run dev +npm run check +``` ---- +Use the recovery snapshots without network access: -# TIW Addendum Package: Fiddly Bits (Thread) +```bash +OFFLINE=1 npm run build +``` -This package contains the canonicalized thread addendum. +Add reviewed project metadata to a repository: -- `canon/addenda/ADDENDUM_FIDDLY_BITS_THREAD.md` +```yaml +category: Mathematics & verification +status: frontier +summary: One-sentence public description. +purpose: The repository's role within the project constellation. +primary_artifact: https://example.org +docs: https://github.com/org/repo/tree/main/docs +relationships: + - Depends on another named project for a specific function. +``` -hmm — unresolved constraint marker: If you want this merged into the full canon zip, say so and I will rebuild a combined package. +Place that file at `.interdependency/project.yml`. Until it exists, the public project page keeps the missing editorial layer visible as `hmmm`. + +## Status language + +- **canon** — exact or mechanically derived from the canonical source +- **interpretation** — explanatory material subject to review +- **research** — externally sourced support, dissent, or context +- **implemented** — a working public surface exists +- **frontier** — experimental, incomplete, or not externally established +- **hmmm** — an unresolved constraint with enough context for continuation + +## Release discipline + +GitHub Actions runs the build, validation, tests, static search generation, and deployment. Failed builds do not replace the last successful Pages artifact. Emergency fallback deployment is explicit rather than automatic. diff --git a/docs/architecture.md b/docs/architecture.md index 822589f..b94495f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,5 +1,18 @@ # Architecture -Static pages are generated by Eleventy. Build-time scripts produce `src/_data/generated/canon.json` from the local canonical snapshot and `src/_data/generated/repos.json` from the GitHub organization API or last-known-good fixture. +The production site is generated into `_site` by Eleventy. Pages are complete HTML first; JavaScript adds only optional interaction. The browser never needs to call GitHub to discover projects or retrieve canon. -The browser receives HTML, CSS, and optional Pagefind search enhancement. No visitor-side GitHub polling is used. +## 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-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. + +## Static backup + +`fallback/` is dependency-free and copied into every normal artifact. A separate manual recovery workflow may deploy it, but an ordinary build failure must leave the previous successful Pages deployment live. + +## Usage guidance + +Run `npm install && npm run dev` for local work. Run `OFFLINE=1 npm run build` to verify last-known-good operation without network access. Run `npm run check` before proposing publication. diff --git a/fallback/CNAME b/fallback/CNAME new file mode 100644 index 0000000..7a6ea0a --- /dev/null +++ b/fallback/CNAME @@ -0,0 +1 @@ +interdependentway.org diff --git a/fallback/fallback.css b/fallback/fallback.css new file mode 100644 index 0000000..5c074fa --- /dev/null +++ b/fallback/fallback.css @@ -0,0 +1 @@ +:root{color-scheme:dark;--bg:#090d18;--panel:#131c30;--text:#eef3ff;--muted:#aebbd2;--line:#334361;--accent:#9b87f5;--amber:#f0c36a}*{box-sizing:border-box}body{max-width:52rem;margin:auto;padding:2rem 1rem;background:var(--bg);color:var(--text);font:18px/1.65 system-ui,sans-serif}header,section,footer{padding:1.25rem;margin:1rem 0;border:1px solid var(--line);border-radius:1rem;background:var(--panel)}h1,h2{font-family:Georgia,serif}h1{font-size:clamp(2.5rem,8vw,5rem);line-height:1;margin:.25rem 0}a{color:#c4bbff}.flag,.hmmm h2{color:var(--amber);font-weight:800;text-transform:uppercase;letter-spacing:.08em;font-size:.8rem}.hmmm{border-left:.35rem solid var(--amber)} diff --git a/fallback/index.html b/fallback/index.html new file mode 100644 index 0000000..222c8e5 --- /dev/null +++ b/fallback/index.html @@ -0,0 +1 @@ +The Interdependent Way — emergency static edition

Emergency static edition

The Interdependent Way

This dependency-free page exists for explicit recovery deployment. The last successful full site should remain live when ordinary builds fail.

Begin

The Interdependent Way is a dense living text and a constellation of projects attempting to explain, test, implement, challenge, and preserve it.

Primary links

hmmm

This edition contains no dynamic project snapshot, search index, Article Lab, or external dependencies. It is a recovery floor, not the preferred public experience.

diff --git a/package.json b/package.json index 553c4c6..dcebe56 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,14 @@ "research:enrich": "node scripts/enrich-citations.mjs", "validate": "node scripts/validate-content.mjs && node scripts/verify-generated-routes.mjs", "build": "npm run refresh:data && npm run validate && eleventy && pagefind --site _site", - "test": "node --test tests/*.test.mjs", + "pretest": "node scripts/prepare-tests.mjs", + "test": "node --test tests/canon-integrity.test.mjs tests/offline-project-snapshot.test.mjs tests/repo-coverage.test.mjs tests/research-ledger.test.mjs tests/site-contract.test.mjs", + "test:generated": "node --test tests/generated-site.test.mjs && node tests/links.test.mjs", "test:e2e": "playwright test tests/*.spec.mjs", "test:a11y": "playwright test tests/accessibility.spec.mjs", "test:links": "node tests/links.test.mjs", "test:performance": "node scripts/performance-placeholder.mjs", - "check": "npm run build && npm run test" + "check": "npm run build && npm test && npm run test:generated" }, "dependencies": { "@11ty/eleventy": "3.1.2", diff --git a/scripts/fetch-canon.mjs b/scripts/fetch-canon.mjs index 27092ac..174dd53 100644 --- a/scripts/fetch-canon.mjs +++ b/scripts/fetch-canon.mjs @@ -1,10 +1,63 @@ -import { readFile, writeFile, mkdir } from 'node:fs/promises'; import { createHash } from 'node:crypto'; import { execFileSync } from 'node:child_process'; -await mkdir('src/_data/snapshots',{recursive:true}); -const local='canon/the_interdependent_way.md'; -let text=await readFile(local,'utf8'); -const commit=execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(); -const hash=createHash('sha256').update(text).digest('hex'); -await writeFile('src/_data/snapshots/canon.last-known-good.md',`---\nrepository: The-Interdependency/a0\npath: interdependent_way.md\ncommit: ${commit}\nretrieved_at: ${new Date().toISOString()}\ncontent_sha256: ${hash}\nfallback: local-repository-copy\n---\n${text}`); -console.log(`canon ${commit} ${hash}`); +import { mkdir, readFile, writeFile } from 'node:fs/promises'; + +// === MODULE_BUILD === +// id: canonical_source_fetch +// purpose: Retrieve the Wayseer canonical text or preserve a visibly labeled local recovery mirror. +// entrypoint: npm run refresh:canon +// tests: tests/canon-integrity.test.mjs +// === END MODULE_BUILD === +// === BOUNDARIES === +// id: canon_network_boundary +// network: read-only HTTPS request to raw.githubusercontent.com +// 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', + branch: 'main', + url: 'https://raw.githubusercontent.com/wayseer00/wayseer.github.io/main/canon/the_interdependent_way.md' +}; +const localMirror = 'canon/the_interdependent_way.md'; + +function fetchRemote() { + if (process.env.OFFLINE === '1') throw new Error('offline requested'); + return execFileSync('curl', ['-fsSL', '--retry', '2', '--max-time', '30', canonical.url], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'] + }); +} + +await mkdir('src/_data/snapshots', { recursive: true }); +let text; +let fallback = false; +let retrievalError = null; +try { + text = fetchRemote(); +} catch (error) { + fallback = true; + retrievalError = String(error?.message || error); + text = await readFile(localMirror, 'utf8'); +} +if (!text.trim()) throw new Error('canonical text is empty'); + +let siteCommit = 'unknown'; +try { + siteCommit = execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(); +} catch {} +const contentSha256 = createHash('sha256').update(text).digest('hex'); +const metadata = { + ...canonical, + retrievedAt: new Date().toISOString(), + contentSha256, + fallback, + fallbackSource: fallback ? localMirror : null, + retrievalError: fallback ? retrievalError : null, + siteCommit +}; +await writeFile('src/_data/snapshots/canon.last-known-good.md', `---\n${JSON.stringify(metadata)}\n---\n${text}`); +await writeFile('src/_data/snapshots/canon.provenance.json', JSON.stringify(metadata, null, 2)); +console.log(`canon ${canonical.repository}/${canonical.path} ${contentSha256}${fallback ? ' fallback' : ''}`); diff --git a/scripts/fetch-github-org.mjs b/scripts/fetch-github-org.mjs index c8f6e88..8e58400 100644 --- a/scripts/fetch-github-org.mjs +++ b/scripts/fetch-github-org.mjs @@ -1,19 +1,153 @@ -import { writeFile, readFile, mkdir } from 'node:fs/promises'; -const org='The-Interdependency'; -async function fetchAll(){ - if(process.env.OFFLINE==='1') throw new Error('offline requested'); - const { execFileSync } = await import('node:child_process'); - let page=1, repos=[]; while(true){ - const args=['-fsSL','-H','Accept: application/vnd.github+json','-H','X-GitHub-Api-Version: 2022-11-28']; - if(process.env.GITHUB_TOKEN) args.push('-H',`Authorization: Bearer ${process.env.GITHUB_TOKEN}`); - args.push(`https://api.github.com/orgs/${org}/repos?type=public&per_page=100&page=${page}`); - const batch=JSON.parse(execFileSync('curl',args,{encoding:'utf8',stdio:['ignore','pipe','pipe']})); - repos.push(...batch); if(batch.length<100) break; page++; - } - return repos; +import { execFileSync } from 'node:child_process'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import yaml from 'js-yaml'; + +// === MODULE_BUILD === +// id: organization_project_map +// purpose: Build public project pages from GitHub facts plus reviewed repository manifests or central overrides. +// entrypoint: npm run refresh:github +// tests: tests/repo-coverage.test.mjs, tests/offline-project-snapshot.test.mjs +// === END MODULE_BUILD === +// === BOUNDARIES === +// id: github_public_metadata +// network: reads only allowlisted HTTPS GitHub API endpoints; optional token raises rate limits +// storage: writes generated and last-known-good JSON snapshots +// failure: preserves last-known-good data with fallback=true, including reviewed editorial fields +// === END BOUNDARIES === + +const org = 'The-Interdependency'; +const githubApiOrigin = 'https://api.github.com'; +const headers = ['-H', 'Accept: application/vnd.github+json', '-H', 'X-GitHub-Api-Version: 2022-11-28']; +if (process.env.GITHUB_TOKEN) headers.push('-H', `Authorization: Bearer ${process.env.GITHUB_TOKEN}`); + +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; } -let fallback=false, repos=[]; -try { repos=await fetchAll(); } catch(e){ fallback=true; try{repos=JSON.parse(await readFile('src/_data/snapshots/repos.last-known-good.json','utf8')).repositories;}catch{repos=[];} } -const mapped=repos.map(r=>({name:r.name,slug:r.name.toLowerCase().replace(/[^a-z0-9]+/g,'-'),html_url:r.html_url,description:r.description,archived:r.archived,fork:r.fork,default_branch:r.default_branch,topics:r.topics||[],license:r.license?.spdx_id||null,language:r.language,homepage:r.homepage,pushed_at:r.pushed_at,visibility:r.visibility||'public',hmmm:['Editorial project map missing until .interdependency/project.yml is reviewed.']})); -const data={organization:org,snapshotAt:new Date().toISOString(),fallback,publicRepoCount:mapped.length,generatedRouteCount:mapped.length,repositories:mapped}; -await mkdir('src/_data/generated',{recursive:true}); await writeFile('src/_data/generated/repos.json',JSON.stringify(data,null,2)); await mkdir('src/_data/snapshots',{recursive:true}); await writeFile('src/_data/snapshots/repos.last-known-good.json',JSON.stringify(data,null,2)); console.log(`repos ${mapped.length}${fallback?' fallback':''}`); + +function getJson(target) { + const url = target instanceof URL ? target : new URL(target); + if (url.protocol !== 'https:' || url.origin !== githubApiOrigin) { + throw new Error(`refusing non-GitHub API target: ${url.origin}`); + } + return JSON.parse(execFileSync( + 'curl', + ['-fsSL', '--retry', '2', '--max-time', '30', ...headers, url.href], + { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] } + )); +} + +function normalizeRepoName(value) { + const name = String(value || ''); + if (!/^[A-Za-z0-9_.-]{1,100}$/.test(name)) throw new Error(`invalid GitHub repository name: ${name}`); + return name; +} + +function getManifest(repoName) { + try { + const safeRepo = normalizeRepoName(repoName); + const response = getJson(githubApiUrl( + `/repos/${encodeURIComponent(org)}/${encodeURIComponent(safeRepo)}/contents/.interdependency/project.yml` + )); + return yaml.load(Buffer.from(response.content || '', 'base64').toString('utf8')) || null; + } catch { + return null; + } +} + +function categoryFor(repo, editorial) { + if (editorial?.category) return editorial.category; + const text = `${repo.name} ${repo.description || ''} ${(repo.topics || []).join(' ')}`.toLowerCase(); + if (/way|canon|article|publication|website/.test(text)) return 'Public doctrine & publishing'; + if (/ucns|math|theorem|lean|gonal/.test(text)) return 'Mathematics & verification'; + if (/edcm|measure|evaluation|metric/.test(text)) return 'Measurement & evaluation'; + if (/a0|agent|zfae|aimmh|replit/.test(text)) return 'Agent infrastructure'; + if (/skill|msdmd|tool/.test(text)) return 'Skills & tooling'; + return 'Frontier projects'; +} + +let fallback = false; +let rawRepos = []; +let overrides = {}; +try { overrides = yaml.load(await readFile('src/_data/project-overrides.yml', 'utf8')) || {}; } catch {} +try { + if (process.env.OFFLINE === '1') throw new Error('offline requested'); + for (let page = 1; ; page += 1) { + const batch = getJson(githubApiUrl(`/orgs/${encodeURIComponent(org)}/repos`, { + type: 'public', + sort: 'updated', + per_page: 100, + page + })); + rawRepos.push(...batch); + if (batch.length < 100) break; + } +} catch { + fallback = true; + try { rawRepos = JSON.parse(await readFile('src/_data/snapshots/repos.last-known-good.json', 'utf8')).repositories; } + catch { rawRepos = []; } +} + +const repositories = rawRepos.map(repo => { + const repoName = normalizeRepoName(repo.name); + const manifest = fallback ? null : getManifest(repoName); + const inheritedEditorial = fallback ? { + category: repo.category, + status: repo.status, + summary: repo.description, + purpose: repo.purpose, + relationships: repo.relationships, + primary_artifact: repo.primary_artifact, + docs: repo.docs + } : {}; + const editorial = { + ...inheritedEditorial, + ...(overrides[repoName] || {}), + ...(manifest || {}) + }; + const hmmm = fallback && Array.isArray(repo.hmmm) ? [...repo.hmmm] : []; + if (!fallback && !manifest && !overrides[repoName]) { + hmmm.push('Editorial project role is inferred from public GitHub metadata until a reviewed .interdependency/project.yml is added.'); + } + if (!editorial.status && !hmmm.includes('Project maturity has not been explicitly declared.')) { + hmmm.push('Project maturity has not been explicitly declared.'); + } + return { + name: repoName, + slug: repoName.toLowerCase().replace(/[^a-z0-9]+/g, '-'), + html_url: repo.html_url || `https://github.com/${org}/${repoName}`, + description: editorial.summary || repo.description || null, + purpose: editorial.purpose || null, + status: editorial.status || 'frontier', + category: categoryFor(repo, editorial), + relationships: Array.isArray(editorial.relationships) ? editorial.relationships : [], + primary_artifact: editorial.primary_artifact || repo.homepage || null, + docs: editorial.docs || null, + archived: Boolean(repo.archived), + fork: Boolean(repo.fork), + default_branch: repo.default_branch || null, + topics: Array.isArray(repo.topics) ? repo.topics : [], + license: repo.license?.spdx_id || repo.license || null, + language: repo.language || null, + homepage: repo.homepage || null, + pushed_at: repo.pushed_at || null, + visibility: repo.visibility || 'public', + hmmm + }; +}); +const categories = [...new Set(repositories.map(repo => repo.category))].sort(); +const data = { + organization: org, + snapshotAt: new Date().toISOString(), + fallback, + publicRepoCount: repositories.length, + generatedRouteCount: repositories.length, + categories, + repositories +}; +await mkdir('src/_data/generated', { recursive: true }); +await mkdir('src/_data/snapshots', { recursive: true }); +await writeFile('src/_data/generated/repos.json', JSON.stringify(data, null, 2)); +if (!fallback) await writeFile('src/_data/snapshots/repos.last-known-good.json', JSON.stringify(data, null, 2)); +console.log(`repos ${repositories.length}${fallback ? ' fallback' : ''}`); diff --git a/scripts/parse-canon.mjs b/scripts/parse-canon.mjs index 12ad684..720325e 100644 --- a/scripts/parse-canon.mjs +++ b/scripts/parse-canon.mjs @@ -1,22 +1,93 @@ -import { readFile, writeFile, mkdir } from 'node:fs/promises'; import { createHash } from 'node:crypto'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; import slugify from 'slugify'; -const parserVersion='0.1.0'; -const raw=await readFile('src/_data/snapshots/canon.last-known-good.md','utf8'); -const text=raw.replace(/^---[\s\S]*?---\n/,''); -const contentSha256=createHash('sha256').update(text).digest('hex'); -const lines=text.split(/\r?\n/); -const units=[]; const sections=[]; const notes=[]; let current=null; let top='source'; let article=0; -function slug(s){return slugify(s,{lower:true,strict:true}) || 'unit';} -function push(end){ if(current){ current.endLine=end; current.content=current.lines.join('\n').trim(); current.hash=createHash('sha256').update(current.content).digest('hex'); units.push(current); }} -for (let i=0;iu),notes,edges:units.map(u=>({from:u.id,to:u.section,type:'unit-parent'}))}; -await mkdir('src/_data/generated',{recursive:true}); await writeFile('src/_data/generated/canon.json',JSON.stringify(data,null,2)); -console.log(`units ${units.length}`); +function boundedRouteSlug(id) { + const candidate = slug(id); + if (candidate.length <= 96) return candidate; + const suffix = createHash('sha256').update(id).digest('hex').slice(0, 10); + return `${candidate.slice(0, 84).replace(/-+$/, '')}-${suffix}`; +} +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]}]`))]; + units.push(current); +} + +for (let index = 0; index < lines.length; index += 1) { + const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(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) { + 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}`; + } + current = { + id: `${sectionId}.${localId}`, + title, + section: sectionId, + level, + startLine: index + 1, + lines: [lines[index]] + }; +} +finish(lines.length); +if (!units.length) throw new Error('canon parser produced no units'); + +const duplicateIds = units.map(unit => unit.id).filter((id, index, all) => all.indexOf(id) !== index); +if (duplicateIds.length) { + for (const duplicate of new Set(duplicateIds)) { + units.filter(unit => unit.id === duplicate).forEach((unit, index) => { unit.id = `${unit.id}-${index + 1}`; }); + } +} +for (const unit of units) unit.routeSlug = boundedRouteSlug(unit.id); +if (new Set(units.map(unit => unit.routeSlug)).size !== units.length) throw new Error('canon route slug collision'); +const notes = units.flatMap(unit => unit.notes.map(note => ({ id: `${unit.id}.note-${slug(note.marker)}`, unit_id: unit.id, ...note }))); +const data = { + source: { ...provenance, contentSha256: documentHash, parserVersion }, + sections, + units: units.map(({ lines: ignored, ...unit }) => unit), + notes, + edges: units.map(unit => ({ from: unit.id, to: unit.section, type: 'unit-parent' })) +}; +await mkdir('src/_data/generated', { recursive: true }); +await writeFile('src/_data/generated/canon.json', JSON.stringify(data, null, 2)); +console.log(`units ${units.length}; notes ${notes.length}; longest route ${Math.max(...units.map(unit => unit.routeSlug.length))}`); diff --git a/scripts/prepare-tests.mjs b/scripts/prepare-tests.mjs new file mode 100644 index 0000000..f55694a --- /dev/null +++ b/scripts/prepare-tests.mjs @@ -0,0 +1,23 @@ +import { execFileSync } from 'node:child_process'; + +// === MODULE_BUILD === +// id: offline_test_data_preparation +// purpose: Regenerate canon and project data deterministically before npm test without requiring network access or a full site build. +// entrypoint: npm test via the pretest lifecycle +// tests: tests/canon-integrity.test.mjs, tests/repo-coverage.test.mjs +// === END MODULE_BUILD === +// === BOUNDARIES === +// id: offline_test_preparation +// network: disabled by OFFLINE=1 +// storage: refreshes generated JSON from repository recovery mirrors and last-known-good snapshots +// failure: exits immediately when any preparation command fails +// === END BOUNDARIES === + +const env = { ...process.env, OFFLINE: '1', GITHUB_TOKEN: '' }; +for (const script of [ + 'scripts/fetch-canon.mjs', + 'scripts/parse-canon.mjs', + 'scripts/fetch-github-org.mjs' +]) { + execFileSync(process.execPath, [script], { env, stdio: 'inherit' }); +} diff --git a/scripts/validate-content.mjs b/scripts/validate-content.mjs index ca6f30b..8da4827 100644 --- a/scripts/validate-content.mjs +++ b/scripts/validate-content.mjs @@ -1,5 +1,20 @@ -import { 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.contentSha256 || !canon.units.length) throw new Error('canon missing hash or units'); -if(repos.publicRepoCount !== repos.generatedRouteCount) throw new Error('repo route mismatch'); -console.log(`validated ${canon.units.length} canon units and ${repos.publicRepoCount} repos`); +import { access, readFile } from 'node:fs/promises'; + +// === MODULE_BUILD === +// id: generated_content_gate +// purpose: Refuse deployment when canon identity, generated route coverage, or recovery artifacts drift. +// entrypoint: npm run validate +// tests: tests/canon-integrity.test.mjs, tests/repo-coverage.test.mjs, tests/site-contract.test.mjs +// === END MODULE_BUILD === + +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.contentSha256 || canon.source.contentSha256.length !== 64) throw new Error('canon missing SHA-256 digest'); +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'); +await access('fallback/index.html'); +await access('artifacts/four-cuts-1.html'); +console.log(`validated ${canon.units.length} canon units, ${canon.notes.length} notes, and ${repos.publicRepoCount} repositories`); diff --git a/src/_data/project-overrides.yml b/src/_data/project-overrides.yml new file mode 100644 index 0000000..3fc76de --- /dev/null +++ b/src/_data/project-overrides.yml @@ -0,0 +1,30 @@ +The-Interdependency.github.io: + category: Public doctrine & publishing + status: implemented + summary: The static-first public entry point for The Interdependent Way and The Interdependency project constellation. + purpose: Orient visitors, preserve canon provenance, host layered companion readings, and generate one public space for every organization repository. + primary_artifact: https://interdependentway.org + docs: https://github.com/The-Interdependency/The-Interdependency.github.io/tree/main/docs + relationships: + - Reads the Wayseer canon mirror while preserving the wayseer00 canonical identity. + - Generates project pages from public GitHub facts and repository manifests. +ucns: + category: Mathematics & verification + status: frontier + summary: Unit Circle Number System mathematics, constructors, tests, and theorem-status work. + purpose: Supply the carrier geometry used by EDCM-related measurement work without allowing substrate proof status to leak into measurement claims. +skill-lib: + category: Skills & tooling + status: implemented + summary: Portable agent skills and msdmd conventions used across The Interdependency repositories. + purpose: Preserve structure, usage guidance, declared contracts, and visible gaps across builds and agent workflows. +a0: + category: Agent infrastructure + status: frontier + summary: Agent-oriented runtime, orchestration, and integration workbench. + purpose: Coordinate model, tool, memory, and audit surfaces while keeping execution boundaries visible. +edcmbone: + category: Measurement & evaluation + status: frontier + summary: Energy Dissonance Circuit Model measurement and evaluation support work. + purpose: Translate constrained interaction traces into provenance-bearing measurement candidates without inheriting proof status from UCNS geometry. diff --git a/src/_includes/layouts/base.njk b/src/_includes/layouts/base.njk index 4cff36f..775d61f 100644 --- a/src/_includes/layouts/base.njk +++ b/src/_includes/layouts/base.njk @@ -1 +1,47 @@ -{{ title or site.title }}
{{ content | safe }}

AI assistance supported implementation; source material and unresolved claims remain visibly attributed. hmmm marks honest incompletion.

Canon snapshot: {{ generated.canon.source.commit }} · {{ generated.canon.source.contentSha256 }}

+ + + + + + + + {{ title or site.title }} + + + + + + + + + +
{{ content | safe }}
+
+
+ The Interdependent Way +

Canon, interpretation, research, implementation, and frontier claims are kept visibly distinct.

+
+ +
hmmmHonest incompletion remains visible so the next action has somewhere true to begin.
+
+ + diff --git a/src/artifacts/index.njk b/src/artifacts/index.njk new file mode 100644 index 0000000..c7b11a1 --- /dev/null +++ b/src/artifacts/index.njk @@ -0,0 +1,8 @@ +--- +layout: layouts/base.njk +title: Artifacts +description: Public visual studies, interactive explainers, compact publications, and experiments from The Interdependency. +--- +

Public artifacts

Things made to be used, tested, and shared

Artifacts inherit the site’s navigation, epistemic labels, accessibility floor, and fallback discipline while keeping the visual character required by their subject.

+ +

hmmm

Additional infographics, experiments, and compact publications will enter this index only after their source and status metadata are declared.

diff --git a/src/assets/css/site.css b/src/assets/css/site.css index 50e3c22..9adc07a 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -1 +1,111 @@ -:root{color-scheme:light dark;--bg:#fbfaf6;--fg:#18201f;--muted:#59625f;--card:#fffdf7;--line:#d8d0c4;--accent:#285e55;--hmmm:#704d00}@media(prefers-color-scheme:dark){:root{--bg:#101514;--fg:#f4efe4;--muted:#b8c0bb;--card:#17201e;--line:#35423f;--accent:#8bd1c3;--hmmm:#f5c66a}}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:18px/1.6 system-ui,-apple-system,Segoe UI,sans-serif}a{color:var(--accent)}.skip{position:absolute;left:-999px}.skip:focus{left:1rem;top:1rem;background:var(--card);padding:.5rem;z-index:2}.site-header{display:flex;gap:1rem;align-items:center;justify-content:space-between;padding:1rem;position:sticky;top:0;background:var(--bg);border-bottom:1px solid var(--line)}nav{display:flex;gap:.75rem;flex-wrap:wrap}.brand{font-weight:700;text-decoration:none}main{max-width:74rem;margin:auto;padding:2rem 1rem}footer{border-top:1px solid var(--line);padding:2rem 1rem;color:var(--muted)}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));gap:1rem}.card,.provenance,.hmmm,.turn{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:1rem}.hmmm{border-color:var(--hmmm)}.label{display:inline-block;border:1px solid var(--line);border-radius:999px;padding:.1rem .5rem;margin:.1rem}.source-block{white-space:pre-wrap;font-family:ui-monospace,monospace;font-size:.9rem;overflow:auto}.turn.note{border-left:6px solid var(--hmmm)}:focus{outline:3px solid var(--accent);outline-offset:3px}@media(max-width:720px){.site-header{position:static;display:block}nav{margin-top:1rem}} +:root { + color-scheme: dark; + --night: #090d18; + --night-raised: #10182a; + --night-soft: #16213a; + --starlight: #eef3ff; + --silver: #aebbd2; + --line: #2a3857; + --violet: #9b87f5; + --scarlet: #ff6b73; + --amber: #f0c36a; + --cyan: #7bc9d8; + --shadow: 0 24px 60px rgba(0, 0, 0, .28); + --radius: 1rem; + --measure: 72rem; +} +* { box-sizing: border-box; } +[hidden] { display: none !important; } +html { scroll-behavior: smooth; } +body { margin: 0; background: var(--night); color: var(--starlight); font: 17px/1.65 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } +body::before { content: ""; position: fixed; inset: 0; pointer-events: none; opacity: .22; background: radial-gradient(circle at 12% 8%, transparent 0 5rem, #27365a 5.08rem 5.14rem, transparent 5.2rem 9rem, #27365a 9.08rem 9.14rem, transparent 9.2rem), radial-gradient(circle at 88% 76%, transparent 0 10rem, #3b2f6e 10.08rem 10.14rem, transparent 10.2rem 16rem, #3b2f6e 16.08rem 16.14rem, transparent 16.2rem); z-index: -1; } +a { color: #aeb8ff; text-underline-offset: .18em; } +a:hover { color: white; } +:focus-visible { outline: 3px solid var(--amber); outline-offset: 4px; } +.skip-link { position: fixed; left: 1rem; top: -6rem; z-index: 100; padding: .7rem 1rem; background: var(--starlight); color: var(--night); border-radius: .5rem; } +.skip-link:focus { top: 1rem; } +.site-header { position: sticky; top: 0; z-index: 50; display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; padding: .9rem max(1rem, calc((100vw - var(--measure))/2)); border-bottom: 1px solid rgba(174, 187, 210, .18); background: rgba(9, 13, 24, .9); backdrop-filter: blur(18px); } +.brand { display: flex; align-items: center; gap: .8rem; color: var(--starlight); text-decoration: none; min-width: 15rem; } +.brand small { display: block; color: var(--silver); font-size: .72rem; letter-spacing: .08em; text-transform: uppercase; } +.brand-mark { position: relative; width: 2.35rem; aspect-ratio: 1; display: grid; place-items: center; } +.brand-mark i { position: absolute; border: 1px solid var(--violet); border-radius: 50%; } +.brand-mark i:nth-child(1) { inset: 0; } +.brand-mark i:nth-child(2) { inset: .34rem; border-color: var(--cyan); } +.brand-mark i:nth-child(3) { inset: .78rem; background: var(--scarlet); border: 0; } +.primary-nav { display: flex; align-items: center; justify-content: flex-end; gap: .18rem; flex-wrap: wrap; } +.primary-nav a { color: var(--silver); text-decoration: none; padding: .45rem .65rem; border-radius: .6rem; font-size: .92rem; } +.primary-nav a:hover { color: var(--starlight); background: var(--night-soft); } +.nav-toggle { display: none; border: 1px solid var(--line); background: var(--night-raised); color: var(--starlight); border-radius: .6rem; padding: .55rem .75rem; } +.noscript { margin: 0; padding: .6rem 1rem; text-align: center; color: var(--silver); background: #211b0f; border-bottom: 1px solid #5b4823; } +.site-main { max-width: var(--measure); min-height: 65vh; margin: 0 auto; padding: clamp(2rem, 5vw, 5rem) 1rem 6rem; } +.hero { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(17rem, .6fr); gap: clamp(2rem, 6vw, 6rem); align-items: center; min-height: 66vh; } +.eyebrow { color: var(--cyan); font: 700 .72rem/1.3 ui-monospace, SFMono-Regular, Consolas, monospace; text-transform: uppercase; letter-spacing: .14em; } +h1, h2, h3 { font-family: Georgia, "Times New Roman", serif; line-height: 1.12; text-wrap: balance; } +h1 { font-size: clamp(2.65rem, 7vw, 6.4rem); margin: .5rem 0 1rem; letter-spacing: -.045em; } +h2 { font-size: clamp(1.7rem, 4vw, 3rem); margin-top: 3.5rem; } +h3 { font-size: clamp(1.2rem, 2vw, 1.55rem); } +.lede { max-width: 62ch; color: var(--silver); font-size: clamp(1.05rem, 2vw, 1.3rem); } +.hero-field { position: relative; aspect-ratio: 1; display: grid; place-items: center; } +.hero-field::before, .hero-field::after, .hero-field span { content: ""; position: absolute; border: 1px solid var(--line); border-radius: 50%; } +.hero-field::before { inset: 4%; } +.hero-field::after { inset: 19%; border-color: var(--violet); } +.hero-field span { inset: 35%; border-color: var(--cyan); box-shadow: 0 0 80px rgba(155, 135, 245, .22); } +.hero-field strong { position: relative; z-index: 2; width: 7rem; aspect-ratio: 1; display: grid; place-items: center; border-radius: 50%; background: var(--scarlet); color: var(--night); font: 800 1rem/1 ui-monospace, monospace; text-align: center; } +.actions { display: flex; gap: .75rem; flex-wrap: wrap; margin-top: 1.7rem; } +.button { display: inline-flex; align-items: center; justify-content: center; min-height: 2.8rem; padding: .7rem 1rem; border: 1px solid var(--violet); border-radius: .75rem; color: var(--starlight); background: rgba(155, 135, 245, .1); text-decoration: none; font-weight: 700; } +.button.secondary { border-color: var(--line); background: transparent; color: var(--silver); } +.section-intro { max-width: 65ch; color: var(--silver); } +.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); gap: 1rem; margin: 1.4rem 0 3rem; } +.card { display: block; position: relative; padding: 1.25rem; color: inherit; text-decoration: none; background: linear-gradient(145deg, rgba(22, 33, 58, .9), rgba(16, 24, 42, .86)); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 0 0 rgba(0,0,0,0); transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease; } +.card:hover { color: inherit; transform: translateY(-3px); border-color: var(--violet); box-shadow: var(--shadow); } +.card h2, .card h3 { margin: .25rem 0 .55rem; font-size: 1.45rem; } +.card p { color: var(--silver); } +.card .eyebrow { margin-bottom: .55rem; } +.status-row { display: flex; flex-wrap: wrap; gap: .4rem; margin: .65rem 0; } +.status { display: inline-flex; align-items: center; gap: .3rem; padding: .16rem .5rem; border-radius: 999px; border: 1px solid var(--line); color: var(--silver); font: 700 .72rem/1.5 ui-monospace, monospace; text-transform: uppercase; letter-spacing: .05em; } +.status-canon { border-color: var(--cyan); color: var(--cyan); } +.status-interpretation { border-color: var(--violet); color: #c2b7ff; } +.status-research { border-color: #85d3a6; color: #9ce2b9; } +.status-implemented, .status-public { border-color: #7bc9d8; color: #9ee8f6; } +.status-frontier, .status-hmmm { border-color: var(--amber); color: var(--amber); } +.status-risk, .status-archived { border-color: var(--scarlet); color: #ff9ca2; } +.panel, .provenance, .hmmm, .reading, details { padding: 1.2rem; background: rgba(16, 24, 42, .88); border: 1px solid var(--line); border-radius: var(--radius); } +.hmmm { border-left: .35rem solid var(--amber); } +.hmmm > :first-child { margin-top: 0; color: var(--amber); } +.breadcrumb { margin-bottom: 2rem; color: var(--silver); font-size: .9rem; } +.breadcrumb a { color: var(--silver); } +.page-head { max-width: 62rem; margin-bottom: 3rem; } +.page-head h1 { font-size: clamp(2.5rem, 6vw, 5rem); } +.index-list { display: grid; gap: .7rem; padding: 0; list-style: none; } +.index-list a { display: grid; grid-template-columns: 7rem 1fr auto; gap: 1rem; align-items: baseline; padding: .9rem 1rem; border: 1px solid var(--line); border-radius: .75rem; background: rgba(16,24,42,.75); color: inherit; text-decoration: none; } +.index-list a:hover { border-color: var(--violet); } +.index-list small { color: var(--silver); } +.source-block { white-space: pre-wrap; overflow-wrap: anywhere; padding: 1rem; border: 1px solid var(--line); border-radius: .8rem; background: #070a12; color: #dce6fa; font: .92rem/1.6 ui-monospace, SFMono-Regular, Consolas, monospace; } +dl.meta { display: grid; grid-template-columns: minmax(9rem, .35fr) 1fr; gap: .45rem 1rem; } +dl.meta dt { color: var(--silver); } +dl.meta dd { margin: 0; overflow-wrap: anywhere; } +.turn { margin: 1rem 0; border-left: .3rem solid var(--violet); } +.turn.note { border-left-color: var(--amber); } +details { margin: 1rem 0; } +summary { cursor: pointer; font-weight: 800; } +.category { margin-top: 4rem; padding-top: 1rem; border-top: 1px solid var(--line); } +.artifact-frame { width: 100%; min-height: 72vh; border: 1px solid var(--line); border-radius: var(--radius); background: #111; } +.site-footer { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; padding: 3rem max(1rem, calc((100vw - var(--measure))/2)); border-top: 1px solid var(--line); background: #070a12; color: var(--silver); } +.footer-provenance { display: flex; flex-direction: column; font: .78rem/1.7 ui-monospace, monospace; overflow-wrap: anywhere; } +.hmmm-boundary { grid-column: 1 / -1; display: grid; grid-template-columns: 5rem 1fr; gap: 1rem; padding-top: 1rem; border-top: 1px solid var(--line); } +.hmmm-boundary strong { color: var(--amber); } +@media (max-width: 780px) { + .site-header { align-items: flex-start; flex-wrap: wrap; } + .nav-toggle { display: block; margin-left: auto; } + .primary-nav { width: 100%; align-items: stretch; flex-direction: column; } + .js .primary-nav { display: none; } + .js .primary-nav[data-open="true"] { display: flex; } + .primary-nav a { padding: .7rem; border: 1px solid var(--line); } + .hero { grid-template-columns: 1fr; min-height: auto; } + .hero-field { max-width: 22rem; width: 78%; margin: 1rem auto; } + .index-list a { grid-template-columns: 1fr; gap: .15rem; } + .site-footer { grid-template-columns: 1fr; } + .hmmm-boundary { grid-column: auto; grid-template-columns: 1fr; } +} +@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; } } +@media print { body { background: white; color: black; } body::before, .site-header, .actions, .noscript { display: none; } .site-main { max-width: none; padding: 0; } .card, .panel, .provenance, .hmmm, details { background: white; color: black; box-shadow: none; } a { color: black; } } diff --git a/src/assets/js/site.js b/src/assets/js/site.js new file mode 100644 index 0000000..7f79174 --- /dev/null +++ b/src/assets/js/site.js @@ -0,0 +1,19 @@ +// === MODULE_BUILD === +// id: optional_site_enhancement +// purpose: Add a compact mobile navigation toggle without hiding static content. +// entrypoint: loaded with defer from the base layout +// tests: tests/site-contract.test.mjs +// === END MODULE_BUILD === + +document.documentElement.classList.add('js'); + +const button = document.querySelector('.nav-toggle'); +const nav = document.querySelector('.primary-nav'); +if (button && nav) { + button.hidden = false; + button.addEventListener('click', () => { + const open = button.getAttribute('aria-expanded') === 'true'; + button.setAttribute('aria-expanded', String(!open)); + nav.dataset.open = String(!open); + }); +} diff --git a/src/index.njk b/src/index.njk index 037cd5a..4624780 100644 --- a/src/index.njk +++ b/src/index.njk @@ -1,14 +1,30 @@ --- layout: layouts/base.njk title: The Interdependent Way +description: A calm, layered entrance to the canon, its deliberate tensions, the research around it, and the projects attempting implementation. --- -

A calm entrance to The Interdependent Way

-

This site is being rebuilt as a static-first knowledge system: orientation first, interpretation second, exact source with provenance when intentionally requested.

-
-Explore The Way -Enter the Article Lab -Map the Projects -Study the Research Method +
+
+

Canon · interpretation · research · implementation · frontier

+

A way through complexity without pretending the tension is gone.

+

The Interdependent Way is a dense living text and a constellation of attempts to test, explain, implement, challenge, and preserve it. This site starts with orientation. Exact source remains available when intentionally sought.

+ +
+
12 + 1
-

Recent project snapshot

{{ generated.repos.publicRepoCount }} public repositories discovered; {{ generated.repos.generatedRouteCount }} project pages generated as of {{ generated.repos.snapshotAt | dateOnly }}.

-

hmmm

Research review is intentionally incomplete in this first scaffold; empty certainty would be a suspiciously tidy hat on a very alive octopus.

+
+

Choose a depth

+

One body of work, several honest entrances

+ +
+
+

Current field snapshot

+

{{ generated.repos.publicRepoCount }} public repositories · {{ generated.canon.units.length }} canonical units

+

Project data was generated {{ generated.repos.snapshotAt | dateOnly }}{% if generated.repos.fallback %} from the last verified snapshot{% endif %}. Canon digest {{ generated.canon.source.contentSha256 }}.

+
+

hmmm

The research ledger and plain-language companion readings are still intentionally incomplete. Empty certainty would be a suspiciously tidy hat on a very alive octopus.

diff --git a/src/lab/index.njk b/src/lab/index.njk index c849f1b..71684b0 100644 --- a/src/lab/index.njk +++ b/src/lab/index.njk @@ -1,5 +1,8 @@ --- layout: layouts/base.njk title: Article Lab +description: A structured conversation between canonical body text, notes, research, dissent, and unresolved evidence gaps. --- -

Article Lab

The Lab treats main text and footnotes as speakers in a deliberate conversation. Reviewed research is not yet complete, so gaps are visible.

{% for unit in generated.canon.units %}{{ unit.title }}{% endfor %}
+

Interpretation with visible seams

Article Lab

The Lab treats the body text and its annotations as speakers in a deliberate conversation. It records what the tension may prevent, what research supports or challenges the claim, and where evidence is still absent. It does not rewrite the canon or claim edcmbone measurement status.

+ +

Research coverage

The Lab route exists for every discovered canonical unit. Reviewed support, dissent, mixed evidence, and synthesis are intentionally published unit by unit rather than mass-invented.

diff --git a/src/lab/unit.njk b/src/lab/unit.njk index 8343a8a..955d635 100644 --- a/src/lab/unit.njk +++ b/src/lab/unit.njk @@ -4,7 +4,12 @@ pagination: data: generated.canon.units size: 1 alias: unit -permalink: "/lab/{{ unit.id | slug }}/" +permalink: "/lab/{{ unit.routeSlug }}/" title: "Lab: {{ unit.title }}" --- -

Lab: {{ unit.title }}

Orientation

This is a static fallback Lab page for {{ unit.id }}. It preserves source identity while marking interpretation as review-needed.

Conversation

Speaker A — main text

Canonical source excerpt is available through the exact source layer.

{% for note in generated.canon.notes %}{% if note.unit_id == unit.id %}
Speaker B — note {{ note.marker }}

Linked note relationship detected; note text mapping awaits parser review.

{% endif %}{% endfor %}

EDCM-style interpretive tension map

Open heuristic map

hmmm — no validated edcmbone metrics are run here; tension type remains certainty-vs-honest-uncertainty.

Research

No reviewed support, dissent, or mixed research is published for this unit yet. Search gap recorded as hmmm.

Exact source

Read exact source with provenance

+ +

Interpretive laboratory

{{ unit.title }}

The body text and its notes are treated as distinct speakers. This is a review surface, not a substitute canon and not an edcmbone metric runtime.

+

Conversation

{% if unit.notes.length %}{% for note in unit.notes %}
Speaker B · note {{ note.marker }}

{{ note.text }}

{% endfor %}{% else %}

No separately parsed note text

This unit may contain no numbered annotations, or its tension may be structural rather than footnoted.

{% endif %}
+

What the tension prevents

A reviewed synthesis has not yet been admitted. The Lab records the gap rather than inventing a clean reconciliation.

+

Research field

Support

Reviewed support

hmmm — no reviewed source is attached to this unit yet.

Dissent

Reviewed dissent

hmmm — no reviewed dissenting source is attached yet.

Mixed

Context and limits

hmmm — context classification awaits editorial review.

+
Open EDCM-style heuristic boundary

This page may later expose transparent text-comparison heuristics such as constraint mismatch, drift, dissonance, divergence, and turn balance. Those readings must remain labeled illustrative and may not claim edcmbone runtime status.

diff --git a/src/projects/index.njk b/src/projects/index.njk index 85a0c7f..c457490 100644 --- a/src/projects/index.njk +++ b/src/projects/index.njk @@ -1,5 +1,7 @@ --- layout: layouts/base.njk title: Projects +description: A generated map of every public repository in The Interdependency organization, grouped by function and status. --- -

Projects

Snapshot date: {{ generated.repos.snapshotAt }}. Public repositories discovered: {{ generated.repos.publicRepoCount }}. Generated pages: {{ generated.repos.generatedRouteCount }}.

{% if generated.repos.publicRepoCount != generated.repos.generatedRouteCount %}

Route count mismatch.

{% endif %}
{% for repo in generated.repos.repositories %}

{{ repo.name }}

{{ repo.description or 'No GitHub description supplied.' }}

{% if repo.archived %}archived{% endif %}{% if repo.fork %}fork{% endif %}
{% endfor %}
+

Build-time organization map

Projects

Every public repository receives a page. GitHub facts update automatically at build time; reviewed manifests supply purpose, maturity, relations, and primary artifacts. Missing editorial knowledge remains visible as hmmm.

{{ generated.repos.publicRepoCount }} public repos{% if generated.repos.fallback %}last-known-good snapshot{% endif %}
+{% for category in generated.repos.categories %}

Project constellation

{{ category }}

{% for repo in generated.repos.repositories %}{% if repo.category == category %}
{{ repo.status }}{% if repo.archived %}archived{% endif %}

{{ repo.name }}

{{ repo.description or 'No public summary supplied.' }}

{% endif %}{% endfor %}
{% endfor %} diff --git a/src/projects/repo.njk b/src/projects/repo.njk index 590aed9..59081e3 100644 --- a/src/projects/repo.njk +++ b/src/projects/repo.njk @@ -7,4 +7,10 @@ pagination: permalink: "/projects/{{ repo.slug }}/" title: "Project: {{ repo.name }}" --- -

{{ repo.name }}

{% if repo.archived %}archived{% endif %}{% if repo.fork %}fork{% endif %}{{ repo.visibility }}

{{ repo.description or 'No GitHub description supplied.' }}

Editorial project map

hmmm — this repository has not yet declared its organization-level role in structured metadata.

Default branch
{{ repo.default_branch or 'hmmm' }}
Primary language
{{ repo.language or 'hmmm' }}
License
{{ repo.license or 'hmmm' }}
Last push
{{ repo.pushed_at or 'hmmm' }}
Snapshot
{{ generated.repos.snapshotAt }}

Open on GitHub

+ +

{{ repo.category }}

{{ repo.name }}

{{ repo.status }}{% if repo.archived %}archived{% endif %}{% if repo.fork %}fork{% endif %}

{{ repo.description or 'No public summary supplied.' }}

+{% if repo.purpose %}

Purpose within the whole

{{ repo.purpose }}

{% else %}

Purpose within the whole

A reviewed project purpose has not yet been declared.

{% endif %} +{% if repo.relationships.length %}

Relationships

    {% for relation in repo.relationships %}
  • {{ relation }}
  • {% endfor %}
{% endif %} +

Public surfaces

Primary artifact
{% if repo.primary_artifact %}{{ repo.primary_artifact }}{% else %}hmmm{% endif %}
Documentation
{% if repo.docs %}{{ repo.docs }}{% else %}hmmm{% endif %}
Default branch
{{ repo.default_branch or 'hmmm' }}
Primary language
{{ repo.language or 'hmmm' }}
License
{{ repo.license or 'hmmm' }}
Last push
{{ repo.pushed_at or 'hmmm' }}
+{% for item in repo.hmmm %}

hmmm

{{ item }}

{% endfor %} +
Open repository{% if repo.homepage %}Open homepage{% endif %}
diff --git a/src/research/method.njk b/src/research/method.njk index 6a571fb..a0e481f 100644 --- a/src/research/method.njk +++ b/src/research/method.njk @@ -1,5 +1,9 @@ --- layout: layouts/base.njk -title: Research Method +title: Research method +description: How The Interdependent Way site distinguishes support, dissent, mixed evidence, implementation, and unresolved research gaps. --- -

Research method

Research records are version-controlled and reviewed. Metadata services may enrich records, but they do not decide support, dissent, or mixed stance.

False-balance rule

When no qualifying source exists, the page must say so rather than manufacturing symmetry.

hmmm — no reviewed research corpus is included in this scaffold.

+

Research method

Evidence is not a decorative citation layer.

Sources are attached to specific claims and units. A metadata service may help locate or normalize a source, but it does not decide whether the source supports, dissents from, limits, or merely contextualizes a claim.

+

Classification rules

Support

Substantive agreement

The source supplies evidence or reasoning that materially strengthens the identified claim.

Dissent

Substantive challenge

The source disputes a premise, mechanism, prediction, interpretation, or consequence.

Mixed

Conditional or partial result

The source supports one part while limiting another, or shows the claim depends on context.

Gap

No qualifying source found

The site says so directly. It does not manufacture false balance or cite a weak source to fill visual symmetry.

+

Publication requirements

  1. Identify the exact canonical unit or project claim.
  2. Record source title, authorship, date, stable identifier, and retrieval date.
  3. Separate direct findings from editorial inference.
  4. Label stance through review, not keyword sentiment.
  5. Preserve contradictory high-quality evidence.
  6. Mark retractions, corrections, and stale data.
  7. Keep implementation evidence separate from theoretical validity.
+

hmmm

The version-controlled research ledger exists as a schema boundary, but the reviewed corpus is not yet broad enough to imply comprehensive coverage.

diff --git a/src/source/index.njk b/src/source/index.njk index c077a1f..c162c8c 100644 --- a/src/source/index.njk +++ b/src/source/index.njk @@ -2,4 +2,4 @@ layout: layouts/base.njk title: Source index --- -

Source index

Direct source permalinks are available for citation and accessibility. Ordinary homepage navigation introduces orientation before this layer.

+

Exact source index

Source index

Direct source permalinks remain available for citation and accessibility. Ordinary homepage navigation introduces orientation before this layer.

diff --git a/src/source/unit.njk b/src/source/unit.njk index 7200ebf..a35372d 100644 --- a/src/source/unit.njk +++ b/src/source/unit.njk @@ -4,7 +4,10 @@ pagination: data: generated.canon.units size: 1 alias: unit -permalink: "/source/{{ unit.id | slug }}/" +permalink: "/source/{{ unit.routeSlug }}/" title: "Source: {{ unit.title }}" --- -

Exact source: {{ unit.title }}

Canonical unit ID
{{ unit.id }}
Repository
{{ generated.canon.source.repository }}
Path
{{ generated.canon.source.path }}
Commit
{{ generated.canon.source.commit }}
Unit hash
{{ unit.hash }}
Document hash
{{ generated.canon.source.contentSha256 }}
{{ unit.content }}
+ +

Exact source · deliberate depth

{{ unit.title }}

Verbatim source content and machine-verifiable provenance. Commentary elsewhere on this site is subordinate to this layer.

+
Canonical repository alias
{{ generated.canon.source.repository }}
Path
{{ generated.canon.source.path }}
Branch
{{ generated.canon.source.branch }}
Retrieved
{{ generated.canon.source.retrievedAt }}
Recovery mirror used
{{ generated.canon.source.fallback }}
Canonical unit ID
{{ unit.id }}
Stable route
{{ unit.routeSlug }}
Source lines
{{ unit.startLine }}–{{ unit.endLine }}
Unit digest
{{ unit.hash }}
Document digest
{{ generated.canon.source.contentSha256 }}
+
{{ unit.content }}
diff --git a/src/start.njk b/src/start.njk index 1d0c068..c1a5388 100644 --- a/src/start.njk +++ b/src/start.njk @@ -1,6 +1,10 @@ --- layout: layouts/base.njk -title: Start +title: Start here permalink: /start/ +description: Orientation to The Interdependent Way, its layered source model, project constellation, and research boundaries. --- -

Start

Begin with orientation, then choose a companion page, Lab page, project map, or research method.

Explore The Way

+

Orientation before density

Start here

The Interdependent Way is simultaneously a canonical text, an interpretive problem, a research program, a public project constellation, and a set of frontier claims. Those layers are related, but they are not interchangeable.

+

How to read this site

Canon

What the source says

Exact text and mechanically derived structure. Canon wins whenever a companion reading disagrees.

Interpretation

What a reading proposes

Orientation, plain-language companions, and tension analysis. Useful scaffolding, always subordinate to source.

Research

What evidence bears on it

Support, dissent, mixed results, context, and evidence gaps are classified by editorial review rather than automated sentiment.

Implementation

What has been built

Repositories and artifacts that attempt to embody, test, measure, publish, or challenge part of the work.

Frontier

What remains unsettled

Experimental or incomplete claims remain named without borrowing certainty from neighboring formal work.

hmmm

What is honestly unresolved

A boundary object that records the missing constraint and preserves a place for continuation.

+

A suggested first path

  1. Open The Way and choose one section.
  2. Read its companion page to locate the unit.
  3. Enter the unit’s Article Lab to see body text and notes treated as separate speakers.
  4. Open exact source when you are ready to verify wording and provenance.
  5. Use Projects to see what has actually been implemented.
+ diff --git a/src/way/index.njk b/src/way/index.njk index d5e5233..720c52d 100644 --- a/src/way/index.njk +++ b/src/way/index.njk @@ -1,5 +1,9 @@ --- layout: layouts/base.njk title: Explore The Way +description: A sectioned companion map of The Interdependent Way, with exact source available one deliberate layer deeper. --- -

Explore The Way

Every discovered canonical unit receives a companion entry. Exact source is one intentional step deeper.

{% for unit in generated.canon.units %}{% endfor %}
+

Canon-derived map

Explore The Way

This layer identifies the shape and relations of the source. It does not replace the canon. Open a unit for orientation, then choose its Lab conversation or exact source.

+{% for section in generated.canon.sections %} +

{{ section.id }}

{{ section.title }}

+{% endfor %} diff --git a/src/way/unit.njk b/src/way/unit.njk index dc0cfa4..12fef2b 100644 --- a/src/way/unit.njk +++ b/src/way/unit.njk @@ -4,7 +4,11 @@ pagination: data: generated.canon.units size: 1 alias: unit -permalink: "/way/{{ unit.id | slug }}/" +permalink: "/way/{{ unit.routeSlug }}/" title: "{{ unit.title }}" --- -

{{ unit.title }}

{{ unit.id }}

This companion page identifies the unit's place in the document and offers a cautious reading without replacing the canonical text.

What this section is doing

hmmm — interpretive summaries for this unit require review before they should sound confident.

Enter the Lab for this unit

Read the exact source

+ +

Canon-derived companion · {{ unit.section }}

{{ unit.title }}

canon-derivedorientation pending review

This page preserves the unit’s identity and location while offering paths into interpretation and exact source. It does not rewrite the canonical text.

+

Place in the document

Unit ID
{{ unit.id }}
Stable route
{{ unit.routeSlug }}
Source lines
{{ unit.startLine }}–{{ unit.endLine }}
Detected notes
{{ unit.notes.length }}
Unit digest
{{ unit.hash }}
+

Companion reading

A reviewed plain-language reading has not yet been admitted for this unit. The source operators, conditions, exceptions, and obligations must remain intact before this field is populated.

+ diff --git a/tests/canon-integrity.test.mjs b/tests/canon-integrity.test.mjs index 078c682..4f1b2cd 100644 --- a/tests/canon-integrity.test.mjs +++ b/tests/canon-integrity.test.mjs @@ -1,2 +1,22 @@ -import test from 'node:test';import assert from 'node:assert/strict';import { readFile } from 'node:fs/promises'; -test('canon data has provenance and units', async()=>{const c=JSON.parse(await readFile('src/_data/generated/canon.json','utf8'));assert.ok(c.source.contentSha256);assert.ok(c.units.length>0);for(const u of c.units) assert.ok(u.hash);}); +import test from 'node:test'; +import assert from 'node:assert/strict'; +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.match(canon.source.contentSha256, /^[a-f0-9]{64}$/); + assert.ok(canon.units.length > 0); + const routes = new Set(); + for (const unit of canon.units) { + assert.ok(unit.id); + assert.match(unit.hash, /^[a-f0-9]{64}$/); + assert.ok(unit.startLine <= unit.endLine); + assert.ok(Array.isArray(unit.notes)); + assert.ok(unit.routeSlug); + assert.ok(unit.routeSlug.length <= 96); + assert.equal(routes.has(unit.routeSlug), false); + routes.add(unit.routeSlug); + } +}); diff --git a/tests/generated-site.test.mjs b/tests/generated-site.test.mjs new file mode 100644 index 0000000..c16a702 --- /dev/null +++ b/tests/generated-site.test.mjs @@ -0,0 +1,18 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; + +// Usage: run only after Eleventy has generated _site, normally through npm run test:generated or npm run check. +test('generated deployment artifact contains the unified routes', async () => { + const [home, artifacts, fourCuts, fallback] = await Promise.all([ + readFile('_site/index.html', 'utf8'), + readFile('_site/artifacts/index.html', 'utf8'), + readFile('_site/artifacts/four-cuts/index.html', 'utf8'), + readFile('_site/fallback/index.html', 'utf8') + ]); + + assert.match(home, /A way through complexity/); + assert.match(artifacts, /Four Cuts of the Same Country/); + assert.match(fourCuts, /Wealth and tax/); + assert.match(fallback, /Emergency static edition/); +}); diff --git a/tests/offline-project-snapshot.test.mjs b/tests/offline-project-snapshot.test.mjs new file mode 100644 index 0000000..feb8f6f --- /dev/null +++ b/tests/offline-project-snapshot.test.mjs @@ -0,0 +1,63 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const scriptPath = join(repositoryRoot, 'scripts', 'fetch-github-org.mjs'); + +// Usage: this runs the real refresh script in an isolated temporary working tree with OFFLINE=1. +test('offline refresh preserves reviewed last-known-good editorial fields', async () => { + const root = await mkdtemp(join(tmpdir(), 'interdependency-project-snapshot-')); + try { + await mkdir(join(root, 'src', '_data', 'snapshots'), { recursive: true }); + await writeFile(join(root, 'src', '_data', 'project-overrides.yml'), '{}\n'); + await writeFile( + join(root, 'src', '_data', 'snapshots', 'repos.last-known-good.json'), + JSON.stringify({ + repositories: [{ + name: 'reviewed-project', + slug: 'reviewed-project', + html_url: 'https://github.com/The-Interdependency/reviewed-project', + description: 'Reviewed summary', + purpose: 'Reviewed purpose', + status: 'implemented', + category: 'Mathematics & verification', + relationships: ['Depends on verified geometry.'], + primary_artifact: 'https://example.org/artifact', + docs: 'https://example.org/docs', + default_branch: 'main', + topics: ['verification'], + language: 'JavaScript', + homepage: 'https://example.org', + visibility: 'public', + hmmm: ['A reviewed unresolved remains visible.'] + }] + }, null, 2) + ); + + await execFileAsync(process.execPath, [scriptPath], { + cwd: root, + env: { ...process.env, OFFLINE: '1', GITHUB_TOKEN: '' } + }); + + const generated = JSON.parse(await readFile(join(root, 'src', '_data', 'generated', 'repos.json'), 'utf8')); + const [repo] = generated.repositories; + assert.equal(generated.fallback, true); + assert.equal(repo.description, 'Reviewed summary'); + assert.equal(repo.purpose, 'Reviewed purpose'); + assert.equal(repo.status, 'implemented'); + assert.equal(repo.category, 'Mathematics & verification'); + assert.deepEqual(repo.relationships, ['Depends on verified geometry.']); + assert.equal(repo.primary_artifact, 'https://example.org/artifact'); + assert.equal(repo.docs, 'https://example.org/docs'); + assert.deepEqual(repo.hmmm, ['A reviewed unresolved remains visible.']); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/tests/site-contract.test.mjs b/tests/site-contract.test.mjs new file mode 100644 index 0000000..ab12bfe --- /dev/null +++ b/tests/site-contract.test.mjs @@ -0,0 +1,23 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; + +test('one static-first build owns public routes', async () => { + const config = await readFile('.eleventy.js', 'utf8'); + assert.match(config, /artifacts\/four-cuts-1\.html.*artifacts\/four-cuts\/index\.html/s); + assert.match(config, /fallback/); +}); + +test('base layout remains readable without javascript', async () => { + const layout = await readFile('src/_includes/layouts/base.njk', 'utf8'); + assert.match(layout, /