Skip to content

chore: relicense to BUSL-1.1 + publish the receipt spec (Signetry goes open core) - #107

Merged
bkd-dotcom merged 2 commits into
mainfrom
open-core-relicense
Sep 1, 2026
Merged

chore: relicense to BUSL-1.1 + publish the receipt spec (Signetry goes open core)#107
bkd-dotcom merged 2 commits into
mainfrom
open-core-relicense

Conversation

@bkd-dotcom

Copy link
Copy Markdown
Member

Relicenses signetry-core from "All Rights Reserved" to BUSL-1.1, and publishes
the receipt format as an Apache-2.0 specification with a conformance suite.

The licence line

This repo (the engine) BUSL-1.1 → Apache-2.0 on 2030-08-31
Everything you integrate with Apache-2.0 (action, plugins, precommit, eval, codex, claude-code, cursor, github-app)
The receipt format + its tests Apache-2.0, and explicitly excluded from the BUSL Licensed Work

Under BUSL you may read the source, run it in your own CI, use it in production to
govern repositories you or your organization control, fork it, patch it, and publish
those patches. The one carve-out is offering it to third parties as a paid, competing
hosted governance service. Every released version carries its own four-year clock, so
the version you install today is guaranteed Apache-2.0 four years from its publication
whatever happens to this project.

The receipt spec is the substantive half of this PR

A receipt is only worth something if it can be verified by someone who does not have
this tool, does not trust it, and is reading it years later. That makes the format an
interface, so this PR writes it down and tests it as one.

  • docs/RECEIPT_SPEC.md — the v1 format in full: envelope, payload,
    canonicalization, signing, the verification algorithm, the §4.3 invariants, a change
    contract, and a threat-model table. RFC 2119 throughout.
  • tests/conformance/ — 7 committed JSON vectors, 17 assertions, and published
    test-key seed strings so an implementation in any language can be checked
    against exactly the same files. Vectors are committed rather than computed at test
    time: a change to canonicalization or signing then shows up as a reviewable diff
    instead of silently redefining what "correct" means.
  • LICENSE's Licensed Work parameter now names both as exclusions, licensed
    under LICENSE-Apache-2.0.txt with no restriction and no Change Date. Writing a
    competing issuer or an independent verifier is a supported use, not a tolerated one.

Two of the vectors are the point

A verifier that only checks cryptography passes most of the suite and fails these:

  • resigned-other-key.json — an envelope signed with the attacker's own keypair,
    embedded public key swapped to match. Hash matches, signature verifies against
    envelope.public_key, nothing malformed. It MUST still fail, because verification
    is against a key you obtained some other way (§8.1). An implementation that trusts
    the embedded key reports this forgery as valid.
  • auto-merge-true.json — correctly signed, and non-conforming, because
    auto_merge: false and human_review_required: true are invariants inside the
    signed payload
    . They can't be flipped or dropped without breaking the signature —
    but that only becomes a guarantee if the verifier checks them.

So this PR makes the verifier check them:

  • check_invariants(receipt) (new, exported from signetry_core.pipeline) returns
    the §4.3 violations.

  • verify_receipt now returns conforming and invariant_violations alongside its
    cryptographic result — reported separately, because "is the signature genuine" and
    "does this payload claim something the format forbids" are different questions.

  • signetry verify exits non-zero on a non-conforming receipt, not only an
    unverifiable one, and never prints a bare VERIFIED when an invariant is broken:

    $ signetry verify receipt.json --public-key "$KEY"
    VERIFIED  (issued_by_signetry=True, hash_matches=True)
    NON-CONFORMING  — auto_merge must be false (RECEIPT_SPEC §4.3)
    REJECTED  — signature is genuine, but this is not a conforming receipt.
    $ echo $?
    1
    

    That is what turns "Signetry never merges on its own judgement" from a sentence in a
    README into a checkable property of every receipt.

One substantive legal change, please read

The CLA's fallback grant was exclusive; it is now non-exclusive. §2 assigns
copyright to the Owner, and where law does not permit assignment it falls back to a
licence grant. An exclusive fallback could be read as stripping a contributor of the
right to use their own code — never the intent. §2 now also says so in plain terms.

LICENSING.md (in the umbrella repo) describes the CLA honestly: it is a copyright
assignment, and if that is not acceptable to you, say so on the issue before you start
— a DCO-only path for small fixes is a reasonable thing to ask for.

Contract compliance

core governs its own PRs. Verified empirically against this diff, not by inspection:

contract source: repo | task_type: library-change | max_files: 60
status: pass
  [PASS] path_wellformed     All changed paths are well-formed.
  [PASS] forbidden_paths     No forbidden paths touched.
  [PASS] allowed_paths       All changed files are within the allowed scope.
  [PASS] max_files_changed   37 file(s) changed (≤ 60).

.signetry/admission.yaml needed two scope additions for this change to be admissible
at all: LICENSELICENSE* (for LICENSE-Apache-2.0.txt) and install.sh. Both
are in the diff.

Verification

  • pytest tests/ -q298 passed (281 existing + 17 new conformance assertions).
  • signetry verify exercised against valid, auto-merge-true, and
    resigned-other-key: exit 0 / 1 / 1 respectively, with the expected reasons.
  • Every relative markdown link across the repo resolves, anchors included. Two
    pre-existing broken links in docs/RELEASING.md (repo-root-relative paths resolved
    from docs/) are fixed here.
  • ruff check on the new and edited files is clean. The repo's 73 pre-existing lint
    findings are untouched — out of scope for a licensing PR.

No functional change to admission

Authority is still earned from evidence, auto_merge is still false at every level,
and distribution is unchanged (installed from source by tag, not published to PyPI).
The only behavioural change is that signetry verify now refuses to bless a receipt
that breaks a format invariant.

…s open core)

Relicenses signetry-core from "All Rights Reserved" to BUSL-1.1, converting to
Apache-2.0 on 2030-08-31, and publishes the receipt format as an Apache-2.0
specification with a conformance suite that is explicitly excluded from the
BUSL Licensed Work.

The spec is the substantive half. A receipt has to be verifiable by someone who
does not have this tool and is reading it years later, so the format is an
interface: docs/RECEIPT_SPEC.md writes it down (RFC 2119, threat model, change
contract) and tests/conformance/ tests it with 7 committed language-agnostic
JSON vectors and published test-key seeds.

Two vectors carry the weight, because a signature-only verifier gets them wrong:
resigned-other-key.json (a self-consistent envelope signed with the attacker's
own keypair — verification must be against a pinned key, §8.1) and
auto-merge-true.json (correctly signed, still non-conforming). So the verifier
now checks conformance too:

- check_invariants(receipt) returns RECEIPT_SPEC §4.3 violations
- verify_receipt returns conforming/invariant_violations alongside the
  cryptographic result — separate questions, reported separately
- signetry verify exits non-zero on a non-conforming receipt and never prints a
  bare VERIFIED when an invariant is broken

The CLA's fallback grant changes from exclusive to non-exclusive: an exclusive
fallback could be read as stripping a contributor of the right to use their own
code, which was never the intent.

.signetry/admission.yaml gains LICENSE* and install.sh so this change is
admissible under core's own contract; evaluated empirically, status: pass.

pytest: 298 passed (281 existing + 17 conformance).
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Signetry Reviewer — 🟣 Escalate to a designated reviewer

Escalate to a designated reviewer — this PR touches security-sensitive surface (.github/workflows/cla.yml, .github/workflows/release.yml, .github/workflows/reviewer.yml). No blocking issue was found automatically, but a human owner should sign off.

Deterministic gates (the authority)

Gate Status
Required status check ⏳ pending
Secret scan ✅ clean
CI permission / OIDC ✅ no forbidden change
Dependency skew ✅ ok
All green

Findings (5, 0 blocking)

  • 🟡 Change touches a protected path: .github/workflows/cla.yml .github/workflows/cla.yml (via cross-check)
    • .github/workflows/cla.yml matches a protected pattern (.github/workflows/*). Changes here alter shared/foundational surface and warrant a designated reviewer.
    • Fix: Route to a code owner / architecture reviewer.
  • 🟡 Change touches a protected path: .github/workflows/release.yml .github/workflows/release.yml (via cross-check)
    • .github/workflows/release.yml matches a protected pattern (.github/workflows/*). Changes here alter shared/foundational surface and warrant a designated reviewer.
    • Fix: Route to a code owner / architecture reviewer.
  • 🟡 Change touches a protected path: .github/workflows/reviewer.yml .github/workflows/reviewer.yml (via cross-check)
    • .github/workflows/reviewer.yml matches a protected pattern (.github/workflows/*). Changes here alter shared/foundational surface and warrant a designated reviewer.
    • Fix: Route to a code owner / architecture reviewer.
  • 🟡 Change touches a protected path: pyproject.toml pyproject.toml (via cross-check)
    • pyproject.toml matches a protected pattern (pyproject.toml). Changes here alter shared/foundational surface and warrant a designated reviewer.
    • Fix: Route to a code owner / architecture reviewer.
  • 🟡 Change touches a protected path: signetry_core/pipeline/receipt.py signetry_core/pipeline/receipt.py (via cross-check)
    • signetry_core/pipeline/receipt.py matches a protected pattern (signetry_core/pipeline/receipt.py). Changes here alter shared/foundational surface and warrant a designated reviewer.
    • Fix: Route to a code owner / architecture reviewer.

Sensitive surface

This PR changes security-sensitive paths that warrant a designated reviewer:

  • .github/workflows/cla.yml
  • .github/workflows/release.yml
  • .github/workflows/reviewer.yml

Merge

A designated reviewer / code owner should sign off before merge (sensitive surface).

This review is advisory. It never merges on its own judgement — the deterministic gates + a human are the authority. Findings can have false negatives; a green bot verdict is not a guarantee.

§9 said provenance fields are OPTIONAL and "when absent the policy MUST be treated
as unsigned". `signetry init` writes `policy_owner: your-team`, which is *present* —
so the letter of the spec was satisfied by exactly the value that means nobody has
adopted the policy. A verifier testing for a non-empty string would report a policy
no human has read as change-controlled.

The addition closes that: presence is not a declaration. Scaffold placeholder text
MUST be treated as if the field were absent, because converting an unanswered
question into a false answer is strictly worse than reporting it unowned. Consumers
MAY distinguish placeholder from absent when explaining *why* a policy is unowned,
but neither may confer provenance.

No `version` bump: per §12 that is reserved for changes which would make a
conforming verifier reject a valid receipt, and this changes how a value is
interpreted and reported, not whether a receipt is accepted. The spec is being
published for the first time here, so this is part of v1 as published rather than an
amendment to it.

The reference implementation of this requirement is core#108, which stops
`policy_status()` returning `declared` for scaffold text. Until that lands, this
engine does not yet satisfy its own §9 — merge the two adjacently.
@bkd-dotcom

Copy link
Copy Markdown
Member Author

Pushed one more commit: spec: §9 — placeholder provenance MUST be treated as absent (40a1672).

§9 as originally written said provenance fields are OPTIONAL and "when absent the policy MUST be treated as unsigned". But signetry init writes policy_owner: your-team, which is present — so the letter of the spec was satisfied by exactly the value that means nobody has adopted the policy. A verifier testing for a non-empty string would report a policy no human has read as change-controlled.

Worth catching before publication, since this spec is the artifact people will implement against.

Two notes:

  • No version bump. Per §12 that is reserved for changes that would make a conforming verifier reject a valid receipt; this changes how a value is interpreted and reported, not whether a receipt is accepted. And since the spec is published for the first time in this PR, it is part of v1 as published rather than an amendment to it.
  • No conformance vector, consistent with existing practice — §12's "verifiers MUST ignore unknown members" has none either, and §11's list is introduced with "which include:". A vector for this becomes worthwhile once the reference implementation is in place.

⚠️ Merge order: the reference implementation is core#108, which stops policy_status() returning declared for scaffold text. Until #108 lands, this engine does not satisfy its own §9. Merge the two adjacently — either order works; they touch no common file except CHANGELOG.md.

@bkd-dotcom
bkd-dotcom merged commit aee3510 into main Sep 1, 2026
8 checks passed
@bkd-dotcom
bkd-dotcom deleted the open-core-relicense branch September 1, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant