diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a0a855b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + +jobs: + quality: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Enable corepack + run: corepack enable + - name: Bootstrap + run: corepack pnpm bootstrap + - name: Lint + run: corepack pnpm lint + - name: Typecheck + run: corepack pnpm typecheck + - name: Test + run: corepack pnpm test + + demo_verify: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Enable corepack + run: corepack enable + - name: Bootstrap + run: corepack pnpm bootstrap + - name: Demo verify + run: corepack pnpm demo:verify + + extension_smoke: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Enable corepack + run: corepack enable + - name: Bootstrap + run: corepack pnpm bootstrap + - name: Install Playwright Chromium + run: corepack pnpm exec playwright install --with-deps chromium + - name: Extension smoke + run: xvfb-run -a corepack pnpm test:extension-smoke diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93148ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +node_modules/ +.pnpm-store/ +apps/**/node_modules/ +apps/**/.next/ +apps/**/build/ +apps/**/dist/ +apps/extension/public/manifest.json +packages/**/node_modules/ +packages/**/dist/ +packages/**/tsconfig.tsbuildinfo +apps/**/tsconfig.tsbuildinfo +*.log +.DS_Store +.tmp-extension-smoke-profile/ +playwright-report/ +test-results/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..44a6d1f --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 100 +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c8635e5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# CHANGELOG + +## Unreleased + +### Added + +--- + +## v0.214 — 2026-02-14 + +### Added + +- `CODE_OF_CONDUCT.md` for community standards. +- Structured extension verifier inspector UI with grouped protocol sections. +- Expandable raw-response viewer showing verbatim server payload. +- Extension runtime `GBV_PING` message for deterministic smoke checks. +- Shared server logger with request IDs and stage timing. +- Privacy-safe trace metadata in verifier responses (`requestId`, `timing`, `traceSummary`, `failedInvariantIds`). +- Top-level `/docs` structure: + - `docs/architecture.md` + - `docs/protocol-overview.md` + - `docs/threat-model.md` + - `docs/demo-walkthrough.md` + - `docs/research/README.md` +- `@gbv/core` unit tests for receipts, merkle proofs, and structure-bound nonces. +- Playwright-based extension smoke script (`scripts/extension-smoke.ts`). +- GitHub Actions CI workflow with quality, strict demo, and extension smoke jobs. +- Release hardening report (`docs/release-hardening-report.md`). + +### Changed + +- `demo:verify` now performs strict assertions for baseline/adversarial outcomes. +- Extension and server debug diagnostics gated by `GBV_DEBUG`. +- `README.md` rewritten for onboarding-first OSS usage. +- `SECURITY.md` moved to GitHub Security Advisories reporting. +- Development and traceability docs consolidated under `/docs`. + +--- + +## 1.0.0 + +- GBV workspace architecture with server, synthetic client, extension, and shared core packages. +- Config-driven GBV API routes and deterministic synthetic dataset outcomes. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..524ba09 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for +moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported through GitHub Security Advisories private reporting in this +repository's **Security** tab. All complaints will be reviewed and investigated +promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1d57fd6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,113 @@ +# CONTRIBUTING + +Thank you for your interest in contributing to the Glass Ballroom Verification (GBV) reference implementation. + +This repository is a **protocol reference and evaluation environment**, so contributions should prioritize correctness, reproducibility, and architectural clarity over feature expansion. + +--- + +## Local Setup + +Prepare a development environment: + +```bash +corepack prepare pnpm@10.4.1 --activate +corepack pnpm bootstrap +corepack pnpm dev +``` + +This will install workspace dependencies, build required artifacts, and start the local development environment. + +--- + +## Required Checks Before Opening a Pull Request + +All contributions must pass the following checks: + +```bash +corepack pnpm lint +corepack pnpm typecheck +corepack pnpm test +corepack pnpm demo:verify +``` + +These ensure code quality, type safety, protocol stability, and expected verifier behavior. + +### Recommended Targeted Checks + +Depending on the area of change, contributors are encouraged to run: + +```bash +corepack pnpm test:server +corepack pnpm test:attack +corepack pnpm build:extension +``` + +These help validate API behavior, adversarial regression coverage, and extension compatibility. + +--- + +## Contribution Rules + +To preserve protocol correctness and evaluation integrity: + +- Maintain alignment with the GBV specification defined in **`gbv_v0.71.pdf`**. +- Preserve the **blind verification property** — the server must not encode dataset validity labels. +- Do not introduce authentication systems, external APIs, or secret-dependent flows. +- Treat `gbv.config.ts` as the single source of runtime configuration. +- Reuse shared protocol logic from `@gbv/core`; avoid duplicating verification behavior across components. +- Prefer deterministic behavior over environment-dependent logic. + +Changes that alter protocol semantics should be clearly justified and documented. + +--- + +## Documentation Expectations + +Documentation must be updated when behavior or structure changes. + +| File | When to Update | +| ---------------------- | -------------------------------------- | +| `README.md` | Setup, demo workflow, or usage changes | +| `docs/protocol-overview.md` | Specification-to-code mapping changes | +| `docs/architecture.md` | Structural or architectural decisions | +| `docs/threat-model.md` | Threat-model or adversarial assumption updates | +| `docs/demo-walkthrough.md` | Demo workflow or inspection UX changes | +| `CHANGELOG.md` | User-visible or release-facing changes | + +Documentation updates are considered part of the contribution, not optional follow-up work. + +--- + +## Pull Request Requirements + +Each pull request should include: + +- a concise summary of what changed +- a **protocol impact note** (if applicable) +- evidence of successful testing (commands run and outcomes) + +Example: + +``` +Ran: +- pnpm lint +- pnpm test +- pnpm demo:verify + +Result: +All checks passed. No protocol behavior changes. +``` + +--- + +## Contribution Philosophy + +GBV is a protocol reference implementation, not a feature-driven application. Contributions should aim to: + +- improve correctness +- strengthen reproducibility +- clarify implementation intent +- maintain architectural simplicity + +When in doubt, prefer smaller, well-scoped changes that preserve protocol transparency. diff --git a/README.md b/README.md index 2c952ec..be6e681 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,216 @@ -# ARGON-V +# Glass Ballroom Verification (GBV) Reference Implementation -**Adversarial-Resistant Ground-truth ObservatioN - Verification** +**Glass Ballroom Verification (GBV)** is a deterministic client-server verification protocol for evaluating cross-surface semantic consistency of browser-observable artifacts under verifier authority. -## Operational Status +This repository provides the official GBV reference implementation. GBV is domain-agnostic; course records are included only as a reproducible demonstration environment. -**Current phase:** Active development with protocol hardening (v0.1). - -This repository serves as the canonical home for the **ARGON-V** protocol. Development is actively underway, with parallel progress on implementation and formalization. Current work focuses on refining protocol invariants, expanding threat-surface coverage, and validating design assumptions against realistic adversarial models. +> **Documentation state — v0.214** +> +> This repository reflects the state of the GBV reference implementation +> as of February 14, 2026, when protocol terminology was consolidated +> under the Glass Ballroom Verification (GBV) name. Earlier drafts may +> reference ARGON-V terminology. > [!NOTE] > While core protocol mechanics are implemented, certain components remain under iterative refinement as security assumptions and edge cases are stress-tested. Interfaces and internal representations may evolve prior to the v0.1 stabilization milestone. -## The Economic Model of Trust +## What This Repository Provides + +- A deterministic GBV reference environment for engineers and researchers. +- A complete client-server verification flow: + - MV3 browser extension collects browser-observable surfaces. + - Server independently evaluates protocol invariants. +- A transparency-first verification inspector with structured summaries and verbatim server payloads. + +GBV evaluates whether independently observed surfaces can represent a single coherent state without requiring privileged provider access or shared ground truth. + +--- + +## What This Repository Is Not + +- Not a production credential verification service. +- Not a hosted SaaS deployment. +- Not a provider-integrated system with authentication, rate limiting, or tenant isolation. + +This repository is a protocol reference and research implementation. + +--- + +## Protocol Blindness Clarification + +In GBV, blindness means authority separation, not UI opacity. + +- The server is decision-authoritative. +- The server does not rely on dataset validity labels. +- The client cannot influence verifier outcomes. + +The extension can display full server-returned metadata without violating protocol blindness. + +--- + +## Repository Layout + +- [`apps/server`](./apps/server/) - GBV verifier API (`/api/gbv/*`) +- [`apps/client/synthetic`](./apps/client/synthetic/) - synthetic learning platform surfaces +- [`apps/extension`](./apps/extension/) - Chrome MV3 GBV client +- [`packages/gbv-core`](./packages/gbv-core/) - canonicalization, invariants, commitments, receipts +- [`packages/gbv-config`](./packages/gbv-config/) - shared runtime configuration +- [`docs/`](./docs/) - architecture, protocol, threat model, walkthroughs, and reports + +--- + +## Research Paper Versioning + +Research drafts are versioned independently from implementation code. + +Current public research draft: + +- [`docs/research/gbv_public_v0.214.pdf`](./docs/research/gbv_public_v0.214.pdf) + +--- + +## Prerequisites + +- Node.js 22+ +- Corepack + +```bash +corepack enable +corepack prepare pnpm@10.4.1 --activate +``` + +--- + +## Quickstart + +```bash +corepack pnpm bootstrap +corepack pnpm dev +``` + +Services: + +- Synthetic client: [http://localhost:3000](http://localhost:3000) +- GBV server: [http://localhost:3001](http://localhost:3001) +- Extension build: [`apps/extension/build`](./apps/extension/build/) + +--- + +## Manual Demo Walkthrough + +1. Open `chrome://extensions`. +2. Enable **Developer Mode**. +3. Load unpacked extension from [`apps/extension/build`](./apps/extension/build/). +4. Open `http://localhost:3000/hub`. +5. Open the extension popup. +6. Choose a course. +7. Click **Verify**. -ARGON-V does not attempt to provide a mathematical guarantee of absolute truth, which is formally impossible in an unmanaged client environment. Instead, it enforces **economic cost asymmetry**. +### Expected Baseline Behavior -By requiring simultaneous multi-surface semantic consistency—tied to ephemeral, server-issued challenges—the protocol raises the complexity and labor cost of a successful forgery beyond the expected utility of the exploit. In effect, ARGON-V transforms a _data manipulation_ problem into a _coherent narrative forgery_ problem, which is significantly harder to automate and sustain. +- `csk_7r2q9p` -> accepted +- `csk_0a81lm` -> semantic mismatch (`SEMANTIC_VERIFICATION_FAILED`, `module_count_consistency`) +- `csk_3z19tt` -> semantic mismatch (`SEMANTIC_VERIFICATION_FAILED`, `certificate_id_consistency`) +- `csk_t1mix` -> semantic mismatch (`SEMANTIC_VERIFICATION_FAILED`, includes `grade_threshold` and `course_key_consistency`) + +### Popup Behavior + +- Structured summary first (state, status, score, IDs, timing) +- Grouped protocol sections +- Expandable **Raw Response** with verbatim verifier payload --- -## Core System Invariants +## Automated Verification + +```bash +corepack pnpm lint +corepack pnpm typecheck +corepack pnpm test +corepack pnpm demo:verify +``` + +`demo:verify` validates expected protocol states, mismatch classes, and invariant behavior. -The ARGON-V pipeline enforces three primary safety properties: +--- -- **Temporal Freshness** - Observations are cryptographically bound to an ephemeral, high-entropy nonce to prevent replay and pre-computed state injection. +## Extension Smoke Test -- **Semantic Coherence** - Facts are extracted from multiple independent observation surfaces. Verification requires that all surfaces maintain logical consistency (e.g., a dashboard total must equal the sum of its ledger entries). +```bash +corepack pnpm exec playwright install chromium +corepack pnpm test:extension-smoke +``` -- **Deterministic Canonicalization** - Raw DOM artifacts are mapped into a strictly typed, server-authoritative schema, neutralizing client-side heuristic manipulation and UI noise. +CI executes these tests under Xvfb. --- -## Repository Structure +## Debug Logs (Off by Default) + +PowerShell: + +```powershell +$env:GBV_DEBUG='1'; corepack pnpm dev +``` + +Bash: -- **`apps/server`** — Authoritative verification and Merkle-commitment engine. -- **`apps/client`** — Core client suite, including the testing DOM (`mock-pass`, `mock-fail`) and the browser extension for server interaction and verification. -- **`docs/theory`** — Systems analysis and economic-security reasoning. Includes the [research paper](docs/theory/research-paper-v0.1.pdf). +```bash +GBV_DEBUG=1 corepack pnpm dev +``` --- -### Ongoing Work +## Security and Privacy Notes + +- Server logs request IDs, timing metrics, and non-sensitive diagnostics only. +- Raw page HTML is not logged by default. +- Security disclosures should use GitHub Security Advisories private reporting. + +--- + +## Troubleshooting + +**Extension cannot verify** + +- Run `corepack pnpm build:extension` +- Reload unpacked extension +- Verify `http://localhost:3001/api/health` +- Verify `http://localhost:3000/api/gbv/courses` + +**Extension smoke test fails** + +```bash +corepack pnpm exec playwright install chromium +``` + +--- + +## Documentation + +For structured documentation navigation, start with +[`docs/README.md`](./docs/README.md). + +- [`docs/architecture.md`](./docs/architecture.md) +- [`docs/protocol-overview.md`](./docs/protocol-overview.md) +- [`docs/threat-model.md`](./docs/threat-model.md) +- [`docs/demo-walkthrough.md`](./docs/demo-walkthrough.md) +- [`docs/release-hardening-report.md`](./docs/release-hardening-report.md) +- [`docs/development-notes.md`](./docs/development-notes.md) +- [`docs/traceability.md`](./docs/traceability.md) +- [`docs/research/`](./docs/research/) + +--- -- **Active Development:** Real-time server and client progress is visible on the [**`develop` branch**](../../tree/develop). -- **Theory & Research:** The current protocol draft and threat model are documented in the [research-paper-v0.1.pdf](docs/theory/research-paper-v0.1.pdf). +## Project Policies -Readers interested in implementation progress, design decisions, or protocol mechanics are encouraged to consult the **develop** branch and accompanying theory documentation. +- [`SECURITY.md`](./SECURITY.md) +- [`CONTRIBUTING.md`](./CONTRIBUTING.md) +- [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) +- [`CHANGELOG.md`](./CHANGELOG.md) --- ## License -Licensed under the Apache License, Version 2.0. +See [`LICENSE`](./LICENSE). diff --git a/SECURITY.md b/SECURITY.md index 1441f7c..39c2a6b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,52 +1,45 @@ # Security Policy -## Responsible Disclosure - -ARGON-V is an adversarial-resistant protocol, and security and integrity are treated as first-class design goals. We welcome and appreciate responsible disclosure from security researchers, protocol analysts, and systems engineers. - -At this stage, ARGON-V is an early protocol and systems design (v0.1). Security reports may concern logical flaws, economic attacks, invariant violations, or weaknesses in the formal specification, in addition to issues in any reference implementations. - ---- - ## Reporting a Vulnerability -If you identify a potential security issue in the ARGON-V protocol specification or its reference implementations, please do **not** use the public issue tracker. - -Instead, report the issue privately to the maintainer: - -**Reporting channel:** dante@youroasis.ai +Do not open public issues for security vulnerabilities. -Please include, where applicable: +Use GitHub Security Advisories private reporting: -- A clear description of the issue or vulnerability -- The affected protocol stage (e.g., Stage III: Semantic Intersection) -- A proof-of-concept, attack sketch, or theoretical walkthrough -- Potential impact on correctness, security, or economic guarantees -- Suggested mitigations or design alternatives (if available) +1. Open the repository **Security** tab. +2. Select **Report a vulnerability**. +3. Submit a private advisory with details. ---- +## What to Include -## Scope of Security Review +- clear vulnerability description +- affected GBV stage/component +- reproducible steps +- expected attacker model/capability +- observed impact +- optional mitigation proposal -During the current **Pre-Birth / Specification Drafting** phase, we are particularly interested in reports covering: +## Priority Scope -- **Logical flaws** — Bypasses or inconsistencies in the five-stage pipeline -- **Economic attacks** — Techniques that reduce the cost of forgery without violating semantic coherence -- **Protocol invariants** — Contradictions or weaknesses in the formal specification (`SPEC.md`) -- **Adversarial assumptions** — Gaps or unrealistic constraints in the threat model +Highest priority findings include: ---- +- nonce-binding bypasses +- semantic invariant bypasses +- verifier authority/blindness violations +- receipt/commitment integrity flaws +- extension collection flow bypasses -## Our Commitment +## Response Expectations -If you follow the responsible disclosure process outlined above, we commit to: +- acknowledgement target: 72 hours +- triage and reproduction +- coordinated remediation +- optional attribution upon fix (on request) -- Acknowledging receipt of your report within **48–72 hours** -- Working with you to understand, validate, and reproduce the issue -- Crediting you in security advisories or specification revisions, where appropriate +## Safe Harbor ---- +Good-faith testing and responsible private disclosure are welcome. -## Non-Disclosure Policy +## Scope Context -We ask that reported issues are not disclosed publicly until they have been addressed in the specification or resolved in a subsequent protocol revision. This policy is intended to support responsible discussion during early development and does not preclude future public disclosure once the protocol reaches a stable release. +This repository is a local-first protocol reference implementation. Reports should prioritize protocol correctness and verification integrity over hosted production hardening concerns. diff --git a/SPEC.md b/SPEC.md deleted file mode 100644 index 80a799a..0000000 --- a/SPEC.md +++ /dev/null @@ -1,33 +0,0 @@ -# ARGON-V Protocol Specification (v0.1-draft) - -## 1. Introduction -The ARGON-V protocol defines a method for a Verifier ($V$) to obtain a high-confidence attestation of state $\sigma$ from a third-party Platform (hereafter referred to as the **Source** $\mathcal{S}$) via an untrusted Prover $\mathcal{A}$ (the adversarial client). The protocol operates under the assumption that $\mathcal{A}$ has full control over the local execution environment and may attempt to present a forged state. - -## 2. Protocol Invariants -For any attestation to be considered valid, it must satisfy the following invariants: - -1. **Freshness**: The observation must be cryptographically bound to an ephemeral, server-issued nonce $\eta$. -2. **Coherence**: The set of extracted facts $F = \{f_1, f_2, ..., f_k\}$ across $k$ surfaces must maintain logical consistency. Coherence is evaluated against verifier-defined semantic constraints $\mathcal{C}$, which are necessarily incomplete and platform-specific. -3. **Immutability**: Once an observation is canonicalized, its Merkle root $R$ must be signed by $V$ to prevent post-facto tampering. - -## 3. The Five-Stage Pipeline - -### Stage I: Challenge (Temporal Binding) -The Verifier issues a single-use, high-entropy nonce $\eta$. This nonce must be embedded into the observation environment to prove the observation occurred *after* $\eta$ was issued and within the validity window $\Delta t$. - -### Stage II: Observation (Multi-Surface Capture) -The Prover $\mathcal{A}$ captures $k$ independent surfaces from the Source $\mathcal{S}$. Each surface $s_i \in \{s_1, ..., s_k\}$ contributes to the total evidence pool. - -### Stage III: Consistency (Semantic Validation) -The system evaluates the captured surfaces for cross-surface invariants. If surface $s_1$ represents a summary and $s_2$ represents a detailed ledger, the protocol enforces that $s_1 \equiv s_2$ according to the verifier-defined constraints $\mathcal{C}$. - - - -### Stage IV: Canonicalization (Deterministic Mapping) -Raw captured data is stripped of non-deterministic artifacts (e.g., timestamps, session IDs) and mapped into a strictly typed schema. This ensures that equivalent platform states deterministically map to identical cryptographic representations. - -### Stage V: Verification (Finality) -The canonical state is inserted into a Merkle-based accumulator. The Verifier signs the Merkle root $R$, issuing a "Verifiable Attestation" that can be audited by third parties—provided they trust $V$’s public key and the constraint set $\mathcal{C}$—without re-running the extraction pipeline. - -## 4. Error States and Termination -If any invariant is violated during Stages I-III, the protocol must terminate immediately with a `NULL_COMMITMENT`. The protocol prioritizes **Safety** (preventing false positives) over **Liveness** (guaranteeing a result). diff --git a/apps/client/synthetic/.gitignore b/apps/client/synthetic/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/apps/client/synthetic/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/client/synthetic/README.md b/apps/client/synthetic/README.md new file mode 100644 index 0000000..6b67493 --- /dev/null +++ b/apps/client/synthetic/README.md @@ -0,0 +1,27 @@ +# @gbv/synthetic-client + +Synthetic multi-surface platform used for GBV verification demos. + +## Purpose + +- Exposes multiple course artifacts through identical route/API shapes. +- Includes coherent and adversarial artifacts. +- Adversarial outcomes emerge from cross-surface inconsistencies, not explicit fail flags. + +## Notable Artifact Keys + +- `csk_7r2q9p`: coherent baseline artifact (expected GBV acceptance) +- `csk_t1mix`: visible Tier-1 mixed adversarial artifact (expected GBV rejection) + +## Local Commands + +From repo root: + +- `corepack pnpm --filter @gbv/synthetic-client dev` +- `corepack pnpm --filter @gbv/synthetic-client lint` +- `corepack pnpm --filter @gbv/synthetic-client typecheck` + +Route catalog API: +- `GET /api/gbv/courses` + +See root `README.md` for extension-driven verification walkthrough. diff --git a/apps/client/synthetic/eslint.config.mjs b/apps/client/synthetic/eslint.config.mjs new file mode 100644 index 0000000..f443835 --- /dev/null +++ b/apps/client/synthetic/eslint.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; + +const eslintConfig = defineConfig([ + ...nextVitals, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/apps/client/synthetic/next.config.mjs b/apps/client/synthetic/next.config.mjs new file mode 100644 index 0000000..b4ba935 --- /dev/null +++ b/apps/client/synthetic/next.config.mjs @@ -0,0 +1,7 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + transpilePackages: ["@gbv/config"], +}; + +export default nextConfig; \ No newline at end of file diff --git a/apps/client/synthetic/package.json b/apps/client/synthetic/package.json new file mode 100644 index 0000000..36ff626 --- /dev/null +++ b/apps/client/synthetic/package.json @@ -0,0 +1,24 @@ +{ + "name": "@gbv/synthetic-client", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "tsx scripts/dev.ts", + "build": "next build", + "start": "next start", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "typecheck": "tsc -p tsconfig.json" + }, + "dependencies": { + "@gbv/config": "workspace:*", + "next": "16.1.1", + "react": "19.2.3", + "react-dom": "19.2.3" + }, + "devDependencies": { + "eslint": "^9.38.0", + "eslint-config-next": "16.1.1", + "@tailwindcss/postcss": "^4", + "tailwindcss": "^4" + } +} diff --git a/apps/client/synthetic/postcss.config.mjs b/apps/client/synthetic/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/apps/client/synthetic/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/apps/client/synthetic/public/favicon.ico b/apps/client/synthetic/public/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/apps/client/synthetic/public/favicon.ico differ diff --git a/apps/client/synthetic/public/file.svg b/apps/client/synthetic/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/apps/client/synthetic/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/client/synthetic/public/globe.svg b/apps/client/synthetic/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/apps/client/synthetic/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/client/synthetic/public/next.svg b/apps/client/synthetic/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/apps/client/synthetic/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/client/synthetic/public/vercel.svg b/apps/client/synthetic/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/apps/client/synthetic/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/client/synthetic/public/window.svg b/apps/client/synthetic/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/apps/client/synthetic/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/client/synthetic/scripts/dev.ts b/apps/client/synthetic/scripts/dev.ts new file mode 100644 index 0000000..9e52156 --- /dev/null +++ b/apps/client/synthetic/scripts/dev.ts @@ -0,0 +1,18 @@ +#!/usr/bin/env node +import { spawn } from "node:child_process"; +import { gbvConfig } from "@gbv/config"; + +const child = spawn( + "corepack", + ["pnpm", "exec", "next", "dev", "-p", String(gbvConfig.ports.syntheticClient)], + { + stdio: "inherit", + shell: process.platform === "win32", + env: { + ...process.env, + PORT: String(gbvConfig.ports.syntheticClient), + }, + }, +); + +child.on("exit", (code) => process.exit(code ?? 0)); diff --git a/apps/client/synthetic/src/lib/data.js b/apps/client/synthetic/src/lib/data.js new file mode 100644 index 0000000..826932d --- /dev/null +++ b/apps/client/synthetic/src/lib/data.js @@ -0,0 +1,231 @@ +const BASE_COURSES = [ + { + courseId: 101, + publicCourseKey: "csk_7r2q9p", + slug: "introduction-microsoft-excel", + platformCourseId: "KDNGIJBDG", + certificateId: "O04CRAKZMLZJ", + title: "Getting Started with Microsoft Excel", + description: + "Learn the basics of Microsoft Excel, including formulas, functions, and data visualization.", + org: "Synthetic", + progressText: "100% complete", + gradeText: "Grade Achieved: 100%", + completedOn: "November 2 2025", + durationText: "1 hours approximately", + hasLinkedIn: true, + semanticProfile: { + moduleCountByPage: { + default: 5, + }, + }, + }, + { + courseId: 202, + publicCourseKey: "csk_0a81lm", + slug: "data-structures-foundations", + platformCourseId: "PZ91KQ2RM", + certificateId: "744DP2JRWJDS", + title: "Data Structures Foundations", + description: + "Understand the fundamental concepts of data structures, including arrays, linked lists, and trees.", + org: "Synthetic", + progressText: "88% complete", + gradeText: "Grade Achieved: 96%", + completedOn: "October 14 2025", + durationText: "18 hours approximately", + hasLinkedIn: false, + semanticProfile: { + moduleCountByPage: { + default: 6, + assignments: 4, + }, + }, + }, + { + courseId: 303, + publicCourseKey: "csk_3z19tt", + slug: "applied-ml-systems", + platformCourseId: "QW0N9X8AA", + certificateId: "C9T-81F2Z0G1", + title: "Applied ML Systems", + description: "Explore the application of machine learning techniques in real-world systems.", + org: "Synthetic", + progressText: "91% complete", + gradeText: "Grade Achieved: 94%", + completedOn: "September 8 2025", + durationText: "24 hours approximately", + hasLinkedIn: true, + semanticProfile: { + moduleCountByPage: { + default: 7, + }, + certificateIdByPage: { + default: "C9T-81F2Z0G1", + proof: "C9T-81F2Z0G1-ALT", + }, + }, + }, + { + courseId: 404, + publicCourseKey: "csk_t1mix", + slug: "secure-evidence-practicum", + platformCourseId: "MIXTIER1A", + certificateId: "MIX-7Q1Z-442K", + title: "Secure Evidence Practicum", + description: + "Practice artifact consistency analysis across provider surfaces and verification checkpoints.", + org: "Synthetic", + progressText: "100% complete", + gradeText: "Grade Achieved: 100%", + completedOn: "January 9 2026", + durationText: "12 hours approximately", + hasLinkedIn: false, + semanticProfile: { + moduleCountByPage: { + default: 6, + assignments: 4, + }, + certificateIdByPage: { + default: "MIX-7Q1Z-442K", + proof: "MIX-7Q1Z-442K-PROOF", + }, + gradePercentByPage: { + default: 100, + assignments: 50, + }, + progressPercentByPage: { + default: 100, + assignments: 52, + }, + courseKeyByPage: { + default: "csk_t1mix", + assignments: "csk_t1mix-shadow", + }, + }, + }, +]; + +function parsePercent(text) { + const match = String(text || "").match(/(\d+(?:\.\d+)?)\s*%/); + return match ? Number.parseFloat(match[1]) : null; +} + +function cloneCourse(course) { + return JSON.parse(JSON.stringify(course)); +} + +function formatPercentValue(value) { + if (!Number.isFinite(value)) return ""; + return Number.isInteger(value) ? String(value) : String(Number(value).toFixed(1)); +} + +function formatGradeText(percent, fallback) { + const normalized = formatPercentValue(percent); + return normalized ? `Grade Achieved: ${normalized}%` : String(fallback || ""); +} + +function formatProgressText(percent, fallback) { + const normalized = formatPercentValue(percent); + return normalized ? `${normalized}% complete` : String(fallback || ""); +} + +function resolveByPage(course, pageType, key, fallback) { + const byPage = course?.semanticProfile?.[key] || {}; + return byPage[pageType] ?? byPage.default ?? fallback; +} + +function resolveGradePercent(course, pageType) { + const fallback = parsePercent(course?.gradeText); + const resolved = resolveByPage(course, pageType, "gradePercentByPage", fallback); + return Number.isFinite(resolved) ? Number(resolved) : fallback; +} + +function resolveProgressPercent(course, pageType) { + const fallback = parsePercent(course?.progressText); + const resolved = resolveByPage(course, pageType, "progressPercentByPage", fallback); + return Number.isFinite(resolved) ? Number(resolved) : fallback; +} + +function resolveModuleCount(course, pageType) { + const resolved = resolveByPage(course, pageType, "moduleCountByPage", null); + return Number.isFinite(resolved) ? Number(resolved) : null; +} + +function resolveCertificateId(course, pageType) { + return String( + resolveByPage(course, pageType, "certificateIdByPage", course?.certificateId || ""), + ); +} + +function resolveCourseKey(course, pageType) { + return String(resolveByPage(course, pageType, "courseKeyByPage", course?.publicCourseKey || "")); +} + +function resolveCourseName(course, pageType) { + return String(resolveByPage(course, pageType, "courseNameByPage", course?.title || "")); +} + +function resolveCourseSlug(course, pageType) { + return String(resolveByPage(course, pageType, "courseSlugByPage", course?.slug || "")); +} + +export function getSurfaceView(course, pageType) { + const gradePercent = resolveGradePercent(course, pageType); + const progressPercent = resolveProgressPercent(course, pageType); + const moduleCount = resolveModuleCount(course, pageType); + const certificateId = resolveCertificateId(course, pageType); + const courseKey = resolveCourseKey(course, pageType); + const courseName = resolveCourseName(course, pageType); + const courseSlug = resolveCourseSlug(course, pageType); + + return { + gradePercent, + progressPercent, + moduleCount, + certificateId, + courseKey, + courseName, + courseSlug, + gradeText: formatGradeText(gradePercent, course?.gradeText), + progressText: formatProgressText(progressPercent, course?.progressText), + }; +} + +export function getCourses() { + return BASE_COURSES.map(cloneCourse); +} + +export function getCourseByCert(certificateId) { + return getCourses().find((course) => course.certificateId === certificateId) || null; +} + +export function getCourseByKey(publicCourseKey) { + return getCourses().find((course) => course.publicCourseKey === publicCourseKey) || null; +} + +export function getCourseCatalog() { + return getCourses().map((course) => ({ + courseId: course.courseId, + publicCourseKey: course.publicCourseKey, + certificateId: course.certificateId, + title: course.title, + slug: course.slug, + })); +} + +export function getSemanticAttrs(course, pageType) { + const view = getSurfaceView(course, pageType); + + return { + "data-gbv-semantic": "true", + "data-gbv-course-id": String(course?.courseId || ""), + "data-gbv-course-name": view.courseName, + "data-gbv-course-key": view.courseKey, + "data-gbv-course-slug": view.courseSlug, + "data-gbv-certificate-id": view.certificateId, + "data-gbv-grade-percent": view.gradePercent ?? "", + "data-gbv-progress-percent": view.progressPercent ?? "", + "data-gbv-module-count": view.moduleCount ?? "", + }; +} diff --git a/apps/client/synthetic/src/pages/_app.js b/apps/client/synthetic/src/pages/_app.js new file mode 100644 index 0000000..b97e52f --- /dev/null +++ b/apps/client/synthetic/src/pages/_app.js @@ -0,0 +1,5 @@ +import "@/styles/globals.css"; + +export default function App({ Component, pageProps }) { + return ; +} diff --git a/apps/client/synthetic/src/pages/_document.js b/apps/client/synthetic/src/pages/_document.js new file mode 100644 index 0000000..628a733 --- /dev/null +++ b/apps/client/synthetic/src/pages/_document.js @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from "next/document"; + +export default function Document() { + return ( + + + +
+ + + + ); +} diff --git a/apps/client/synthetic/src/pages/api/gbv/courses.js b/apps/client/synthetic/src/pages/api/gbv/courses.js new file mode 100644 index 0000000..2fb695b --- /dev/null +++ b/apps/client/synthetic/src/pages/api/gbv/courses.js @@ -0,0 +1,8 @@ +import { getCourseCatalog } from "@/lib/data"; + +export default function handler(_req, res) { + res.status(200).json({ + ok: true, + courses: getCourseCatalog(), + }); +} diff --git a/apps/client/synthetic/src/pages/c/[publicCourseKey]/assignments.js b/apps/client/synthetic/src/pages/c/[publicCourseKey]/assignments.js new file mode 100644 index 0000000..9890020 --- /dev/null +++ b/apps/client/synthetic/src/pages/c/[publicCourseKey]/assignments.js @@ -0,0 +1,38 @@ +import AppShell from "@/ui/AppShell"; +import CourseHero from "@/ui/CourseHero"; +import AssignmentsTable from "@/ui/AssignmentsTable"; +import ActivityPanel from "@/ui/ActivityPanel"; +import { getCourseByKey, getSemanticAttrs, getSurfaceView } from "@/lib/data"; + +export default function Assignments({ course }) { + if (!course) { + return ( + +
+ Course not found. +
+
+ ); + } + + const view = getSurfaceView(course, "assignments"); + + return ( + +
+ + + ); +} + +export async function getServerSideProps(ctx) { + const course = getCourseByKey(ctx.params.publicCourseKey); + return { props: { course } }; +} diff --git a/apps/client/synthetic/src/pages/c/[publicCourseKey]/index.js b/apps/client/synthetic/src/pages/c/[publicCourseKey]/index.js new file mode 100644 index 0000000..43a2bea --- /dev/null +++ b/apps/client/synthetic/src/pages/c/[publicCourseKey]/index.js @@ -0,0 +1,86 @@ +import AppShell from "@/ui/AppShell"; +import CourseHero from "@/ui/CourseHero"; +import ModulesList from "@/ui/ModulesList"; +import ActivityPanel from "@/ui/ActivityPanel"; +import { getCourseByKey, getSemanticAttrs, getSurfaceView } from "@/lib/data"; + +export default function CourseHome({ course }) { + if (!course) { + return ( + +
+ Course not found. +
+
+ ); + } + + const view = getSurfaceView(course, "course"); + + return ( + +
+ + + ); +} + +export async function getServerSideProps(ctx) { + const course = getCourseByKey(ctx.params.publicCourseKey); + return { props: { course } }; +} diff --git a/apps/client/synthetic/src/pages/c/[publicCourseKey]/module/[n].js b/apps/client/synthetic/src/pages/c/[publicCourseKey]/module/[n].js new file mode 100644 index 0000000..919fcc9 --- /dev/null +++ b/apps/client/synthetic/src/pages/c/[publicCourseKey]/module/[n].js @@ -0,0 +1,62 @@ +import AppShell from "@/ui/AppShell"; +import CourseHero from "@/ui/CourseHero"; +import ModulesList from "@/ui/ModulesList"; +import { getCourseByKey, getSemanticAttrs } from "@/lib/data"; + +export default function Module({ course, n }) { + if (!course) { + return ( + +
+ Course not found. +
+
+ ); + } + + return ( + +
+ + + ); +} + +export async function getServerSideProps(ctx) { + const course = getCourseByKey(ctx.params.publicCourseKey); + return { props: { course, n: ctx.params.n } }; +} diff --git a/apps/client/synthetic/src/pages/certificate/[certificateId].js b/apps/client/synthetic/src/pages/certificate/[certificateId].js new file mode 100644 index 0000000..d1a9345 --- /dev/null +++ b/apps/client/synthetic/src/pages/certificate/[certificateId].js @@ -0,0 +1,139 @@ +import Link from "next/link"; +import AppShell from "@/ui/AppShell"; +import { getCourseByCert, getCourses, getSemanticAttrs, getSurfaceView } from "@/lib/data"; + +function OtherCertificates({ currentCert, courses }) { + const others = courses.filter((course) => course.certificateId !== currentCert).slice(0, 3); + + return ( +
+
+
+
Other certificates
+
Multiple artifacts are available.
+
+ + All + +
+ +
+ {others.map((course) => ( + +
{course.title}
+
{course.org}
+ + ))} +
+
+ ); +} + +export default function CertificatePage({ certificateId, course, courses }) { + if (!course) { + return ( + +
+
Certificate not found
+
+ No certificate exists for ID {certificateId}. +
+
+ + Go to Milestones + + + Back to Hub + +
+
+
+ ); + } + + const view = getSurfaceView(course, "certificate"); + + return ( + +
+ + + ); +} + +export async function getServerSideProps(ctx) { + const certificateId = ctx.params.certificateId; + const course = getCourseByCert(certificateId); + const courses = getCourses(); + return { props: { certificateId, course, courses } }; +} diff --git a/apps/client/synthetic/src/pages/hub.js b/apps/client/synthetic/src/pages/hub.js new file mode 100644 index 0000000..d482ca4 --- /dev/null +++ b/apps/client/synthetic/src/pages/hub.js @@ -0,0 +1,71 @@ +import AppShell from "@/ui/AppShell"; +import CourseCard from "@/ui/CourseCard"; +import { getCourses } from "@/lib/data"; + +export default function Hub({ courses }) { + return ( + +
+
+
+
+
Welcome back
+
+ Pick up where you left off. +
+
+
+ + +
+
+ +
+
+
Courses
+
+ {courses.length} +
+
+ In this workspace +
+
+
+
Streak
+
+ 5 days +
+
Keep it going
+
+
+
Next milestone
+
+ 2 modules +
+
+ Until your next badge +
+
+
+
+ +
+ {courses.map((c) => ( + + ))} +
+
+
+ ); +} + +export async function getServerSideProps() { + const courses = getCourses(); + return { props: { courses } }; +} diff --git a/apps/client/synthetic/src/pages/index.js b/apps/client/synthetic/src/pages/index.js new file mode 100644 index 0000000..d18dc3e --- /dev/null +++ b/apps/client/synthetic/src/pages/index.js @@ -0,0 +1,23 @@ +import Link from "next/link"; + +export default function Home() { + return ( +
+

+ Welcome to the Synthetic Learning Environment +

+

+ This is a public demo UI for verification. Not all features are + available. Information is static. +

+
+ + Open + +
+
+ ); +} diff --git a/apps/client/synthetic/src/pages/milestones.js b/apps/client/synthetic/src/pages/milestones.js new file mode 100644 index 0000000..09b883f --- /dev/null +++ b/apps/client/synthetic/src/pages/milestones.js @@ -0,0 +1,70 @@ +import Link from "next/link"; +import AppShell from "@/ui/AppShell"; +import { getCourses, getSurfaceView } from "@/lib/data"; + +function RecordRow({ c }) { + const view = getSurfaceView(c, "milestones"); + + return ( +
+
+
+
+ {c.title} +
+
{c.org}
+ +
+ + {view.gradeText} + +
+
+ +
+ + Create Certificate + + + View + +
+
+
+ ); +} + +export default function Milestones({ courses }) { + return ( + +
+
+
Your records
+
+ All certificates and completions in one place. +
+
+ +
+ {courses.map((c) => ( + + ))} +
+
+
+ ); +} + +export async function getServerSideProps() { + const courses = getCourses(); + return { props: { courses } }; +} diff --git a/apps/client/synthetic/src/pages/proof/[certificateId].js b/apps/client/synthetic/src/pages/proof/[certificateId].js new file mode 100644 index 0000000..7b5c4bb --- /dev/null +++ b/apps/client/synthetic/src/pages/proof/[certificateId].js @@ -0,0 +1,140 @@ +import Link from "next/link"; +import AppShell from "@/ui/AppShell"; +import { getCourseByCert, getCourses, getSemanticAttrs, getSurfaceView } from "@/lib/data"; + +function SimilarCourses({ currentCert, courses }) { + const others = courses.filter((course) => course.certificateId !== currentCert).slice(0, 3); + + return ( +
+
+
+
Other records
+
Additional artifacts from the same platform.
+
+ + View all + +
+ +
+ {others.map((course) => ( + +
{course.title}
+
+ {course.org} | cert {course.certificateId} +
+ + ))} +
+
+ ); +} + +export default function ProofPage({ certificateId, course, courses }) { + if (!course) { + return ( + +
+
Record not found
+
+ We could not find a certificate with ID {certificateId}. +
+
+ + Go to Milestones + + + Back to Hub + +
+
+
+ ); + } + + const view = getSurfaceView(course, "proof"); + + return ( + +
+