Thanks for your interest in PDPP. This project is a protocol plus a forkable reference implementation, so contributions range from protocol-text edits to docs and the public site.
Two kinds of contribution now start in a different repository:
- Connector source begins in
PDP-Connect/data-connectors. This repository keeps a retained frozen copy underpackages/polyfill-connectors/connectors/. A pull request that changes those files fails the write-freeze guard in.github/workflows/polyfill-connectors.yml, unless it carries theconnector-write-freeze-overridelabel. Fixtures, docs, and scripts are not frozen. For the runnable mechanics — how to run a connector, validate manifests, and what the registry currently contains — seepackages/polyfill-connectors/CONNECTORS.md. - Reference-implementation code lives under
reference-implementation/; console code lives underapps/console/, both inPDP-Connect/data-connect.
This guide applies to human and AI contributors alike. It distills the durable conventions for working in this repo: the spec-first workflow for protocol and contract changes, test expectations, and pull-request conventions.
-
No personal data in the repo. Do not commit private names, personal handles, private absolute paths, or third-party personal references in docs, code, fixtures, or reports. Use role-neutral labels (
the owner,an operator,your-pdpp-host.example.com) unless a real value is explicitly approved. -
Connector fixtures are opt-in, and raw captures never leave your machine. Capturing live connector runs (
PDPP_CAPTURE_FIXTURES=1) is something you choose to do locally; nothing captures or commits automatically. Raw captures underfixtures/<connector>/raw/are gitignored and must never be committed. Turning a capture into a contributed fixture is a deliberate, separate act: run the scrubber (bin/scrub-fixtures.ts), then review the scrubbed output yourself as a human before committing it — the deterministic scrubber is a conservative first pass, not a guarantee, and DOM/HTML captures in particular can carry unredacted names. Seepackages/polyfill-connectors/docs/connector-authoring-guide.mdfor the full scrub-and-review workflow. -
Respect the authority order. This repo has a strict authority order:
- Root PDPP specs (
spec-*.md) define normative protocol semantics. - Code and tests define what the current reference implementation actually does.
- OpenSpec (
openspec/) defines project-level architecture and change planning.
Public web spec pages are downstream copies of the root specs;
pnpm spec:checkenforces parity. OpenSpec is project-scoped and does not replace or compete with the normative PDPP specs. - Root PDPP specs (
-
Mind the voice. Before writing or editing prose in any spec, design note, README, site copy, operator/dashboard string, or release note, read
docs/reference/voice-and-framing.md. It keeps PDPP-as-protocol above OAuth/RAR, separates Core from Collection Profile from reference implementation from operator console, and lists phrasings to avoid. -
Run the spec prose gate. Edits to a root
spec-*.mdare checked in two passes.pnpm spec:proserunsscripts/spec-prose-lint.mjs, which fails on six mechanical faults: prose hard-wrapped mid-sentence, a lowercase RFC 2119 key word governing a protocol role (RFC 8174 gives those no normative force), a capitalized key word inside a note (notes are informative, so the requirement would be unenforceable), a sentence over 40 words, filler and meta-commentary, and a paragraph duplicated within the file.pnpm spec:prose:fixrejoins hard-wrapped prose and changes nothing else; the other five faults need a decision about meaning, so the tool will not guess. Thespec-prose-gateGitHub Actions job runs the same linter on the spec files a pull request touches, as a ratchet:hard-wrapalways fails, becausespec:prose:fixrepairs it mechanically and changes no word, but the five judgment rules fail only on a finding your pull request introduced. The job lints the same files as of the merge-base to build a baseline, and prints a finding already in that baseline as a warning. Sopnpm spec:proserun by hand reports more than the job fails on — the specs carry pre-existing findings of every judgment kind, and clearing one means rewriting a normative sentence, which is a spec editor's decision that needs its own review rather than a blocker on unrelated work. Fix what you introduce; leave the backlog to a dedicated editorial pass. The second pass is a judged read againstdocs/reference/spec-writing-rubric.md, ten criteria — each with a one-line test and the standard it comes from — covering what a regex cannot decide, such as whether an obligation names the role that bears it and whether a registry section documents its registration policy.
Building with AI is welcome. If AI helped meaningfully, add Assisted-by: AI
to each assisted commit and as a standalone final line in the pull request
description so reviewers can calibrate their scrutiny; a local hook can add the
commit trailer for you (.github/hooks/prepare-commit-msg). It is
vendor-neutral and sits alongside your DCO sign-off. We review contributions on
whether they are good, not how they were made.
This repo is spec-driven. When you are asked to design, plan, refactor, or introduce a non-trivial feature, write it as an OpenSpec change before writing code, and keep the two in lockstep afterward.
Read openspec/README.md before non-trivial planning
work — it is the local rulebook for the OpenSpec lifecycle, closeout, and the
design-note intake lane.
Write a change proposal when any of these are true:
- You are introducing a new capability, new dependency, or new architectural boundary.
- You are changing a durable contract (schemas, wire formats, endpoints, grant shapes, manifest fields).
- You are modifying behavior a reviewer (standards body, a forker, a future you) should be able to audit after the fact.
- The request asks you to "write it up," "propose," "plan," "design," or "spec it out."
In one line: OpenSpec is for design and contract decisions — the choices a future reviewer, forker, or standards body must be able to audit.
Do not open a change for work that carries no durable design decision:
- Typos, comment edits, formatting, or a one-line bug fix.
- A localized refactor or test tweak that preserves behavior and touches no contract.
- Dependency bumps, lockfile churn, or CI/tooling config with no protocol impact.
- Anything you could describe as "just fix it" without needing to explain a tradeoff.
For those, skip OpenSpec and just do the work — a stray proposal for a minor fix is noise that dilutes the changes that actually record a decision.
Changes live under openspec/changes/<change-name>/:
proposal.md— short.## Why,## What Changes,## Capabilities(Modified / Added / Removed),## Impact. State facts, no novel prose.design.md— rationale, alternatives considered, what is and isn't in scope, acceptance checks. This is where you show the thinking.tasks.md— numbered sections with checkbox items, each small enough for one commit. Include an "Acceptance checks" section with reproducible steps.specs/<capability>/spec.md— the capability-spec delta, using## ADDED Requirements,## MODIFIED Requirements,## REMOVED Requirements. Every Requirement needs at least one#### Scenario:with**WHEN** / **THEN**phrasing. Requirements are normative (SHALL,SHALL NOT); scenarios are evidence. Do not put task lists in spec files.
Capability names mirror existing folders under openspec/specs/. Prefer
updating an existing capability over minting a new one. If you are proposing
multiple loosely related things, split them into separate changes.
Always run before handing back:
openspec validate <change-name> --strictA valid change is the minimum bar; an invalid change is not ready for review.
If you change the protocol or a durable contract, update
openspec/specs/<capability>/spec.md via a proper delta. Drive-by edits to
capability spec files are not OK. When a change is fully implemented and
accepted, its Requirement deltas fold into the durable specs/<cap>/spec.md
and the change folder moves to openspec/changes/archive/ — don't archive
work yourself unless asked.
A change's tasks.md is for implementation work a contributor can finish
and tick as it lands — code, tests, spec deltas. It is not an issue tracker.
Post-merge steps that are not part of the change's implementation — a live
production deploy, an acceptance pass, a migration to run against real data — do
not belong as open checkboxes in tasks.md. Those boxes rarely get ticked
(they depend on a deployment, not the merge), so they leave the change
pseudo-active forever and the folder rots. Record such residual work in the
issue tracker and, per openspec/README.md's closeout checklist, note it as
a residual risk in the change — then let the change be archived. A ✓ Complete
change should not stay active more than one merge cycle just because a live
follow-up step is outstanding.
To catch changes that have effectively landed but are still sitting active, run:
pnpm openspec:archive-checkIt lists changes under openspec/changes/ that look archive-due — all
implementation tasks done, or the code they reference already exists on main.
It's a non-blocking reminder (also wired as a pre-push warning and a
report-only CI job), not a gate: folding a change's spec deltas into
openspec/specs/ and archiving it is a maintainer step, so the check never
fails a build or a push.
Install dependencies with pnpm install from the repo root (this is a pnpm
workspace).
Common commands:
pnpm dev # reference AS/RS + operator console
pnpm reference-implementation:server # reference server only
pnpm reference-implementation:cli --help
pnpm reference-implementation:test # reference implementation tests
pnpm spec:check # root-spec / web-spec parityUse pnpm test:scratch -- <command> [args...] for supported ad hoc test or
host-writing verification commands. Canonical repository test aliases and
reviewed CI test commands enter this boundary automatically. It creates one
private, command-scoped host scratch root, passes it to ordinary descendants
through the normal temporary-directory environment, and removes the verified
root after normal completion, failure, SIGINT, or SIGTERM.
The wrapper preserves the exact child exit code and signal semantics. If a successful child leaves a root that cannot be verified and removed, the wrapper fails with infrastructure code 74 rather than reporting success. SIGKILL, OOM, host failure, detached descendants, container/service state, explicit external storage roots, and hard-coded paths that are not part of a reviewed migration remain outside the immediate-cleanup guarantee; verified stale roots are recovered conservatively by a later owner.
A raw node --test, tsx file.test.ts, or direct shell command is an
intentional bypass unless you invoke it through pnpm test:scratch -- ....
Do not set a global login-shell TMPDIR or delete ambient /tmp/TMPDIR to
compensate. Keep per-test t.after()/try/finally cleanup for prompt local
release, and do not move reviewed container paths or the shared dynamic n.eko
flock into invocation scratch.
See the self-host quickstart for the
full local and Docker workflows, and the
reference implementation README
in PDP-Connect/data-connect for the reference-implementation workflows.
The testing policy defines oracle selection,
fixture and live-service rules, profile-gated evidence, test changes, and the
coverage/mutation/selection strategy. test-accounting.manifest.json, runners,
and CI remain the executable suite authority.
- Branch and PR. All changes to protocol text, the reference implementation, and the site go through public pull requests. Non-trivial protocol, reference-contract, or architecture changes are tracked with an OpenSpec change before implementation.
- Conventional Commits. Commit messages follow
Conventional Commits.
fix:creates a patch release andfeat:creates a minor release; commits that do not follow the format do not release. Breaking-change markers are reserved for the intentional 1.0 milestone. Seedocs/reference/package-release-policy.md. - Keep specs and code in lockstep. If your PR implements an OpenSpec change, the change artifacts and the code should land together and stay consistent.
- CI and merge gate. Every pull request must pass the required CI checks.
Before pushing, run the focused tests and signoff gates required for the
changed behavior; run
pnpm spec:checkwhen root or public spec parity is in scope. See the testing policy anddocs/reference/ci-mode.md.
Contributions to this project require a Developer Certificate of Origin sign-off. The DCO is a lightweight statement that you wrote the contribution or otherwise have the right to submit it under the project's licenses. It is not a copyright assignment. The full text is at developercertificate.org.
To sign off, add a Signed-off-by trailer to each commit message with your real
name and email:
Signed-off-by: Jane Doe <jane.doe@example.com>
Git adds this automatically when you commit with the -s flag:
git commit -s -m "feat: add the thing"The name and email in the sign-off must match the commit author. By signing off,
you certify the statements in the DCO for that contribution. Pull requests whose
commits are not signed off will be asked to add the sign-off before merge (you
can amend existing commits with git rebase --signoff <base>).
This project follows the Contributor Covenant. By
participating, you agree to uphold it. Report unacceptable behavior through the
private channel described in CODE_OF_CONDUCT.md.
Maintainers and their scopes are listed in MAINTAINERS.md.
For root protocol specifications, active maintainers act as editors for the
current draft. Maintainer changes are proposed through pull request. This
project is developed at PDP-Connect, a lab at LF Decentralized Trust; see
the "Governance & stewardship" section of the README for the
stewardship model, and GOVERNANCE.md for the programme.
Ask whether a piece of work is change-worthy before inventing scope. "I'd draft
this as an OpenSpec change add-X — yes?" is a one-line question that saves a
rewrite.