Skip to content

fix(codex): write the provenance ledger, bounded (#2622) - #2626

Merged
lidge-jun merged 3 commits into
devfrom
codex/2622-provenance-writer-bounded
Aug 25, 2026
Merged

lidge-jun merged 3 commits into
devfrom
codex/2622-provenance-writer-bounded

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #2622. updateIntegrationRecord had no production caller, so the Codex provenance ledger was never written — a tested, exported, documented writer that nothing invoked, which reads as implemented. The apply and remove paths now append evidence for the artifacts a native transaction may mutate: config, generated-profile, and injection-journal.

Ordering is the load-bearing decision. The append runs after withCodexWriteLock returns an acquired transaction, so the native files and the coordinator row have already committed. A crash between the two leaves an admitted transaction with no ledger entry — safe, because absence grants no restore authority and existing acceptance already treats absence as unavailable evidence. Writing before the commit would be the unsafe direction: it would leave provenance for a transaction that never happened. The append is best-effort for the same reason; a non-CAS JSON failure must not turn a committed native transaction into a reported failure.

Bound added on review

As written the ledger was unbounded, and that is not a small leak. Each transaction appends three entries, and a present baseline embeds the artifact's exact bytes as base64 — a 25 KB config.toml measures ~34 KB per entry, so roughly 100 KB per transaction. A machine that syncs on every start grows integrations/codex.json without limit, and because the record is re-read and re-serialized on every append, the cost is quadratic rather than merely large.

The window keeps the newest 16 transactions, whole. Trimming by entry count would cut a transaction in half and leave a record claiming it touched two artifacts when it touched three — a partial record still reads as complete, which is worse than dropping it outright.

The append also now spreads the existing provenance object, so an unknown ledger-level key from a newer writer survives — the record contract requires that, and the original append dropped it.

Verification

bun x tsc --noEmit                                exit 0
bun test <inject-write-lock + integration-record
          + composed-acceptance + convergence>     40 pass / 0 fail
bun run privacy:scan                              Privacy scan passed

Falsified both ways: removing the production writer leaves an admitted transaction with [] matching artifacts instead of the three expected ones; removing the window reddens the trimming test while the within-window identity case stays green — which pins the bound as a bound rather than an unconditional filter.

Checklist

  • Targets dev
  • Design note at devlog/_plan/260826_provenance_writer/010_design.md
  • Records without provenance stay valid; unknown extensions preserved
  • No credential, auth, workflow or release-automation surface touched

Summary by CodeRabbit

  • New Features

    • Added provenance tracking for completed configuration, profile, and journal changes.
    • Records transaction details, timestamps, previous metadata, and resulting file fingerprints.
    • Retains the 16 most recent complete transactions while preserving compatibility with existing data.
    • Provenance data remains within the Codex home and is removed during uninstallation.
  • Bug Fixes

    • Invalid provenance data is safely rejected without altering existing files or transactions.

The writer appends three entries per admitted transaction, and a 'present'
baseline carries the artifact's exact bytes as base64 - a 25 KB config.toml is
~34 KB per entry, so roughly 100 KB per transaction. Unbounded, a machine that
syncs on every start grows integrations/codex.json forever, and since the record
is re-read and re-serialized on every append the cost is quadratic rather than
merely large.

A ledger is evidence, not an archive. The window keeps whole transactions:
trimming by entry count would cut one in half and leave a record claiming a
transaction touched two artifacts when it touched three, which reads as complete
and is worse than dropping it.

Also spreads the existing provenance object so an unknown ledger-level key from
a newer writer survives the append, which the record contract requires.

Falsified: removing the window reddens the trimming test and leaves the
within-window identity case green.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 25, 2026 20:57
@lidge-jun
lidge-jun merged commit c6084fe into dev Aug 25, 2026
6 of 7 checks passed
@lidge-jun
lidge-jun deleted the codex/2622-provenance-writer-bounded branch August 25, 2026 20:57
@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8b8bba7e-35bc-4f5a-9cd7-c9bd93fb530b

📥 Commits

Reviewing files that changed from the base of the PR and between 6f3b5af and 2df876f.

📒 Files selected for processing (4)
  • devlog/_plan/260826_provenance_writer/010_design.md
  • src/codex/inject-coordination.ts
  • src/codex/inject.ts
  • tests/codex-inject-write-lock.test.ts

📝 Walkthrough

Walkthrough

Codex apply and restore transactions now record bounded provenance for native artifacts. The writer stores pre-image metadata, post-image hashes, transaction IDs, and timestamps, preserves unknown integration fields, and handles malformed records without changing the admitted transition.

Changes

Codex provenance recording

Layer / File(s) Summary
Provenance model and retention
devlog/_plan/260826_provenance_writer/010_design.md:1-39, src/codex/inject-coordination.ts:14-19, src/codex/inject-coordination.ts:186-233
The design defines provenance fields and compatibility behavior. Helpers capture absent or present baselines, hash post-images, and retain complete transaction groups within the configured bound.
Native transaction provenance writer
src/codex/inject-coordination.ts:235-261
recordCodexNativeTransactionProvenance records provenance for config, generated profile, and injection journal artifacts before updating the integration record.
Apply and restore integration
src/codex/inject.ts:22, src/codex/inject.ts:1024-1045, src/codex/inject.ts:1600-1622, tests/codex-inject-write-lock.test.ts:18, tests/codex-inject-write-lock.test.ts:166-246, tests/codex-inject-write-lock.test.ts:469-511
Apply and restore pass lock-captured pre-images and transaction IDs to the writer. Tests cover unknown-field preservation, provenance entries, malformed storage, and transaction-bound retention.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CodexWritePath
  participant withCodexWriteLock
  participant recordCodexNativeTransactionProvenance
  participant IntegrationRecord
  CodexWritePath->>withCodexWriteLock: Commit apply or restore transaction
  withCodexWriteLock-->>CodexWritePath: Return preImages and currentTxId
  CodexWritePath->>recordCodexNativeTransactionProvenance: Record committed transaction
  recordCodexNativeTransactionProvenance->>IntegrationRecord: Update provenance ledger
Loading

Suggested reviewers: ingwannu

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/2622-provenance-writer-bounded

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2df876f193

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/codex/inject.ts
Comment on lines +1042 to +1045
recordCodexNativeTransactionProvenance(
coordinated.value.preImages,
coordinated.value.receipt.currentTxId,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Capture post-images before releasing the Codex lock

When two processes update the same Codex home, this call runs only after withCodexWriteLock has committed and released N, so a second transaction can replace any of the three files before provenancePostImage() reads them. The first transaction's ledger entries can therefore contain the second transaction's hashes, allowing later provenance-based restoration to mistake another writer's state for bytes owned by the first transaction. Compute and return the post-image hashes inside the locked callback, then append those captured values only after the transaction commits.

Useful? React with 👍 / 👎.

return {
kind: "present",
sha256: createHash("sha256").update(bytes).digest("hex"),
bytesBase64: Buffer.from(bytes).toString("base64"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid persisting credential-bearing config bytes

When config.toml contains credentials, this serializes its exact contents as reversible base64 into integrations/codex.json; the same module already acknowledges that config bytes can carry credentials. Unlike the transient journal, the bounded ledger retains up to 16 historical snapshots, including rotated secrets, expanding both their lifetime and storage surface. Store only non-reversible evidence in this record, or place the required restoration material behind an appropriate credential-storage boundary rather than embedding it in the ledger.

AGENTS.md reference: AGENTS.md:L266-L272

Useful? React with 👍 / 👎.

tarunravi pushed a commit to tarunravi/opencodex that referenced this pull request Sep 14, 2026
…dge-jun#2626)

* fix(codex): write admitted transaction provenance

* fix(provenance): bound the ledger to the newest 16 transactions

The writer appends three entries per admitted transaction, and a 'present'
baseline carries the artifact's exact bytes as base64 - a 25 KB config.toml is
~34 KB per entry, so roughly 100 KB per transaction. Unbounded, a machine that
syncs on every start grows integrations/codex.json forever, and since the record
is re-read and re-serialized on every append the cost is quadratic rather than
merely large.

A ledger is evidence, not an archive. The window keeps whole transactions:
trimming by entry count would cut one in half and leave a record claiming a
transaction touched two artifacts when it touched three, which reads as complete
and is worse than dropping it.

Also spreads the existing provenance object so an unknown ledger-level key from
a newer writer survives the append, which the record contract requires.

Falsified: removing the window reddens the trimming test and leaves the
within-window identity case green.
agentHits pushed a commit to agentHits/opencodex that referenced this pull request Sep 17, 2026
…dge-jun#2626)

* fix(codex): write admitted transaction provenance

* fix(provenance): bound the ledger to the newest 16 transactions

The writer appends three entries per admitted transaction, and a 'present'
baseline carries the artifact's exact bytes as base64 - a 25 KB config.toml is
~34 KB per entry, so roughly 100 KB per transaction. Unbounded, a machine that
syncs on every start grows integrations/codex.json forever, and since the record
is re-read and re-serialized on every append the cost is quadratic rather than
merely large.

A ledger is evidence, not an archive. The window keeps whole transactions:
trimming by entry count would cut one in half and leave a record claiming a
transaction touched two artifacts when it touched three, which reads as complete
and is worse than dropping it.

Also spreads the existing provenance object so an unknown ledger-level key from
a newer writer survives the append, which the record contract requires.

Falsified: removing the window reddens the trimming test and leaves the
within-window identity case green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant