diff --git a/.eleventy.js b/.eleventy.js index 2fcb4d2..f019fb1 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -14,6 +14,12 @@ import { installMathRenderer } from './scripts/markdown-math.mjs'; // then: the same source bytes are copied to the public data route without runtime fetching or schema shadowing // class: evidence // since: 2026-08-03 +// +// id: website_sitrep_has_one_public_projection_copy +// given: repo-owned plan reports are validated and projected through the pinned skill-lib work-graph instrument +// then: the website publishes the generated presentation view without becoming a second source of repo canon +// class: evidence +// since: 2026-08-14 // === END CONTRACTS === // Usage: run `npm run build`; Eleventy emits the static site, copies root machine instructions, and preserves dependency-free public reading paths. @@ -23,6 +29,7 @@ export default function configureEleventy(eleventyConfig) { eleventyConfig.addPassthroughCopy({ 'src/assets': 'assets', 'src/_data/gonol_relationship_display.json': 'assets/data/gonol-relationship-display-v1.json', + 'src/_data/generated/sitrep.json': 'assets/data/sitrep.json', 'CNAME': 'CNAME', 'llms.txt': 'llms.txt', 'artifacts/four-cuts-1.html': 'artifacts/four-cuts/index.html', diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index d3f1349..9e3eb9b 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -7,7 +7,7 @@ on: repository_dispatch: types: [org-repo-updated] schedule: - - cron: '24 5 * * *' + - cron: '24 * * * *' permissions: contents: read diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json new file mode 100644 index 0000000..3b177ae --- /dev/null +++ b/docs/work-graphs/repository-plan-report.json @@ -0,0 +1,157 @@ +{ + "schema": "the-interdependency.repository-plan-report", + "version": "1.0.0", + "repository": "The-Interdependency/The-Interdependency.github.io", + "contract": { + "repository": "The-Interdependency/skill-lib", + "path": "interdependent-work-graph/repository-plan-report.schema.json", + "version": "1.0.0", + "blob_sha": "9b347b2dff7692054b571602f30ee6d00c2e7265" + }, + "source": { + "branch": "feature/dynamic-repo-sitrep", + "commit": "8f41144653fd084e4f0ef433367ee01faee9d29e", + "generated_at": "2026-08-14", + "note": "This report describes the SITREP implementation commit immediately beneath the first website self-report. The report commit is coordination metadata and does not acquire semantic, mathematical, measurement, runtime, empirical, or publication authority from the repositories it presents." + }, + "authority": { + "owns": [ + "public website presentation, navigation, static build behavior, and publication surfaces for interdependentway.org", + "website-local data fetching, caching, fallback behavior, accessibility, and presentation contracts", + "public rendering of repository-owned reports and GitHub telemetry without altering their source authority", + "the website-specific SITREP presentation view and its machine-readable public copy" + ], + "does_not_own": [ + "METAPAT semantic canon or ontology validity", + "UCNS mathematical representation, geometry, or proof status", + "EDCM measurement validity, empirical results, or experiment authority", + "a0 runtime, agent identity, memory, privacy, or deployment authority", + "skill-lib reusable skill doctrine, repository-plan-report contract, or portfolio projection semantics" + ], + "non_transfer": [ + "publication on this website does not transfer producer-repository authority to the website", + "GitHub workflow success does not establish semantic, mathematical, measurement, empirical, or runtime validity", + "the SITREP presentation view does not become a second source of repository canon", + "a stale or missing repository report remains visibly incomplete rather than being reconstructed from telemetry or memory", + "skill-lib validation and deterministic projection constrain coordination representation but do not decide substantive project truth" + ] + }, + "portfolio_role": { + "summary": "Provide the public presentation and observability surface for repo-owned situation reports: consume the exact skill-lib work-graph contract and projection, overlay separately observed GitHub activity, and make current relations, blockers, next actions, and unresolved boundaries legible without centralizing repository canon.", + "upstream_contract": "Participating repositories own the claims in their repository-plan-report.json files; skill-lib owns the frozen report contract and deterministic projection instrument.", + "downstream_contract": "The website exposes human-readable infograms and a machine-readable generated SITREP view while preserving source identities, fallback state, and authority-transfer boundaries.", + "reports_to": { + "repository": "The-Interdependency/skill-lib", + "skill": "interdependent-work-graph", + "relation": "repo-owned publication and observability status input; no authority transfer" + } + }, + "status": { + "state": "implemented static public knowledge system with dynamic repository SITREP pending branch validation and deployment", + "current_claim": "The website now contains a build-time SITREP path for METAPAT, UCNS, EDCM, a0, and itself that executes the exact pinned skill-lib portfolio projection, preserves missing reports and fallback explicitly, and renders GitHub telemetry separately from repo-declared situation; public deployment evidence remains pending merge.", + "latest_main_milestone": { + "commit": "4d4da2436ae556e5274cf5cb98f601e34bb793d3", + "description": "current main before the dynamic SITREP branch: static-first public knowledge system with provenance subordinated to human reading" + } + }, + "delivered": [ + { + "surface": "static-first public knowledge system", + "status": "implemented and deployed on current main", + "boundary": "publication and rendering do not establish the validity of producer-repository claims" + }, + { + "surface": "generated organization project map", + "status": "implemented build-time GitHub observation and reviewed project-manifest presentation", + "boundary": "project discovery and GitHub metadata do not constitute portfolio membership or repository-plan status" + }, + { + "surface": "skill-lib-governed repository SITREP collector", + "status": "implemented on feature branch pending executable CI evidence", + "boundary": "collector executes a commit- and blob-pinned skill-lib portfolio_plan.py rather than reimplementing its projection semantics" + }, + { + "surface": "repository SITREP infogram page", + "status": "implemented on feature branch with declared-versus-observed separation and explicit fallback", + "boundary": "presentation does not reconcile report/HEAD mismatches or upgrade source claims" + }, + { + "surface": "machine-readable public SITREP view", + "status": "configured for build-time publication at assets/data/sitrep.json", + "boundary": "generated JSON is a presentation projection and not a replacement for repo-owned reports" + } + ], + "active_frontier": [ + "validate the SITREP collector, skill-lib projection execution, Eleventy rendering, links, and accessibility in CI", + "merge and deploy the SITREP page only after existing website release gates pass", + "refresh the website repo report after merge while preserving the report-refresh commit as coordination metadata", + "decide whether producer repositories should emit repository_dispatch events for near-immediate refresh in addition to the hourly fallback schedule", + "decide which additional repositories, if any, intentionally join the public SITREP portfolio after the initial five-surface scope" + ], + "next_actions": [ + { + "action": "run the full pull-request validation suite against the dynamic SITREP branch", + "owner": "The-Interdependency/The-Interdependency.github.io", + "dependency": "GitHub Actions runner and public GitHub API access" + }, + { + "action": "merge and deploy the SITREP only if build, unit, generated-site, browser, accessibility, and link gates remain green", + "owner": "The-Interdependency/The-Interdependency.github.io", + "dependency": "successful pull-request validation" + }, + { + "action": "add producer-side org-repo-updated dispatch only where repository permissions and failure behavior can be kept explicit", + "owner": "participating repositories", + "dependency": "post-deployment decision; hourly scheduled refresh already provides bounded eventual update" + } + ], + "blocked": [ + "public SITREP deployment is blocked on branch validation and merge" + ], + "cross_repository_relations": [ + { + "repository": "The-Interdependency/skill-lib", + "relation": "report-contract and deterministic work-graph projection authority consumed by the website", + "authority_transfer": false + }, + { + "repository": "The-Interdependency/metapat", + "relation": "repo-owned semantic situation report presented without semantic authority transfer", + "authority_transfer": false + }, + { + "repository": "The-Interdependency/ucns", + "relation": "repo-owned mathematical and representation situation report presented without proof-status transfer", + "authority_transfer": false, + "proof_status_transfer": false + }, + { + "repository": "The-Interdependency/edcm", + "relation": "repo-owned measurement situation report presented without measurement-status transfer", + "authority_transfer": false, + "measurement_status_transfer": false + }, + { + "repository": "The-Interdependency/a0", + "relation": "repo-owned runtime situation report presented without runtime or deployment authority transfer", + "authority_transfer": false + } + ], + "machine_entrypoints": { + "repo_report": "docs/work-graphs/repository-plan-report.json", + "sitrep_collector": "scripts/fetch-sitrep.mjs", + "sitrep_page": "src/sitrep/index.njk", + "sitrep_styles": "src/assets/css/sitrep.css", + "public_sitrep_data": "assets/data/sitrep.json", + "project_manifest": ".interdependency/project.yml", + "site_configuration": ".eleventy.js", + "release_workflow": ".github/workflows/pages.yml" + }, + "hmmm": [ + "producer-side repository_dispatch coverage for near-immediate SITREP rebuilds", + "whether the public portfolio remains exactly METAPAT, UCNS, EDCM, a0, and website or later admits additional intentional members", + "long-term historical SITREP snapshot retention and public timeline policy", + "cryptographic producer authentication beyond GitHub transport and content identities", + "whether report freshness should later distinguish substantive repository change from report-only coordination commits" + ] +} diff --git a/package.json b/package.json index 75f1e78..5b3775b 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,10 @@ "type": "module", "scripts": { "dev": "npm run refresh:data && eleventy --serve", - "refresh:data": "npm run refresh:canon && npm run refresh:github && npm run refresh:textbook && npm run refresh:works", + "refresh:data": "npm run refresh:canon && npm run refresh:github && npm run refresh:sitrep && npm run refresh:textbook && npm run refresh:works", "refresh:canon": "node scripts/fetch-canon.mjs && node scripts/parse-canon.mjs", "refresh:github": "node scripts/fetch-github-org.mjs", + "refresh:sitrep": "node scripts/fetch-sitrep.mjs", "refresh:textbook": "node scripts/fetch-textbook.mjs", "research:enrich": "node scripts/enrich-citations.mjs", "audit:workflows": "node scripts/audit-workflows.mjs", @@ -19,7 +20,7 @@ "validate": "node scripts/validate-content.mjs && node scripts/verify-generated-routes.mjs && node scripts/verify-article-canon.mjs", "build": "npm run validate && eleventy && pagefind --site _site && node scripts/write-build-info.mjs", "pretest": "node scripts/prepare-tests.mjs", - "test": "node --test tests/aicontext.test.mjs tests/edcm-mathematics.test.mjs tests/gonol-relationships.test.mjs tests/post-merge-reconciliation.test.mjs tests/llms-build.test.mjs tests/canon-parser.test.mjs tests/canon-integrity.test.mjs tests/textbook-integrity.test.mjs tests/math-rendering.test.mjs tests/narratives.test.mjs tests/offline-project-snapshot.test.mjs tests/repo-coverage.test.mjs tests/research-ledger.test.mjs tests/works-registry.test.mjs tests/site-contract.test.mjs", + "test": "node --test tests/aicontext.test.mjs tests/edcm-mathematics.test.mjs tests/gonol-relationships.test.mjs tests/post-merge-reconciliation.test.mjs tests/llms-build.test.mjs tests/canon-parser.test.mjs tests/canon-integrity.test.mjs tests/textbook-integrity.test.mjs tests/math-rendering.test.mjs tests/narratives.test.mjs tests/offline-project-snapshot.test.mjs tests/repo-coverage.test.mjs tests/research-ledger.test.mjs tests/works-registry.test.mjs tests/site-contract.test.mjs tests/sitrep.test.mjs", "test:generated": "node --test tests/generated-site.test.mjs tests/human-ui-generated.test.mjs tests/textbook-generated.test.mjs tests/math-generated.test.mjs && node tests/links.test.mjs", "test:browser": "playwright test", "test:e2e": "playwright test tests/site.spec.mjs", diff --git a/scripts/fetch-sitrep.mjs b/scripts/fetch-sitrep.mjs new file mode 100644 index 0000000..2a91f33 --- /dev/null +++ b/scripts/fetch-sitrep.mjs @@ -0,0 +1,293 @@ +import { execFileSync } from 'node:child_process'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +// === MODULE_BUILD === +// id: repository_sitrep_projection +// purpose: Build the public repository SITREP from repo-owned plan reports through the exact skill-lib portfolio projection, then add separate GitHub telemetry. +// entrypoint: npm run refresh:sitrep +// tests: tests/sitrep.test.mjs +// === END MODULE_BUILD === +// === BOUNDARIES === +// id: sitrep_authority_boundary +// network: reads public GitHub repository metadata and repo-owned reports; optional token raises rate limits +// storage: writes generated and last-known-good SITREP JSON only +// authority: skill-lib owns the reporting contract and deterministic projection; each repository owns its report claims; this site owns presentation only +// failure: never reconstructs missing reports; never publishes raw command errors or credentials; missing or HEAD-different sources remain visible and a last-known-good projection may be used only with fallback=true +// === END BOUNDARIES === +// Usage: run `npm run refresh:sitrep`; the output is presentation data, not a new source of repository canon. + +const org = 'The-Interdependency'; +const githubApiOrigin = 'https://api.github.com'; +const websiteRepository = `${org}/The-Interdependency.github.io`; +const localReportPath = 'docs/work-graphs/repository-plan-report.json'; + +// Exact control-plane identity. The report schema is already frozen by blob SHA; +// the portfolio script is additionally pinned so the website cannot silently +// reinterpret repo reports when skill-lib main changes. +const controlPlane = { + repository: `${org}/skill-lib`, + commit: '6ef2e4c123225f9db20e5230e5894c9c86b42ee6', + skill: 'interdependent-work-graph', + reportSchemaVersion: '1.0.0', + reportSchemaPath: 'interdependent-work-graph/repository-plan-report.schema.json', + reportSchemaBlob: '9b347b2dff7692054b571602f30ee6d00c2e7265', + portfolioScriptPath: 'interdependent-work-graph/portfolio_plan.py', + portfolioScriptBlob: '97b8b546b4151486164c8a4b730c24a8c895b25b' +}; + +const projectDefinitions = [ + { repository: `${org}/metapat`, name: 'metapat', label: 'METAPAT', slug: 'metapat' }, + { repository: `${org}/ucns`, name: 'ucns', label: 'UCNS', slug: 'ucns' }, + { repository: `${org}/edcm`, name: 'edcm', label: 'EDCM', slug: 'edcm' }, + { repository: `${org}/a0`, name: 'a0', label: 'a0', slug: 'a0' }, + { repository: websiteRepository, name: 'The-Interdependency.github.io', label: 'Website', slug: 'website', localReport: true } +]; + +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; +} + +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 tryGetJson(target) { + try { return getJson(target); } catch { return null; } +} + +function publicFailureReason(error, scope = 'refresh') { + const message = String(error?.message || error || ''); + if (message.includes('offline requested')) return 'offline refresh requested'; + if (message.includes('skill-lib report schema drift')) return 'skill-lib report schema identity mismatch'; + if (message.includes('skill-lib portfolio script drift')) return 'skill-lib portfolio projection identity mismatch'; + if (message.includes('no repository plan reports could be collected')) return 'repository plan reports unavailable'; + if (message.includes('repo report identifies') || message.includes('expected base64 GitHub contents response')) return `${scope} report invalid or unavailable`; + if (message.includes('Python interpreter') || message.includes('portfolio projection')) return 'skill-lib portfolio projection execution failed'; + return `${scope} unavailable`; +} + +function decodeContent(response, label) { + if (!response?.content || response.encoding !== 'base64') { + throw new Error(`${label}: expected base64 GitHub contents response`); + } + return Buffer.from(response.content, 'base64').toString('utf8'); +} + +function runPortfolio(scriptPath, reportPaths, outputPath) { + let lastError = null; + for (const executable of ['python3', 'python']) { + try { + execFileSync(executable, [scriptPath, ...reportPaths, '--output', outputPath], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'] + }); + return executable; + } catch (error) { + lastError = error; + if (error?.code !== 'ENOENT') break; + } + } + throw lastError || new Error('no Python interpreter available for skill-lib portfolio projection'); +} + +async function collectReport(definition, workDir) { + let text; + let reportBlob = null; + if (definition.localReport) { + text = await readFile(localReportPath, 'utf8'); + } else { + const response = getJson(githubApiUrl( + `/repos/${encodeURIComponent(org)}/${encodeURIComponent(definition.name)}/contents/docs/work-graphs/repository-plan-report.json`, + { ref: 'main' } + )); + reportBlob = response.sha || null; + text = decodeContent(response, definition.repository); + } + const report = JSON.parse(text); + if (report.repository !== definition.repository) { + throw new Error(`${definition.repository}: repo report identifies ${report.repository || 'hmmm'}`); + } + const reportPath = path.join(workDir, `${definition.slug}.repository-plan-report.json`); + await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`); + return { report, reportPath, reportBlob }; +} + +function collectTelemetry(definition) { + const repo = getJson(githubApiUrl(`/repos/${encodeURIComponent(org)}/${encodeURIComponent(definition.name)}`)); + const branch = repo.default_branch || 'main'; + const commit = getJson(githubApiUrl(`/repos/${encodeURIComponent(org)}/${encodeURIComponent(definition.name)}/commits/${encodeURIComponent(branch)}`)); + const pulls = tryGetJson(githubApiUrl(`/repos/${encodeURIComponent(org)}/${encodeURIComponent(definition.name)}/pulls`, { + state: 'open', + per_page: 100 + })) || []; + const runResponse = tryGetJson(githubApiUrl(`/repos/${encodeURIComponent(org)}/${encodeURIComponent(definition.name)}/actions/runs`, { + branch, + per_page: 1 + })); + const latestRun = runResponse?.workflow_runs?.[0] || null; + return { + branch, + head: commit.sha || null, + headDate: commit.commit?.committer?.date || commit.commit?.author?.date || null, + pushedAt: repo.pushed_at || null, + updatedAt: repo.updated_at || null, + openPullRequests: Array.isArray(pulls) ? pulls.length : null, + latestWorkflow: latestRun ? { + name: latestRun.name || null, + status: latestRun.status || null, + conclusion: latestRun.conclusion || null, + event: latestRun.event || null, + headSha: latestRun.head_sha || null, + updatedAt: latestRun.updated_at || null, + url: latestRun.html_url || null + } : null + }; +} + +function projectView(definition, reportRecord, telemetry, portfolioView) { + const sourceCommit = reportRecord?.report?.source?.commit || null; + const head = telemetry?.head || null; + let reportFreshness = 'unknown'; + if (sourceCommit && head) reportFreshness = sourceCommit === head ? 'current' : 'HEAD differs'; + else if (!reportRecord) reportFreshness = 'missing'; + return { + ...definition, + report: reportRecord?.report || null, + reportBlob: reportRecord?.reportBlob || null, + portfolio: portfolioView || null, + telemetry: telemetry || null, + reportFreshness + }; +} + +async function writeGenerated(data, persistSnapshot = false) { + await mkdir('src/_data/generated', { recursive: true }); + await mkdir('src/_data/snapshots', { recursive: true }); + await writeFile('src/_data/generated/sitrep.json', `${JSON.stringify(data, null, 2)}\n`); + if (persistSnapshot) { + await writeFile('src/_data/snapshots/sitrep.last-known-good.json', `${JSON.stringify(data, null, 2)}\n`); + } +} + +async function fallbackData(reason) { + try { + const previous = JSON.parse(await readFile('src/_data/snapshots/sitrep.last-known-good.json', 'utf8')); + return { + ...previous, + snapshotAt: new Date().toISOString(), + fallback: true, + fallbackReason: reason, + sourceSnapshotAt: previous.snapshotAt || null + }; + } catch { + return { + schema: 'the-interdependency.website-sitrep-view', + version: '1.0.0', + snapshotAt: new Date().toISOString(), + fallback: true, + fallbackReason: reason, + sourceSnapshotAt: null, + controlPlane, + portfolio: null, + missingReports: projectDefinitions.map(project => project.repository), + projects: projectDefinitions.map(project => ({ + ...project, + report: null, + portfolio: null, + telemetry: null, + reportFreshness: 'unknown' + })) + }; + } +} + +let workDir = null; +try { + if (process.env.OFFLINE === '1') throw new Error('offline requested'); + workDir = await mkdtemp(path.join(tmpdir(), 'interdependency-sitrep-')); + + const schemaResponse = getJson(githubApiUrl( + `/repos/${encodeURIComponent(org)}/skill-lib/contents/${controlPlane.reportSchemaPath}`, + { ref: controlPlane.commit } + )); + if (schemaResponse.sha !== controlPlane.reportSchemaBlob) { + throw new Error(`skill-lib report schema drift: expected ${controlPlane.reportSchemaBlob}, got ${schemaResponse.sha || 'hmmm'}`); + } + + const scriptResponse = getJson(githubApiUrl( + `/repos/${encodeURIComponent(org)}/skill-lib/contents/${controlPlane.portfolioScriptPath}`, + { ref: controlPlane.commit } + )); + if (scriptResponse.sha !== controlPlane.portfolioScriptBlob) { + throw new Error(`skill-lib portfolio script drift: expected ${controlPlane.portfolioScriptBlob}, got ${scriptResponse.sha || 'hmmm'}`); + } + const portfolioScript = path.join(workDir, 'portfolio_plan.py'); + await writeFile(portfolioScript, decodeContent(scriptResponse, 'skill-lib portfolio_plan.py')); + + const reportRecords = new Map(); + const missingReports = []; + for (const definition of projectDefinitions) { + try { + reportRecords.set(definition.repository, await collectReport(definition, workDir)); + } catch (error) { + missingReports.push({ repository: definition.repository, reason: publicFailureReason(error, definition.repository) }); + } + } + if (reportRecords.size === 0) throw new Error('no repository plan reports could be collected'); + + const portfolioOutput = path.join(workDir, 'portfolio-plan.json'); + const python = runPortfolio( + portfolioScript, + [...reportRecords.values()].map(record => record.reportPath), + portfolioOutput + ); + const portfolio = JSON.parse(await readFile(portfolioOutput, 'utf8')); + + const portfolioByRepo = new Map((portfolio.repositories || []).map(item => [item.repository, item])); + const telemetry = new Map(); + for (const definition of projectDefinitions) { + try { telemetry.set(definition.repository, collectTelemetry(definition)); } + catch { telemetry.set(definition.repository, null); } + } + + const data = { + schema: 'the-interdependency.website-sitrep-view', + version: '1.0.0', + snapshotAt: new Date().toISOString(), + fallback: false, + fallbackReason: null, + sourceSnapshotAt: null, + controlPlane: { ...controlPlane, executor: python }, + portfolio, + missingReports, + projects: projectDefinitions.map(definition => projectView( + definition, + reportRecords.get(definition.repository), + telemetry.get(definition.repository), + portfolioByRepo.get(definition.repository) + )) + }; + await writeGenerated(data, true); + console.log(`sitrep ${data.projects.length} projects, ${missingReports.length} missing report(s), plan ${portfolio.portfolio_plan_sha256}`); +} catch (error) { + const data = await fallbackData(publicFailureReason(error)); + await writeGenerated(data, false); + console.log(`sitrep fallback: ${data.fallbackReason}`); +} finally { + if (workDir) await rm(workDir, { recursive: true, force: true }); +} \ No newline at end of file diff --git a/src/_includes/layouts/base.njk b/src/_includes/layouts/base.njk index effe1aa..788b5b9 100644 --- a/src/_includes/layouts/base.njk +++ b/src/_includes/layouts/base.njk @@ -12,6 +12,7 @@ + @@ -27,6 +28,7 @@ Textbook Narratives Projects + SITREP Artifacts Works Research diff --git a/src/assets/css/sitrep.css b/src/assets/css/sitrep.css new file mode 100644 index 0000000..117a729 --- /dev/null +++ b/src/assets/css/sitrep.css @@ -0,0 +1,173 @@ +.sitrep-head { max-width: 68rem; } +.sitrep-control-plane, +.sitrep-boundary { + display: grid; + grid-template-columns: minmax(0, 1.15fr) minmax(18rem, .85fr); + gap: 1.5rem; + align-items: start; + margin: 2rem 0 3.5rem; + padding: clamp(1.2rem, 3vw, 2rem); + border: 1px solid var(--violet); + border-radius: var(--radius); + background: linear-gradient(145deg, rgba(24, 28, 55, .96), rgba(10, 15, 29, .94)); + box-shadow: var(--shadow); +} +.sitrep-control-plane h2, +.sitrep-boundary h2 { margin: .25rem 0 .8rem; } +.sitrep-contract-grid, +.sitrep-metrics, +.sitrep-observed-list { display: grid; gap: .55rem; margin: 0; } +.sitrep-contract-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.sitrep-contract-grid div, +.sitrep-metrics div, +.sitrep-observed-list div { + min-width: 0; + padding: .65rem .75rem; + border: 1px solid var(--line); + border-radius: .7rem; + background: rgba(9, 13, 24, .62); +} +.sitrep-contract-grid dt, +.sitrep-metrics dt, +.sitrep-observed-list dt { + color: var(--silver); + font: 700 .68rem/1.3 ui-monospace, SFMono-Regular, Consolas, monospace; + text-transform: uppercase; + letter-spacing: .08em; +} +.sitrep-contract-grid dd, +.sitrep-metrics dd, +.sitrep-observed-list dd { margin: .2rem 0 0; overflow-wrap: anywhere; } +.sitrep-fallback { margin: 0 0 3rem; } +.sitrep-fallback h2 { margin-top: 0; } +.sitrep-grid { display: grid; gap: 1.35rem; margin: 1.6rem 0 4rem; } +.sitrep-card { + overflow: hidden; + border: 1px solid var(--line); + border-radius: 1.2rem; + background: linear-gradient(145deg, rgba(16, 24, 42, .98), rgba(9, 13, 24, .96)); + box-shadow: 0 20px 55px rgba(0, 0, 0, .2); +} +.sitrep-card-header { + display: grid; + grid-template-columns: 8.5rem minmax(0, 1fr); + gap: 1.3rem; + align-items: center; + padding: 1.35rem; + border-bottom: 1px solid var(--line); +} +.sitrep-card-header h3 { margin: .2rem 0 .35rem; font-size: clamp(1.6rem, 4vw, 2.5rem); } +.sitrep-state { margin: 0; color: var(--silver); } +.sitrep-orbit { + position: relative; + width: 7.25rem; + aspect-ratio: 1; + display: grid; + place-items: center; + border-radius: 50%; + background: radial-gradient(circle, rgba(155, 135, 245, .18) 0 23%, transparent 24% 100%); +} +.sitrep-orbit-ring, +.sitrep-orbit::before, +.sitrep-orbit::after { + content: ""; + position: absolute; + border: 1px solid var(--line); + border-radius: 50%; +} +.sitrep-orbit-ring { inset: .35rem; border-color: var(--violet); } +.sitrep-orbit::before { inset: 1.25rem; border-color: rgba(123, 201, 216, .72); } +.sitrep-orbit::after { inset: 2.3rem; border-color: rgba(255, 107, 115, .8); } +.sitrep-orbit strong { + position: relative; + z-index: 2; + max-width: 4.8rem; + text-align: center; + color: var(--starlight); + font: 800 .72rem/1.05 ui-monospace, SFMono-Regular, Consolas, monospace; + overflow-wrap: anywhere; +} +.orbit-marker { + position: absolute; + z-index: 3; + width: 1.65rem; + aspect-ratio: 1; + display: grid; + place-items: center; + border: 1px solid var(--line); + border-radius: 50%; + background: var(--night); + color: var(--silver); + font: 800 .57rem/1 ui-monospace, SFMono-Regular, Consolas, monospace; +} +.orbit-report { top: -.1rem; left: calc(50% - .82rem); border-color: var(--violet); color: #c2b7ff; } +.orbit-ci { right: -.05rem; top: calc(50% - .82rem); border-color: var(--cyan); color: var(--cyan); } +.orbit-next { bottom: -.1rem; left: calc(50% - .82rem); border-color: #85d3a6; color: #9ce2b9; } +.orbit-hmmm { left: -.05rem; top: calc(50% - .82rem); border-color: var(--amber); color: var(--amber); } +.sitrep-dual { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(17rem, .65fr); } +.sitrep-declared, +.sitrep-observed { padding: 1.25rem 1.35rem; } +.sitrep-declared { border-right: 1px solid var(--line); } +.sitrep-observed { background: rgba(7, 11, 21, .48); } +.sitrep-axis-label { + margin: 0 0 .9rem; + color: var(--cyan); + font: 800 .68rem/1.3 ui-monospace, SFMono-Regular, Consolas, monospace; + text-transform: uppercase; + letter-spacing: .1em; +} +.sitrep-observed .sitrep-axis-label { color: var(--silver); } +.sitrep-claim { max-width: 75ch; margin-top: 0; } +.sitrep-metrics { grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 1rem 0; } +.sitrep-metrics dd { font: 800 1.35rem/1.2 Georgia, "Times New Roman", serif; } +.sitrep-observed-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.sitrep-details { margin: 0; border: 0; border-top: 1px solid var(--line); border-radius: 0; background: rgba(13, 19, 34, .78); } +.sitrep-details > summary { cursor: pointer; color: var(--cyan); font-weight: 800; } +.sitrep-detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem 1.5rem; padding-top: 1rem; } +.sitrep-detail-grid section { min-width: 0; } +.sitrep-detail-grid h4 { margin: 0 0 .5rem; font-size: .78rem; color: var(--silver); text-transform: uppercase; letter-spacing: .08em; } +.sitrep-detail-grid ul { margin: 0; padding-left: 1.2rem; } +.sitrep-detail-grid li { margin: .55rem 0; } +.sitrep-detail-grid small { display: block; margin-top: .2rem; color: var(--silver); } +.sitrep-relations { grid-column: 1 / -1; } +.sitrep-relations li { display: grid; grid-template-columns: minmax(12rem, .45fr) minmax(0, 1fr); gap: .5rem 1rem; padding: .75rem 0; border-bottom: 1px solid rgba(42, 56, 87, .65); } +.sitrep-relations li:last-child { border-bottom: 0; } +.sitrep-relations small { grid-column: 2; color: var(--cyan); } +.sitrep-work-graph { margin: 4rem 0; } +.relation-list { display: grid; gap: .7rem; padding: 0; list-style: none; } +.relation-list li { + display: grid; + grid-template-columns: minmax(12rem, .75fr) auto minmax(12rem, .75fr) minmax(16rem, 1.5fr); + gap: .75rem; + align-items: center; + padding: .9rem 1rem; + border: 1px solid var(--line); + border-radius: .8rem; + background: rgba(16, 24, 42, .78); +} +.relation-list li > span { color: var(--violet); font-size: 1.35rem; } +.relation-list strong { color: var(--silver); font-weight: 500; } +.sitrep-boundary { grid-template-columns: 1fr; } +@media (max-width: 820px) { + .sitrep-control-plane, + .sitrep-dual { grid-template-columns: 1fr; } + .sitrep-declared { border-right: 0; border-bottom: 1px solid var(--line); } + .relation-list li { grid-template-columns: 1fr auto 1fr; } + .relation-list strong { grid-column: 1 / -1; } +} +@media (max-width: 560px) { + .sitrep-card-header { grid-template-columns: 1fr; } + .sitrep-orbit { width: 6.5rem; } + .sitrep-contract-grid, + .sitrep-metrics, + .sitrep-observed-list, + .sitrep-detail-grid { grid-template-columns: 1fr 1fr; } + .sitrep-relations { grid-column: auto; } + .sitrep-relations li { display: block; } + .sitrep-relations small { display: block; } + .relation-list li { grid-template-columns: 1fr; } + .relation-list li > span { transform: rotate(90deg); justify-self: start; } +} +@media (prefers-reduced-motion: reduce) { + .sitrep-card { scroll-behavior: auto; } +} diff --git a/src/sitrep/index.njk b/src/sitrep/index.njk new file mode 100644 index 0000000..79ae89c --- /dev/null +++ b/src/sitrep/index.njk @@ -0,0 +1,143 @@ +--- +layout: layouts/base.njk +title: Repository SITREP +description: A live, provenance-preserving view of METAPAT, UCNS, EDCM, a0, and this website, derived from repo-owned reports through skill-lib and overlaid with separate GitHub telemetry. +--- +
+

Interdependent work graph · live public projection

+

Repository SITREP

+

Each repository states its own situation. skill-lib supplies the frozen reporting contract and deterministic portfolio projection. GitHub activity is shown separately as observed telemetry; a passing workflow never upgrades a repository claim.

+
+ {{ generated.sitrep.projects | length }} participating views + {% if generated.sitrep.fallback %}fallback snapshot{% endif %} + {% if generated.sitrep.missingReports and generated.sitrep.missingReports | length %}{{ generated.sitrep.missingReports | length }} missing report(s){% endif %} +
+
+ +
+
+

Control-plane provenance

+

skill-lib / interdependent-work-graph

+

The control plane validates, orders, hashes, and projects repo-owned reports. It owns no METAPAT canon, UCNS mathematics, EDCM measurement validity, a0 runtime authority, or website publication claim.

+
+
+
Contract
repository-plan-report {{ generated.sitrep.controlPlane.reportSchemaVersion }}
+
Schema blob
{{ generated.sitrep.controlPlane.reportSchemaBlob | truncate(12, true, '') }}
+
skill-lib commit
{{ generated.sitrep.controlPlane.commit | truncate(12, true, '') }}
+
Projection
{% if generated.sitrep.portfolio %}{{ generated.sitrep.portfolio.portfolio_plan_sha256 | truncate(12, true, '') }}{% else %}hmmm{% endif %}
+
Snapshot
{{ generated.sitrep.snapshotAt | dateOnly }}
+
Mode
{% if generated.sitrep.fallback %}last-known-good / unavailable boundary{% else %}fresh build-time observation{% endif %}
+
+
+ +{% if generated.sitrep.fallback %} +
+

Live refresh unavailable

+

{{ generated.sitrep.fallbackReason }}. Displayed data is explicitly marked fallback; missing current state is not reconstructed.

+
+{% endif %} + +
+

Five distinct authority surfaces

+

Current repository situations

+

The left side of each infogram is declared situation from the repository report. The right side is GitHub-observed activity at site build time. Report/head mismatch is shown rather than silently reconciled.

+ +
+ {% for project in generated.sitrep.projects %} +
+
+ +
+

{{ project.repository }}

+

{{ project.label }}

+ {% if project.report %} +

{{ project.report.status.state }}

+ {% else %} +

Repository report unavailable.

+ {% endif %} +
+
+ +
+
+

Declared · repo-owned

+ {% if project.report %} +

{{ project.report.status.current_claim }}

+
+
Frontier
{{ project.report.active_frontier | length }}
+
Next
{{ project.report.next_actions | length }}
+
Blocked
{{ project.report.blocked | length }}
+
hmmm
{{ project.report.hmmm | length }}
+
+
+ report {{ project.reportFreshness }} + source {{ project.report.source.commit | truncate(9, true, '') }} +
+ {% else %} +
hmmm

No report was supplied to the skill-lib projection. No project state has been synthesized.

+ {% endif %} +
+ +
+

Observed · GitHub

+ {% if project.telemetry %} +
+
HEAD
{{ project.telemetry.head | truncate(9, true, '') }}
+
Branch
{{ project.telemetry.branch }}
+
Open PRs
{{ project.telemetry.openPullRequests }}
+
Last push
{{ project.telemetry.pushedAt | dateOnly }}
+
Workflow
{% if project.telemetry.latestWorkflow %}{{ project.telemetry.latestWorkflow.conclusion or project.telemetry.latestWorkflow.status or 'hmmm' }}{% else %}hmmm{% endif %}
+
+ {% if project.telemetry.latestWorkflow %} +
CI {{ project.telemetry.latestWorkflow.conclusion or project.telemetry.latestWorkflow.status }}
+ {% endif %} + {% else %} +

GitHub telemetry was unavailable at build time.

+ {% endif %} +
+
+ + {% if project.report %} +
+ Frontier, next actions, blockers, relations, and hmmm +
+

Active frontier

    {% for item in project.report.active_frontier %}
  • {{ item }}
  • {% else %}
  • None declared.
  • {% endfor %}
+

Next actions

    {% for item in project.report.next_actions %}
  • {{ item.action }}{% if item.dependency %}Dependency: {{ item.dependency }}{% endif %}
  • {% else %}
  • None declared.
  • {% endfor %}
+

Blocked

    {% for item in project.report.blocked %}
  • {{ item }}
  • {% else %}
  • None declared.
  • {% endfor %}
+

hmmm

    {% for item in project.report.hmmm %}
  • {{ item }}
  • {% else %}
  • None declared.
  • {% endfor %}
+

Cross-repository relations

    {% for relation in project.report.cross_repository_relations %}
  • {{ relation.repository }}{{ relation.relation }}authority transfer: false
  • {% else %}
  • None declared.
  • {% endfor %}
+
+
+ {% endif %} +
+ {% endfor %} +
+
+ +{% if generated.sitrep.portfolio %} +
+

Deterministic relation projection

+

Current work graph

+

These edges are emitted from repo-owned relation declarations by the skill-lib projection. Connection does not transfer authority, proof status, measurement validity, empirical status, or publication ownership.

+
    + {% for relation in generated.sitrep.portfolio.cross_repository_dependencies %} +
  1. {{ relation.from }}{{ relation.to }}{{ relation.relation }}
  2. + {% else %} +
  3. No cross-repository relations were projected.
  4. + {% endfor %} +
+
+{% endif %} + +
+

Invariant

+

Relation without collapse

+

Repositories expose their own claims and boundaries. skill-lib defines the reusable coordination contract. This website presents the projection and observes GitHub state. None of those roles silently inherits another role's authority.

+
diff --git a/tests/accessibility.spec.mjs b/tests/accessibility.spec.mjs index ff9bf53..ef1da60 100644 --- a/tests/accessibility.spec.mjs +++ b/tests/accessibility.spec.mjs @@ -13,7 +13,7 @@ if (!firstLabUnit) throw new Error(`missing Lab canon unit ${articleLab[0].unit_ for (const route of [ '/', '/home/', '/preamble/', '/chapters/', '/chapters/chapter-zero/', '/chapters/chapter-seven/', - '/articles/', '/articles/article-two/', '/narratives/', '/narratives/jack-and-diane/', '/way/', '/lab/', `/lab/${firstLabUnit.routeSlug}/`, '/projects/', + '/articles/', '/articles/article-two/', '/narratives/', '/narratives/jack-and-diane/', '/way/', '/lab/', `/lab/${firstLabUnit.routeSlug}/`, '/projects/', '/sitrep/', '/artifacts/gonol-relationships/', '/artifacts/edcm-mathematics/', '/artifacts/four-cuts/', '/fallback/', '/research/method/' ]) { test(`${route} has no serious or critical automated accessibility violations`, async ({ page }) => { @@ -26,4 +26,4 @@ for (const route of [ const blocking = results.violations.filter(violation => ['serious', 'critical'].includes(violation.impact)); expect(blocking, JSON.stringify(blocking, null, 2)).toEqual([]); }); -} +} \ No newline at end of file diff --git a/tests/site.spec.mjs b/tests/site.spec.mjs index 448f540..e56ad2e 100644 --- a/tests/site.spec.mjs +++ b/tests/site.spec.mjs @@ -27,6 +27,7 @@ const routes = [ ['/lab/', /Rights Article laboratories/], ['/source/', /Source/], ['/projects/', /Projects/], + ['/sitrep/', /Repository SITREP/], ['/artifacts/', /Artifacts/], ['/artifacts/gonol-relationships/', /Public Gonol relationship lab/], ['/artifacts/edcm-mathematics/', /EDCM mathematical reference/], @@ -212,15 +213,16 @@ test('Public Gonol lab preserves vesica pieces, triquetra pair receipts, and unr await expect(page.locator('.gonol-pair:not([hidden])')).toHaveCount(1); const activeOperandOutput = page.locator('.gonol-operand:not([hidden]) .gonol-output').first(); - await expect(activeOperandOutput.locator(':scope > .copy-button')).toHaveCount(1); + await expect(activeOperandOutput.locator(':scope > .field-actions > .copy-button')).toHaveCount(3); const completeReceiptOutput = page.locator('[data-receipt-output]').locator('..'); - await expect(completeReceiptOutput.locator(':scope > .copy-button')).toHaveCount(1); + await expect(completeReceiptOutput.locator(':scope > .field-actions > .copy-button')).toHaveCount(3); await page.locator('#gonol-payload-A').fill('root'); await page.locator('#gonol-payload-B').fill('root'); await page.locator('[data-comparison-policy]').selectOption('exact-utf8'); await expect(page.locator('[data-receipt-output]')).toContainText('"exact_utf8_equal": true'); - await expect(page.locator('[data-vector-output]')).toContainText('"code_point": "U+0072"'); + await expect(page.locator('[data-vector-output]')).toContainText('"unicode_scalar_value": 114'); + await expect(page.locator('[data-vector-output]')).toContainText('"source_value": "r"'); await page.locator('.gonol-segmented label', { has: page.locator('input[value="3"]') }).click(); await expect(page.locator('[data-gonol-status]')).toContainText('3 retained pairwise vesicas'); @@ -228,14 +230,18 @@ test('Public Gonol lab preserves vesica pieces, triquetra pair receipts, and unr await expect(page.locator('[data-gonol-stage] [data-pair-intersection]')).toHaveCount(6); await expect(page.locator('.gonol-pair:not([hidden])')).toHaveCount(3); for (const pair of ['AB', 'BC', 'CA']) { - await expect(page.locator(`.gonol-pair[data-pair="${pair}"]`)).toContainText(`vesica-${pair.toLowerCase()}`); + const pairCard = page.locator(`.gonol-pair[data-pair="${pair}"]`); + await expect(pairCard).toContainText(`Retained vesica ${pair[0]} ↔ ${pair[1]}`); + await expect(pairCard).toContainText(`"pair_id": "${pair[0]}-${pair[1]}"`); } await page.locator('.gonol-segmented label', { has: page.locator('input[value="7"]') }).click(); await expect(page.locator('[data-gonol-status]')).toContainText('geometry and pairing remain hmmm'); await expect(page.locator('[data-gonol-stage] .gonol-identity-box')).toHaveCount(7); await expect(page.locator('[data-gonol-stage] .gonol-operand-ring')).toHaveCount(0); - await expect(page.locator('[data-receipt-output]')).toContainText('no seven-form geometry or pairing count was inferred'); + await expect(page.locator('[data-receipt-output]')).toContainText('"standing": "hmmm-unresolved"'); + await expect(page.locator('[data-receipt-output]')).toContainText('"pairs": []'); + await expect(page.locator('[data-receipt-output]')).toContainText('seven-gonol display geometry and pairing plan'); }); test('Public Gonol lab contains long receipts on a narrow viewport', async ({ page }) => { @@ -270,4 +276,4 @@ test('Research is study-only and exposes provisional citation gaps', async ({ pa await expect(page.locator('body')).toContainText('support no-qualifying-study-found'); await expect(page.locator('body')).toContainText('dissent no-qualifying-study-found'); await expect(page.locator('body')).not.toContainText('TeamSTEPPS'); -}); +}); \ No newline at end of file diff --git a/tests/sitrep.test.mjs b/tests/sitrep.test.mjs new file mode 100644 index 0000000..1840615 --- /dev/null +++ b/tests/sitrep.test.mjs @@ -0,0 +1,49 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +test('SITREP consumes the frozen skill-lib projection instead of redefining repo authority', async () => { + const source = await readFile('scripts/fetch-sitrep.mjs', 'utf8'); + assert.match(source, /repository-plan-report\.schema\.json/); + assert.match(source, /9b347b2dff7692054b571602f30ee6d00c2e7265/); + assert.match(source, /interdependent-work-graph\/portfolio_plan\.py/); + assert.match(source, /97b8b546b4151486164c8a4b730c24a8c895b25b/); + assert.match(source, /runPortfolio/); + assert.match(source, /python3/); + assert.match(source, /missingReports/); +}); + +test('SITREP failure publication classifies errors instead of echoing command details', async () => { + const source = await readFile('scripts/fetch-sitrep.mjs', 'utf8'); + assert.match(source, /function publicFailureReason/); + assert.match(source, /fallbackData\(publicFailureReason\(error\)\)/); + assert.doesNotMatch(source, /fallbackData\(error\.message/); + assert.doesNotMatch(source, /reason:\s*error\.message/); +}); + +test('HEAD mismatch is exposed as difference without inferring substantive staleness', async () => { + const source = await readFile('scripts/fetch-sitrep.mjs', 'utf8'); + assert.match(source, /sourceCommit === head \? 'current' : 'HEAD differs'/); + assert.doesNotMatch(source, /sourceCommit === head \? 'current' : 'stale'/); +}); + +test('SITREP presentation keeps declared situation separate from observed GitHub telemetry', async () => { + const template = await readFile('src/sitrep/index.njk', 'utf8'); + assert.match(template, /Declared · repo-owned/); + assert.match(template, /Observed · GitHub/); + assert.match(template, /Report\/head mismatch is shown rather than silently reconciled/); + assert.match(template, /authority transfer: false/); +}); + +test('website participates through the skill-lib repository-plan-report contract', async () => { + const report = JSON.parse(await readFile('docs/work-graphs/repository-plan-report.json', 'utf8')); + assert.equal(report.schema, 'the-interdependency.repository-plan-report'); + assert.equal(report.version, '1.0.0'); + assert.equal(report.repository, 'The-Interdependency/The-Interdependency.github.io'); + assert.equal(report.contract.repository, 'The-Interdependency/skill-lib'); + assert.equal(report.contract.path, 'interdependent-work-graph/repository-plan-report.schema.json'); + assert.equal(report.contract.blob_sha, '9b347b2dff7692054b571602f30ee6d00c2e7265'); + assert.equal(report.portfolio_role.reports_to.skill, 'interdependent-work-graph'); + assert.match(report.source.commit, /^[0-9a-f]{40}$/); + assert.ok(report.authority.non_transfer.length > 0); +}); \ No newline at end of file