Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Files whose exact Git and working-tree bytes are hashed by the release chain.
ledger/official_observations.jsonl text eol=lf
ledger/immutable_prefix.json text eol=lf
releases/manifests/*.json text eol=lf
releases/anchors/*.pem text eol=lf

# RFC 3161 timestamp responses are DER binary.
releases/manifests/*.tsr binary
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4

- name: Verify witnessed release chain
if: >-
github.event_name == 'push' &&
github.ref == 'refs/heads/codex/thesis-ledger-facts'
run: python3 scripts/verify_release_chain.py --full

- name: Install uv
uses: astral-sh/setup-uv@v5

Expand Down Expand Up @@ -65,8 +71,12 @@ jobs:
tests/test_hierarchical_pipeline.py
tests/test_supabase_client.py
tests/test_policyengine_ledger.py
tests/test_release_chain.py
tests/test_thesis_append_adversarial.py
scripts/check_thesis_facts_append.py
scripts/canonical_json.py
scripts/cut_release_manifest.py
scripts/verify_release_chain.py

- name: Test Arch surface
run: >
Expand All @@ -90,6 +100,8 @@ jobs:
tests/test_hierarchical_pipeline.py
tests/test_supabase_client.py
tests/test_policyengine_ledger.py
tests/test_release_chain.py
tests/test_thesis_append_adversarial.py
-q

- name: Build target input database
Expand Down
28 changes: 22 additions & 6 deletions .github/workflows/thesis-facts-append.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,33 @@ name: Thesis facts append gate
# Deterministic review for every change to the observation ledger. Resolver
# appends arrive as pull requests targeting codex/thesis-ledger-facts; this
# gate enforces the immutable frozen prefix, an append-only diff against the
# PR base, per-row schema and binding requirements, and explicit supersede
# semantics for corrections. Direct pushes get the same full-file checks.
# PR base, the witnessed release chain, per-row schema and binding requirements,
# and explicit supersede semantics for corrections. Direct pushes get the same
# full-file checks.

on:
pull_request:
branches:
- codex/thesis-ledger-facts
paths:
- "ledger/**"
- "releases/**"
- "scripts/check_thesis_facts_append.py"
- "scripts/canonical_json.py"
- "scripts/cut_release_manifest.py"
- "scripts/verify_release_chain.py"
- ".github/workflows/thesis-facts-append.yml"
push:
branches:
- codex/thesis-ledger-facts
paths:
- "ledger/**"
- "releases/**"
- "scripts/check_thesis_facts_append.py"
- "scripts/canonical_json.py"
- "scripts/cut_release_manifest.py"
- "scripts/verify_release_chain.py"
- ".github/workflows/thesis-facts-append.yml"

jobs:
append-gate:
Expand All @@ -35,10 +45,11 @@ jobs:
- name: Enforce append-only ledger invariants
run: |
set -euo pipefail
if [ -n "${{ github.base_ref }}" ]; then
git fetch origin "${{ github.base_ref }}" --depth=1
if [ "${{ github.event_name }}" = "pull_request" ]; then
base_sha="${{ github.event.pull_request.base.sha }}"
git cat-file -e "${base_sha}^{commit}"
python3 scripts/check_thesis_facts_append.py \
--base-ref "origin/${{ github.base_ref }}"
--base-ref "$base_sha"
else
python3 scripts/check_thesis_facts_append.py
fi
Expand All @@ -55,4 +66,9 @@ jobs:
run: uv sync --locked --all-extras

- name: Ledger observation invariants
run: uv run pytest tests/test_policyengine_ledger.py -q
run: >
uv run pytest
tests/test_policyengine_ledger.py
tests/test_release_chain.py
tests/test_thesis_append_adversarial.py
-q
5 changes: 5 additions & 0 deletions ledger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ forecast distributions, agent traces, or forecast scores.
official observations that downstream systems can use as resolution facts. Each
row should keep `source_record_id` stable and source-specific, because downstream
prediction systems resolve against that ID.

Proposed ledger states in the gated append flow are recorded in chained, RFC
3161-witnessed release manifests. See
[`../releases/README.md`](../releases/README.md) for the manifest schema, offline
verification procedure, and security limitations.
121 changes: 121 additions & 0 deletions releases/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Witnessed ledger releases

In the gated append flow, each proposed state of
`ledger/official_observations.jsonl` is committed by a canonical release manifest
and witnessed by two RFC 3161 timestamp authorities. The manifests form an
append-only hash chain. The receipts let a verifier show that the exact manifest
bytes existed no later than each receipt's `genTime`.

## Files

For a stem `<index>-<hash16>`, where `index` is the zero-padded four-digit
release index and `hash16` is the first 16 lowercase hexadecimal characters of
the manifest file's SHA-256 digest, a release consists of exactly these files:

- `releases/manifests/<stem>.json`
- `releases/manifests/<stem>.freetsa.tsr`
- `releases/manifests/<stem>.digicert.tsr`

For example, the receipt for manifest `0007-0123456789abcdef.json` is named
`0007-0123456789abcdef.freetsa.tsr`, not
`0007-0123456789abcdef.json.freetsa.tsr`. Receipts are DER-encoded RFC 3161
responses whose SHA-256 message imprint covers the manifest file's exact bytes.
Those bytes are canonical JSON produced by `scripts/canonical_json.py`, followed
by one newline.

The pinned TSA verification chains are committed as PEM files under
`releases/anchors/`. Verification does not contact either TSA or any other
network service.

## Manifest schema

Every manifest uses the closed-world `thesis_ledger_release_v1` schema. Unknown
keys are invalid at every level, and counts are JSON integers (booleans are not
accepted as integers). The required root members are:

- `schemaVersion`: the literal `"thesis_ledger_release_v1"`.
- `releaseIndex`: a contiguous integer beginning at zero.
- `previousManifestSha256`: `null` for genesis; otherwise the full lowercase
SHA-256 digest of the previous manifest file's exact bytes.
- `state`: an object containing only:
- `path`: the literal `"ledger/official_observations.jsonl"`.
- `jsonlSha256`: the lowercase SHA-256 digest of the ledger bytes represented
by this release.
- `lineCount`: the number of JSONL rows represented by this release.
- `immutablePrefixSha256`: the lowercase SHA-256 digest of the exact bytes of
`ledger/immutable_prefix.json`.
- `append`: `null` for genesis; otherwise an object containing only:
- `previousLineCount`: the preceding manifest's `state.lineCount`.
- `appendedRowCount`: the number of newly appended JSONL rows.
- `appendedBytesSha256`: the lowercase SHA-256 digest of the exact byte suffix
added after the preceding state.
- `createdAtUtc`: a strict UTC timestamp ending in `Z`.
- `producer`: an object containing only the free-form provenance strings `repo`
and `branch`. These strings are recorded claims, not trusted authorization.

Genesis has index zero, a null previous hash, and a null append block. Every
later release increments the index by one, hashes the preceding manifest file,
increases the line count, and binds both the row-count delta and the exact byte
suffix in its append block. Both receipts must verify against their separately
pinned anchor chain and cover that release's exact manifest bytes.

## Offline verification

Clone the repository at the state you want to inspect and run:

```console
python3 scripts/verify_release_chain.py --full
```

The verifier needs only Python, OpenSSL, the committed manifests and receipts,
the committed TSA anchors, and the ledger files. It checks canonical bytes,
filenames, contiguous indices, previous-manifest links, state and append
commitments, both timestamp receipts, and timestamp ordering. Any mismatch exits
nonzero with an error identifying the failed invariant.

Retain a trusted checkpoint outside this repository, at minimum the full SHA-256
digest of a previously accepted head manifest, and compare it with later clones.
Internal verification proves that a clone is self-consistent; by itself it cannot
distinguish the original history from a complete, freshly witnessed replacement
fork. RFC 3161 timestamp authorities attest timestamps for submitted digests but
do not publish a uniqueness-enforcing append-only ledger for this repository.

## Security properties and limits

Under the configured proposal gate, every ledger append arrives with the next
manifest and both receipts. This binds the proposed ledger state, immutable
prefix, previous release, row-count change, and exact appended byte suffix into a
witnessed chain. Rewriting a checkpointed manifest or producing an unwitnessed or
internally inconsistent state is therefore detectable by any verifier holding
that checkpoint.

An RFC 3161 `genTime` establishes that the manifest existed no later than that
time. It does **not** timestamp GitHub's merge or prove when the organization
accepted the proposal. In the intended pull-request flow the receipts are created
before the gate can pass and therefore before merge; their times do not upper-bound
the later acceptance time.

This mechanism provides tamper evidence, not multi-party authorization or admin
non-repudiation. Governance remains within one GitHub organization:

- It does not require approval by an independent institution or a second party.
- An unwitnessed or inconsistent admin direct push turns verification and CI red,
but repository controls cannot prevent every admin bypass.
- An admin direct push that includes a valid next manifest and both valid receipts
is neither prevented nor distinguishable by offline cryptographic verification
from an append merged through the intended pull-request path.
- The verifier, workflows, and anchors live in the repository they verify. Running
the verifier from a clone assumes those security files were not weakened in the
same rewrite; independent verification should pin or audit them separately as
well as retaining a manifest checkpoint.
- The manifest chain hashes manifests, not receipt files. A receipt can be
replaced by a newer valid receipt over the same manifest without changing later
manifest hashes unless its exact bytes or digest were retained externally.
- The production anchors are fixed. There is no in-schema anchor-rotation
protocol, so a TSA chain change requires an explicit verifier/schema migration
that continues to preserve verification of the old chain.
- Four-digit indices cap this filename format at release 9999; the current schema
does not define a rollover.

These limitations are why a retained external checkpoint is essential and why
the current design should not be described as cross-institution governance.
32 changes: 32 additions & 0 deletions releases/anchors/digicert-trusted-root-g4.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
-----END CERTIFICATE-----
45 changes: 45 additions & 0 deletions releases/anchors/freetsa-root-2016.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
-----BEGIN CERTIFICATE-----
MIIH/zCCBeegAwIBAgIJAMHphhYNqOmAMA0GCSqGSIb3DQEBDQUAMIGVMREwDwYD
VQQKEwhGcmVlIFRTQTEQMA4GA1UECxMHUm9vdCBDQTEYMBYGA1UEAxMPd3d3LmZy
ZWV0c2Eub3JnMSIwIAYJKoZIhvcNAQkBFhNidXNpbGV6YXNAZ21haWwuY29tMRIw
EAYDVQQHEwlXdWVyemJ1cmcxDzANBgNVBAgTBkJheWVybjELMAkGA1UEBhMCREUw
HhcNMTYwMzEzMDE1MjEzWhcNNDEwMzA3MDE1MjEzWjCBlTERMA8GA1UEChMIRnJl
ZSBUU0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9y
ZzEiMCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJ
V3VlcnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFMIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtgKODjAy8REQ2WTNqUudAnjhlCrpE6ql
mQfNppeTmVvZrH4zutn+NwTaHAGpjSGv4/WRpZ1wZ3BRZ5mPUBZyLgq0YrIfQ5Fx
0s/MRZPzc1r3lKWrMR9sAQx4mN4z11xFEO529L0dFJjPF9MD8Gpd2feWzGyptlel
b+PqT+++fOa2oY0+NaMM7l/xcNHPOaMz0/2olk0i22hbKeVhvokPCqhFhzsuhKsm
q4Of/o+t6dI7sx5h0nPMm4gGSRhfq+z6BTRgCrqQG2FOLoVFgt6iIm/BnNffUr7V
DYd3zZmIwFOj/H3DKHoGik/xK3E82YA2ZulVOFRW/zj4ApjPa5OFbpIkd0pmzxzd
EcL479hSA9dFiyVmSxPtY5ze1P+BE9bMU1PScpRzw8MHFXxyKqW13Qv7LWw4sbk3
SciB7GACbQiVGzgkvXG6y85HOuvWNvC5GLSiyP9GlPB0V68tbxz4JVTRdw/Xn/XT
FNzRBM3cq8lBOAVt/PAX5+uFcv1S9wFE8YjaBfWCP1jdBil+c4e+0tdywT2oJmYB
BF/kEt1wmGwMmHunNEuQNzh1FtJY54hbUfiWi38mASE7xMtMhfj/C4SvapiDN837
gYaPfs8x3KZxbX7C3YAsFnJinlwAUss1fdKar8Q/YVs7H/nU4c4Ixxxz4f67fcVq
M2ITKentbCMCAwEAAaOCAk4wggJKMAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQD
AgHGMB0GA1UdDgQWBBT6VQ2MNGZRQ0z357OnbJWveuaklzCBygYDVR0jBIHCMIG/
gBT6VQ2MNGZRQ0z357OnbJWveuakl6GBm6SBmDCBlTERMA8GA1UEChMIRnJlZSBU
U0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9yZzEi
MCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJV3Vl
cnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFggkAwemGFg2o6YAw
MwYDVR0fBCwwKjAooCagJIYiaHR0cDovL3d3dy5mcmVldHNhLm9yZy9yb290X2Nh
LmNybDCBzwYDVR0gBIHHMIHEMIHBBgorBgEEAYHyJAEBMIGyMDMGCCsGAQUFBwIB
FidodHRwOi8vd3d3LmZyZWV0c2Eub3JnL2ZyZWV0c2FfY3BzLmh0bWwwMgYIKwYB
BQUHAgEWJmh0dHA6Ly93d3cuZnJlZXRzYS5vcmcvZnJlZXRzYV9jcHMucGRmMEcG
CCsGAQUFBwICMDsaOUZyZWVUU0EgdHJ1c3RlZCB0aW1lc3RhbXBpbmcgU29mdHdh
cmUgYXMgYSBTZXJ2aWNlIChTYWFTKTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUH
MAGGG2h0dHA6Ly93d3cuZnJlZXRzYS5vcmc6MjU2MDANBgkqhkiG9w0BAQ0FAAOC
AgEAaK9+v5OFYu9M6ztYC+L69sw1omdyli89lZAfpWMMh9CRmJhM6KBqM/ipwoLt
nxyxGsbCPhcQjuTvzm+ylN6VwTMmIlVyVSLKYZcdSjt/eCUN+41K7sD7GVmxZBAF
ILnBDmTGJmLkrU0KuuIpj8lI/E6Z6NnmuP2+RAQSHsfBQi6sssnXMo4HOW5gtPO7
gDrUpVXID++1P4XndkoKn7Svw5n0zS9fv1hxBcYIHPPQUze2u30bAQt0n0iIyRLz
aWuhtpAtd7ffwEbASgzB7E+NGF4tpV37e8KiA2xiGSRqT5ndu28fgpOY87gD3ArZ
DctZvvTCfHdAS5kEO3gnGGeZEVLDmfEsv8TGJa3AljVa5E40IQDsUXpQLi8G+UC4
1DWZu8EVT4rnYaCw1VX7ShOR1PNCCvjb8S8tfdudd9zhU3gEB0rxdeTy1tVbNLXW
99y90xcwr1ZIDUwM/xQ/noO8FRhm0LoPC73Ef+J4ZBdrvWwauF3zJe33d4ibxEcb
8/pz5WzFkeixYM2nsHhqHsBKw7JPouKNXRnl5IAE1eFmqDyC7G/VT7OF669xM6hb
Ut5G21JE4cNK6NNucS+fzg1JPX0+3VhsYZjj7D5uljRvQXrJ8iHgr/M6j2oLHvTA
I2MLdq2qjZFDOCXsxBxJpbmLGBx9ow6ZerlUxzws2AWv2pk=
-----END CERTIFICATE-----
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions releases/manifests/0000-307cedbc91de43be.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"append":null,"createdAtUtc":"2026-07-11T14:13:39Z","previousManifestSha256":null,"producer":{"branch":"witnessed-journal","repo":"PolicyEngine/ledger"},"releaseIndex":0,"schemaVersion":"thesis_ledger_release_v1","state":{"immutablePrefixSha256":"db5d69575db0adb505b1b46b0496befb852315b3ff8c7690313fe77319578c0b","jsonlSha256":"590b71bed9a7fc0354f2449c5e533ed20b508fb3554e395b1bdd283b945f0ea5","lineCount":143,"path":"ledger/official_observations.jsonl"}}
Loading
Loading