From 6864bed58de3c1fa60a8d00717d1ad7da013034e Mon Sep 17 00:00:00 2001 From: LKSNDRTMLKV Date: Thu, 20 Aug 2026 23:06:24 +0200 Subject: [PATCH 1/4] docs: correct the README's claim that the type-checker checks links, and document the staging workflow and CI gates --- README.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6e54162..5646e8c 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ dpp-web/ ├── pnpm-lock.yaml # single lockfile for the whole workspace ├── README.md # this file │ -├── public/brand/ # canonical brand assets (marks, favicon, og) +├── scripts/ # CI gates: link crawler, leakage scan │ ├── packages/ │ └── brand-tokens/ # @odal/brand-tokens — colour, type, spacing @@ -66,10 +66,21 @@ pnpm dev:docs # http://localhost:4321 → site/dpp-docs # Build for production (same command Cloudflare runs) pnpm -r build -# Type-check + broken-link check +# Type-check templates and content-collection references pnpm -r check + +# The gates CI runs. All four need a build first, except the leakage scan. +pnpm run check:links # crawl both dist trees for internal links that 404 +pnpm run check:leakage # internal vocabulary / private-repo paths, incl. public/ +pnpm run check:openapi # vendored API spec still matches its pinned engine commit +pnpm audit --audit-level critical ``` +`pnpm -r check` does **not** check links, and never did — a markdown link target is an opaque +string to `astro check`. That is why `check:links` exists separately and reads the built output +rather than the source: four `[Licensing](/engine/licensing)` links once passed `check` and +404'd in production. + Prerequisites: Node.js 22.13+ (LTS 24 recommended — pnpm 11 requires `node:sqlite`, unavailable before 22.13) and pnpm (managed via [corepack](https://nodejs.org/api/corepack.html) — the exact version is pinned in `package.json` `packageManager`). --- @@ -88,7 +99,21 @@ The relationship between the repositories — the open-core boundary, the depend ## Status -The original phased build (workspace foundations → landing MVP → docs IA → polish) is complete through its first three phases, and the **June 2026 redesign** re-skinned both sites onto the navy/ice brand, replaced retired messaging with *"Signed by you. Verified by anyone."*, moved editable content into data files, and corrected stale claims. What remains before public launch: the Lighthouse/a11y pass and deployment. `LICENSE` is settled (Apache-2.0) and CI (`.github/workflows/ci.yml`, gating `pnpm -r build` + `pnpm -r check` on every push/PR) is in place. +The original phased build (workspace foundations → landing MVP → docs IA → polish) is complete through its first three phases, and the **June 2026 redesign** re-skinned both sites onto the navy/ice brand, replaced retired messaging with *"Signed by you. Verified by anyone."*, and moved editable content into data files. `LICENSE` is settled (Apache-2.0). + +An **August 2026 audit** of both sites read every published page against primary regulatory text and against the engine's source. It found a delegated act that does not exist described as adopted, roughly twenty misattributed citations, four security-property claims the code contradicted, and a registry described as unbuilt eight months after it went live. Those are corrected; the findings register lives outside this repository. + +What remains before public launch: the Lighthouse/axe pass, a runtime check that the API reference does not relay requests through a third-party proxy, and a named data controller in the privacy policy — which is blocked on a registered entity existing, not on a copy edit. + +## How changes land + +`main` is what Cloudflare Pages publishes, so nothing lands on it directly. + +- **`staging`** is the integration branch. Work branches off it and merges back through a pull request. +- Promotion is a second pull request, `staging` → `main`, reviewed on its own. +- `main` carries a ruleset matching the other repositories: pull request required, squash-only, CI must pass, no force-push, no deletion, and **no bypass for anyone** — including the owner. + +CI (`.github/workflows/ci.yml`) runs build, type-check, and the four gates listed above on every push and pull request, with the workflow token scoped to `contents: read`. --- From 27eae1d1eb7ca202df989980f059592b3ada33b1 Mon Sep 17 00:00:00 2001 From: LKSNDRTMLKV Date: Thu, 20 Aug 2026 23:06:36 +0200 Subject: [PATCH 2/4] docs: point the README at the pages that exist rather than at a removed design section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5646e8c..73b8aba 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ The [`dpp-core`](https://github.com/odal-node/dpp-core) repository (Apache-2.0) The [`dpp-engine`](https://github.com/odal-node/dpp-engine) repository (BSL-1.1, with a production self-host grant) holds the deployment layer — HTTP services, persistence, authentication, telemetry, the public resolver, the Wasm plugin sandbox. The docs site documents `dpp-engine`; it does not contain its source. -The relationship between the repositories — the open-core boundary, the dependency direction, the licensing rationale — is covered on the docs site under [Design Principles](https://docs.odal-node.io/design/open-core) and in the parent project's strategy documents. +The relationship between the repositories — the open-core boundary, the dependency direction, the licensing rationale — is covered on the docs site under [Core Concepts](https://docs.odal-node.io/core-concepts) and [Licensing](https://docs.odal-node.io/getting-started/licensing), and in the parent project's strategy documents. --- From 7859a03664615d13ea7b5ececade11d583d488e9 Mon Sep 17 00:00:00 2001 From: LKSNDRTMLKV Date: Thu, 20 Aug 2026 23:07:03 +0200 Subject: [PATCH 3/4] docs: describe deploys as promotions into main rather than direct pushes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73b8aba..ec0de87 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Each Astro project has its own `package.json` and its own Cloudflare Pages proje ## Independent Deploys, Shared Brand -The architectural commitment of this repository is *independence at the deployment layer, coherence at the brand layer*. Pushing to `main` with a change that only touches `site/dpp-docs/src/content/docs/quick-start.mdx` produces a single docs deploy and zero landing deploys. Pushing a change that touches `packages/brand-tokens/` produces two deploys, because a token change genuinely should re-render both surfaces. This is enforced via Cloudflare Pages [build watch paths](https://developers.cloudflare.com/pages/configuration/build-watch-paths/) rather than at the Git layer. +The architectural commitment of this repository is *independence at the deployment layer, coherence at the brand layer*. A promotion into `main` that only touches `site/dpp-docs/src/content/docs/quick-start.mdx` produces a single docs deploy and zero landing deploys. One that touches `packages/brand-tokens/` produces two deploys, because a token change genuinely should re-render both surfaces. This is enforced via Cloudflare Pages [build watch paths](https://developers.cloudflare.com/pages/configuration/build-watch-paths/) rather than at the Git layer. --- From 5a6a51c649533bada67a288d9599f58ad026f2ea Mon Sep 17 00:00:00 2001 From: LKSNDRTMLKV Date: Fri, 21 Aug 2026 02:03:36 +0200 Subject: [PATCH 4/4] ci: pin every action to a commit SHA, and add the API-spec sync workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A floating tag is mutable. Whoever controls the action repository can repoint v4 at new code, which then runs with whatever permissions the job holds. sync-openapi.yml holds contents: write and pull-requests: write, so it is the highest-value job here to pin — but ci.yml is pinned too, since a compromised action in a read-only job still sees the runner. Pins are v7 rather than the current v4 tip, for two reasons. dpp-engine already pins actions/checkout at 3d3c42e5, the same commit v7 resolves to, so the two public repos now reference an identical SHA rather than merely matching in style. And because a pinned SHA never moves, pinning v4's tip would freeze this repo on a maintenance line permanently; freezing on the current line is the better resting place. The majors were checked against what these workflows actually do, not assumed. checkout still defaults persist-credentials to true, and v6's move of those credentials into a file under RUNNER_TEMP leaves git push working unchanged — which sync-openapi.yml depends on when it pushes its branch; that change's one caveat, Docker container actions, applies to neither workflow. v7's new fork-PR block only affects pull_request_target and workflow_run, and neither is a trigger here. setup-node's v5/v6 automatic caching only ever applies to npm, and both jobs already pass cache: pnpm explicitly with corepack enable ordered ahead of it. deploy.yml needed no change: it uses no actions, only run steps. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 6 +- .github/workflows/sync-openapi.yml | 161 +++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/sync-openapi.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e84d00e..011d690 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # The vendored OpenAPI spec is a copy of the engine's. Without the source # beside it there is nothing to compare against, and a drift check that @@ -26,7 +26,7 @@ jobs: # openapi-source.json rather than at whatever is currently on main. See # that script's header for why the pin exists. - name: Checkout dpp-engine (source of the vendored API spec) - uses: actions/checkout@v4 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: odal-node/dpp-engine path: .dpp-engine @@ -37,7 +37,7 @@ jobs: run: corepack enable - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: 24.18.0 cache: pnpm diff --git a/.github/workflows/sync-openapi.yml b/.github/workflows/sync-openapi.yml new file mode 100644 index 0000000..2500830 --- /dev/null +++ b/.github/workflows/sync-openapi.yml @@ -0,0 +1,161 @@ +name: Sync API spec pin + +# Proposes a bump of the vendored OpenAPI spec. It opens a pull request; it +# never pushes to main, and it never turns an unrelated pull request red. +# +# WHY A PULL REQUEST AND NOT A GATE +# +# site/dpp-docs/scripts/sync-openapi.mjs compares the vendored copy against the +# spec at the commit recorded in openapi-source.json — deliberately not against +# whatever is currently on the engine's main. Pinning is what keeps this repo's +# CI deterministic: an unrelated merge in the engine cannot redden a pull +# request here, and a correction that must land in both repos does not deadlock +# on which one merges first. See that script's header. +# +# This job does not change that. The pinned-commit check in ci.yml is untouched +# and remains the gate. All this does is notice the pin has fallen behind and +# put the bump in front of a human as a reviewable diff — which is what the +# "reviewable line in a diff" in openapi-source.json's comment describes, done +# on a schedule instead of from memory. + +on: + schedule: + # 03:17 UTC — off the hour, so this is not queued behind everything else + # that runs at midnight. + - cron: '17 3 * * *' + workflow_dispatch: + +# Scoped on the job, not the workflow, and this is the only workflow in the repo +# that writes anything. +permissions: + contents: read + +jobs: + sync: + name: Bump the vendored API spec + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + # Full history: the sync script reads the spec out of the engine's git + # objects, and records the commit it copied from. + - name: Checkout dpp-engine (source of the vendored API spec) + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + repository: odal-node/dpp-engine + path: .dpp-engine + fetch-depth: 0 + persist-credentials: false + + - name: Enable Corepack + run: corepack enable + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 + with: + node-version: 24.18.0 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Sync the vendored spec and its pin + run: pnpm run sync:openapi + env: + DPP_ENGINE_DIR: ${{ github.workspace }}/.dpp-engine + + - name: Is there anything to propose? + id: diff + run: | + if git diff --quiet -- site/dpp-docs/public/openapi.yaml site/dpp-docs/openapi-source.json; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "The pin is already current. Nothing to propose." + else + echo "changed=true" >> "$GITHUB_OUTPUT" + git --no-pager diff --stat -- site/dpp-docs/public/openapi.yaml site/dpp-docs/openapi-source.json + fi + + # The same checks ci.yml runs, executed here rather than trusted to run + # later. A pull request opened with GITHUB_TOKEN does not trigger workflow + # runs — GitHub blocks that to stop workflows recursing — so without this + # the bump would arrive with no evidence attached. If SYNC_PAT is + # configured the pull request does get a normal CI run as well, and this + # step is then belt and braces. + - name: Verify the bumped copy builds and passes its own checks + if: steps.diff.outputs.changed == 'true' + run: | + pnpm run check:openapi + pnpm -r build + pnpm -r check + pnpm run check:links + pnpm run check:leakage + env: + DPP_ENGINE_DIR: ${{ github.workspace }}/.dpp-engine + + # A PAT makes the pull request trigger CI like a human's would, which + # matters because main requires passing checks and GITHUB_TOKEN pull + # requests never get them. Without the secret this still works — the + # request is opened, and a reviewer closing and reopening it starts CI. + - name: Choose a token + if: steps.diff.outputs.changed == 'true' + id: token + env: + SYNC_PAT: ${{ secrets.SYNC_PAT }} + run: | + if [ -n "$SYNC_PAT" ]; then + echo "ci_runs=true" >> "$GITHUB_OUTPUT" + else + echo "ci_runs=false" >> "$GITHUB_OUTPUT" + echo "::warning::SYNC_PAT is not configured. The pull request will open but CI will not run on it automatically; close and reopen it to start a run." + fi + + # A fixed branch, force-updated. A second night's bump supersedes the + # first rather than opening a second request against the same file. + - name: Open or update the pull request + if: steps.diff.outputs.changed == 'true' + env: + GH_TOKEN: ${{ secrets.SYNC_PAT || github.token }} + CI_RUNS: ${{ steps.token.outputs.ci_runs }} + run: | + set -euo pipefail + BRANCH=chore/sync-openapi-pin + PIN=$(node -p "require('./site/dpp-docs/openapi-source.json').commit") + SHORT=${PIN:0:9} + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + git add site/dpp-docs/public/openapi.yaml site/dpp-docs/openapi-source.json + git commit -m "chore(docs): bump the vendored API spec to ${SHORT}" + git push --force origin "$BRANCH" + + if gh pr view "$BRANCH" --json number >/dev/null 2>&1; then + echo "Pull request already open for $BRANCH; the force-push updated it." + exit 0 + fi + + cat > /tmp/pr-body.md < CI does not run automatically on a pull request opened with the default token. Close and reopen this request to start a run.\n' >> /tmp/pr-body.md + fi + + gh pr create \ + --base main \ + --head "$BRANCH" \ + --title "chore(docs): bump the vendored API spec to ${SHORT}" \ + --body-file /tmp/pr-body.md